Main Page | Modules | Namespace List | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals | Related Pages | Examples

htpasswd.c

Go to the documentation of this file.
00001 /* Copyright 1999-2005 The Apache Software Foundation or its licensors, as
00002  * applicable.
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  *     http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 
00017 /******************************************************************************
00018  ******************************************************************************
00019  * NOTE! This program is not safe as a setuid executable!  Do not make it
00020  * setuid!
00021  ******************************************************************************
00022  *****************************************************************************/
00023 /*
00024  * htpasswd.c: simple program for manipulating password file for
00025  * the Apache HTTP server
00026  * 
00027  * Originally by Rob McCool
00028  *
00029  * Exit values:
00030  *  0: Success
00031  *  1: Failure; file access/permission problem
00032  *  2: Failure; command line syntax problem (usage message issued)
00033  *  3: Failure; password verification failure
00034  *  4: Failure; operation interrupted (such as with CTRL/C)
00035  *  5: Failure; buffer would overflow (username, filename, or computed
00036  *     record too long)
00037  *  6: Failure; username contains illegal or reserved characters
00038  *  7: Failure; file is not a valid htpasswd file
00039  */
00040 
00041 #include "apr.h"
00042 #include "apr_lib.h"
00043 #include "apr_strings.h"
00044 #include "apr_errno.h"
00045 #include "apr_file_io.h"
00046 #include "apr_general.h"
00047 #include "apr_signal.h"
00048 
00049 #if APR_HAVE_STDIO_H
00050 #include <stdio.h>
00051 #endif
00052 
00053 #include "apr_md5.h"
00054 #include "apr_sha1.h"
00055 #include <time.h>
00056 
00057 #if APR_HAVE_CRYPT_H
00058 #include <crypt.h>
00059 #endif
00060 #if APR_HAVE_STDLIB_H
00061 #include <stdlib.h>
00062 #endif
00063 #if APR_HAVE_STRING_H
00064 #include <string.h>
00065 #endif
00066 #if APR_HAVE_UNISTD_H
00067 #include <unistd.h>
00068 #endif
00069 
00070 #ifdef WIN32
00071 #include <conio.h>
00072 #define unlink _unlink
00073 #endif
00074 
00075 #if !APR_CHARSET_EBCDIC
00076 #define LF 10
00077 #define CR 13
00078 #else /*APR_CHARSET_EBCDIC*/
00079 #define LF '\n'
00080 #define CR '\r'
00081 #endif /*APR_CHARSET_EBCDIC*/
00082 
00083 #define MAX_STRING_LEN 256
00084 #define ALG_PLAIN 0
00085 #define ALG_CRYPT 1
00086 #define ALG_APMD5 2
00087 #define ALG_APSHA 3 
00088 
00089 #define ERR_FILEPERM 1
00090 #define ERR_SYNTAX 2
00091 #define ERR_PWMISMATCH 3
00092 #define ERR_INTERRUPTED 4
00093 #define ERR_OVERFLOW 5
00094 #define ERR_BADUSER 6
00095 #define ERR_INVALID 7
00096 
00097 #define APHTP_NEWFILE        1
00098 #define APHTP_NOFILE         2
00099 #define APHTP_NONINTERACTIVE 4
00100 #define APHTP_DELUSER        8
00101 
00102 apr_file_t *errfile;
00103 apr_file_t *ftemp = NULL;
00104 
00105 static void to64(char *s, unsigned long v, int n)
00106 {
00107     static unsigned char itoa64[] =         /* 0 ... 63 => ASCII - 64 */
00108         "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
00109 
00110     while (--n >= 0) {
00111         *s++ = itoa64[v&0x3f];
00112         v >>= 6;
00113     }
00114 }
00115 
00116 static void putline(apr_file_t *f, const char *l)
00117 {
00118     apr_file_puts(l, f);
00119 }
00120 
00121 /*
00122  * Make a password record from the given information.  A zero return
00123  * indicates success; failure means that the output buffer contains an
00124  * error message instead.
00125  */
00126 static int mkrecord(char *user, char *record, apr_size_t rlen, char *passwd,
00127                     int alg)
00128 {
00129     char *pw;
00130     char cpw[120];
00131     char pwin[MAX_STRING_LEN];
00132     char pwv[MAX_STRING_LEN];
00133     char salt[9];
00134     apr_size_t bufsize;
00135 
00136     if (passwd != NULL) {
00137         pw = passwd;
00138     }
00139     else {
00140         bufsize = sizeof(pwin);
00141         if (apr_password_get("New password: ", pwin, &bufsize) != 0) {
00142             apr_snprintf(record, (rlen - 1), "password too long (>%" 
00143                          APR_SIZE_T_FMT ")", sizeof(pwin) - 1);
00144             return ERR_OVERFLOW;
00145         }
00146         bufsize = sizeof(pwv);
00147         apr_password_get("Re-type new password: ", pwv, &bufsize);
00148         if (strcmp(pwin, pwv) != 0) {
00149             apr_cpystrn(record, "password verification error", (rlen - 1));
00150             return ERR_PWMISMATCH;
00151         }
00152         pw = pwin;
00153         memset(pwv, '\0', sizeof(pwin));
00154     }
00155     switch (alg) {
00156 
00157     case ALG_APSHA:
00158         /* XXX cpw >= 28 + strlen(sha1) chars - fixed len SHA */
00159         apr_sha1_base64(pw,strlen(pw),cpw);
00160         break;
00161 
00162     case ALG_APMD5: 
00163         (void) srand((int) time((time_t *) NULL));
00164         to64(&salt[0], rand(), 8);
00165         salt[8] = '\0';
00166 
00167         apr_md5_encode((const char *)pw, (const char *)salt,
00168                      cpw, sizeof(cpw));
00169         break;
00170 
00171     case ALG_PLAIN:
00172         /* XXX this len limitation is not in sync with any HTTPd len. */
00173         apr_cpystrn(cpw,pw,sizeof(cpw));
00174         break;
00175 
00176 #if !(defined(WIN32) || defined(NETWARE))
00177     case ALG_CRYPT:
00178     default:
00179         (void) srand((int) time((time_t *) NULL));
00180         to64(&salt[0], rand(), 8);
00181         salt[8] = '\0';
00182 
00183         apr_cpystrn(cpw, (char *)crypt(pw, salt), sizeof(cpw) - 1);
00184         break;
00185 #endif
00186     }
00187     memset(pw, '\0', strlen(pw));
00188 
00189     /*
00190      * Check to see if the buffer is large enough to hold the username,
00191      * hash, and delimiters.
00192      */
00193     if ((strlen(user) + 1 + strlen(cpw)) > (rlen - 1)) {
00194         apr_cpystrn(record, "resultant record too long", (rlen - 1));
00195         return ERR_OVERFLOW;
00196     }
00197     strcpy(record, user);
00198     strcat(record, ":");
00199     strcat(record, cpw);
00200     strcat(record, "\n");
00201     return 0;
00202 }
00203 
00204 static void usage(void)
00205 {
00206     apr_file_printf(errfile, "Usage:\n");
00207     apr_file_printf(errfile, "\thtpasswd [-cmdpsD] passwordfile username\n");
00208     apr_file_printf(errfile, "\thtpasswd -b[cmdpsD] passwordfile username "
00209                     "password\n\n");
00210     apr_file_printf(errfile, "\thtpasswd -n[mdps] username\n");
00211     apr_file_printf(errfile, "\thtpasswd -nb[mdps] username password\n");
00212     apr_file_printf(errfile, " -c  Create a new file.\n");
00213     apr_file_printf(errfile, " -n  Don't update file; display results on "
00214                     "stdout.\n");
00215     apr_file_printf(errfile, " -m  Force MD5 encryption of the password"
00216 #if defined(WIN32) || defined(TPF) || defined(NETWARE)
00217         " (default)"
00218 #endif
00219         ".\n");
00220     apr_file_printf(errfile, " -d  Force CRYPT encryption of the password"
00221 #if (!(defined(WIN32) || defined(TPF) || defined(NETWARE)))
00222             " (default)"
00223 #endif
00224             ".\n");
00225     apr_file_printf(errfile, " -p  Do not encrypt the password (plaintext).\n");
00226     apr_file_printf(errfile, " -s  Force SHA encryption of the password.\n");
00227     apr_file_printf(errfile, " -b  Use the password from the command line "
00228             "rather than prompting for it.\n");
00229     apr_file_printf(errfile, " -D  Delete the specified user.\n");
00230     apr_file_printf(errfile,
00231             "On Windows, NetWare and TPF systems the '-m' flag is used by "
00232             "default.\n");
00233     apr_file_printf(errfile,
00234             "On all other systems, the '-p' flag will probably not work.\n");
00235     exit(ERR_SYNTAX);
00236 }
00237 
00238 /*
00239  * Check to see if the specified file can be opened for the given
00240  * access.
00241  */
00242 static int accessible(apr_pool_t *pool, char *fname, int mode)
00243 {
00244     apr_file_t *f = NULL;
00245 
00246     if (apr_file_open(&f, fname, mode, APR_OS_DEFAULT, pool) != APR_SUCCESS) {
00247         return 0;
00248     }
00249     apr_file_close(f);
00250     return 1;
00251 }
00252 
00253 /*
00254  * Return true if the named file exists, regardless of permissions.
00255  */
00256 static int exists(char *fname, apr_pool_t *pool)
00257 {
00258     apr_finfo_t sbuf;
00259     apr_status_t check;
00260 
00261     check = apr_stat(&sbuf, fname, APR_FINFO_TYPE, pool);
00262     return ((check || sbuf.filetype != APR_REG) ? 0 : 1);
00263 }
00264 
00265 static void terminate(void)
00266 {
00267     apr_terminate();
00268 #ifdef NETWARE
00269     pressanykey();
00270 #endif
00271 }
00272 
00273 static void check_args(apr_pool_t *pool, int argc, const char *const argv[], 
00274                        int *alg, int *mask, char **user, char **pwfilename, 
00275                        char **password)
00276 {
00277     const char *arg;
00278     int args_left = 2;
00279     int i;
00280 
00281     /*
00282      * Preliminary check to make sure they provided at least
00283      * three arguments, we'll do better argument checking as 
00284      * we parse the command line.
00285      */
00286     if (argc < 3) {
00287         usage();
00288     }
00289 
00290     /*
00291      * Go through the argument list and pick out any options.  They
00292      * have to precede any other arguments.
00293      */
00294     for (i = 1; i < argc; i++) {
00295         arg = argv[i];
00296         if (*arg != '-') {
00297             break;
00298         }
00299         while (*++arg != '\0') {
00300             if (*arg == 'c') {
00301                 *mask |= APHTP_NEWFILE;
00302             }
00303             else if (*arg == 'n') {
00304                 *mask |= APHTP_NOFILE;
00305                 args_left--;
00306             }
00307             else if (*arg == 'm') {
00308                 *alg = ALG_APMD5;
00309             }
00310             else if (*arg == 's') {
00311                 *alg = ALG_APSHA;
00312             }
00313             else if (*arg == 'p') {
00314                 *alg = ALG_PLAIN;
00315             }
00316             else if (*arg == 'd') {
00317                 *alg = ALG_CRYPT;
00318             }
00319             else if (*arg == 'b') {
00320                 *mask |= APHTP_NONINTERACTIVE;
00321                 args_left++;
00322             }
00323             else if (*arg == 'D') {
00324                 *mask |= APHTP_DELUSER;
00325             }
00326             else {
00327                 usage();
00328             }
00329         }
00330     }
00331 
00332     if ((*mask & APHTP_NEWFILE) && (*mask & APHTP_NOFILE)) {
00333         apr_file_printf(errfile, "%s: -c and -n options conflict\n", argv[0]);
00334         exit(ERR_SYNTAX);
00335     }
00336     if ((*mask & APHTP_NEWFILE) && (*mask & APHTP_DELUSER)) {
00337         apr_file_printf(errfile, "%s: -c and -D options conflict\n", argv[0]);
00338         exit(ERR_SYNTAX);
00339     }
00340     if ((*mask & APHTP_NOFILE) && (*mask & APHTP_DELUSER)) {
00341         apr_file_printf(errfile, "%s: -n and -D options conflict\n", argv[0]);
00342         exit(ERR_SYNTAX);
00343     }
00344     /*
00345      * Make sure we still have exactly the right number of arguments left
00346      * (the filename, the username, and possibly the password if -b was
00347      * specified).
00348      */
00349     if ((argc - i) != args_left) {
00350         usage();
00351     }
00352 
00353     if (*mask & APHTP_NOFILE) {
00354         i--;
00355     }
00356     else {
00357         if (strlen(argv[i]) > (APR_PATH_MAX - 1)) {
00358             apr_file_printf(errfile, "%s: filename too long\n", argv[0]);
00359             exit(ERR_OVERFLOW);
00360         }
00361         *pwfilename = apr_pstrdup(pool, argv[i]);
00362         if (strlen(argv[i + 1]) > (MAX_STRING_LEN - 1)) {
00363             apr_file_printf(errfile, "%s: username too long (> %d)\n",
00364                 argv[0], MAX_STRING_LEN - 1);
00365             exit(ERR_OVERFLOW);
00366         }
00367     }
00368     *user = apr_pstrdup(pool, argv[i + 1]);
00369     if ((arg = strchr(*user, ':')) != NULL) {
00370         apr_file_printf(errfile, "%s: username contains illegal "
00371                         "character '%c'\n", argv[0], *arg);
00372         exit(ERR_BADUSER);
00373     }
00374     if (*mask & APHTP_NONINTERACTIVE) {
00375         if (strlen(argv[i + 2]) > (MAX_STRING_LEN - 1)) {
00376             apr_file_printf(errfile, "%s: password too long (> %d)\n",
00377                 argv[0], MAX_STRING_LEN);
00378             exit(ERR_OVERFLOW);
00379         }
00380         *password = apr_pstrdup(pool, argv[i + 2]);
00381     }
00382 }
00383 
00384 /*
00385  * Let's do it.  We end up doing a lot of file opening and closing,
00386  * but what do we care?  This application isn't run constantly.
00387  */
00388 int main(int argc, const char * const argv[])
00389 {
00390     apr_file_t *fpw = NULL;
00391     char record[MAX_STRING_LEN];
00392     char line[MAX_STRING_LEN];
00393     char *password = NULL;
00394     char *pwfilename = NULL;
00395     char *user = NULL;
00396     char tn[] = "htpasswd.tmp.XXXXXX";
00397     char *dirname;
00398     char *scratch, cp[MAX_STRING_LEN];
00399     int found = 0;
00400     int i;
00401     int alg = ALG_CRYPT;
00402     int mask = 0;
00403     apr_pool_t *pool;
00404     int existing_file = 0;
00405 #if APR_CHARSET_EBCDIC
00406     apr_status_t rv;
00407     apr_xlate_t *to_ascii;
00408 #endif
00409 
00410     apr_app_initialize(&argc, &argv, NULL);
00411     atexit(terminate);
00412     apr_pool_create(&pool, NULL);
00413     apr_file_open_stderr(&errfile, pool);
00414 
00415 #if APR_CHARSET_EBCDIC
00416     rv = apr_xlate_open(&to_ascii, "ISO8859-1", APR_DEFAULT_CHARSET, pool);
00417     if (rv) {
00418         apr_file_printf(errfile, "apr_xlate_open(to ASCII)->%d\n", rv);
00419         exit(1);
00420     }
00421     rv = apr_SHA1InitEBCDIC(to_ascii);
00422     if (rv) {
00423         apr_file_printf(errfile, "apr_SHA1InitEBCDIC()->%d\n", rv);
00424         exit(1);
00425     }
00426     rv = apr_MD5InitEBCDIC(to_ascii);
00427     if (rv) {
00428         apr_file_printf(errfile, "apr_MD5InitEBCDIC()->%d\n", rv);
00429         exit(1);
00430     }
00431 #endif /*APR_CHARSET_EBCDIC*/
00432 
00433     check_args(pool, argc, argv, &alg, &mask, &user, &pwfilename, &password);
00434 
00435 
00436 #if defined(WIN32) || defined(NETWARE)
00437     if (alg == ALG_CRYPT) {
00438         alg = ALG_APMD5;
00439         apr_file_printf(errfile, "Automatically using MD5 format.\n");
00440     }
00441 #endif
00442 
00443 #if (!(defined(WIN32) || defined(TPF) || defined(NETWARE)))
00444     if (alg == ALG_PLAIN) {
00445         apr_file_printf(errfile,"Warning: storing passwords as plain text "
00446                         "might just not work on this platform.\n");
00447     }
00448 #endif
00449 
00450     /*
00451      * Only do the file checks if we're supposed to frob it.
00452      */
00453     if (!(mask & APHTP_NOFILE)) {
00454         existing_file = exists(pwfilename, pool);
00455         if (existing_file) {
00456             /*
00457              * Check that this existing file is readable and writable.
00458              */
00459             if (!accessible(pool, pwfilename, APR_READ | APR_APPEND)) {
00460                 apr_file_printf(errfile, "%s: cannot open file %s for "
00461                                 "read/write access\n", argv[0], pwfilename);
00462                 exit(ERR_FILEPERM);
00463             }
00464         }
00465         else {
00466             /*
00467              * Error out if -c was omitted for this non-existant file.
00468              */
00469             if (!(mask & APHTP_NEWFILE)) {
00470                 apr_file_printf(errfile,
00471                         "%s: cannot modify file %s; use '-c' to create it\n",
00472                         argv[0], pwfilename);
00473                 exit(ERR_FILEPERM);
00474             }
00475             /*
00476              * As it doesn't exist yet, verify that we can create it.
00477              */
00478             if (!accessible(pool, pwfilename, APR_CREATE | APR_WRITE)) {
00479                 apr_file_printf(errfile, "%s: cannot create file %s\n",
00480                                 argv[0], pwfilename);
00481                 exit(ERR_FILEPERM);
00482             }
00483         }
00484     }
00485 
00486     /*
00487      * All the file access checks (if any) have been made.  Time to go to work;
00488      * try to create the record for the username in question.  If that
00489      * fails, there's no need to waste any time on file manipulations.
00490      * Any error message text is returned in the record buffer, since
00491      * the mkrecord() routine doesn't have access to argv[].
00492      */
00493     if (!(mask & APHTP_DELUSER)) {
00494         i = mkrecord(user, record, sizeof(record) - 1,
00495                      password, alg);
00496         if (i != 0) {
00497             apr_file_printf(errfile, "%s: %s\n", argv[0], record);
00498             exit(i);
00499         }
00500         if (mask & APHTP_NOFILE) {
00501             printf("%s\n", record);
00502             exit(0);
00503         }
00504     }
00505 
00506     /*
00507      * We can access the files the right way, and we have a record
00508      * to add or update.  Let's do it..
00509      */
00510     if (apr_temp_dir_get((const char**)&dirname, pool) != APR_SUCCESS) {
00511         apr_file_printf(errfile, "%s: could not determine temp dir\n",
00512                         argv[0]);
00513         exit(ERR_FILEPERM);
00514     }
00515     dirname = apr_psprintf(pool, "%s/%s", dirname, tn);
00516 
00517     if (apr_file_mktemp(&ftemp, dirname, 0, pool) != APR_SUCCESS) {
00518         apr_file_printf(errfile, "%s: unable to create temporary file %s\n", 
00519                         argv[0], dirname);
00520         exit(ERR_FILEPERM);
00521     }
00522 
00523     /*
00524      * If we're not creating a new file, copy records from the existing
00525      * one to the temporary file until we find the specified user.
00526      */
00527     if (existing_file && !(mask & APHTP_NEWFILE)) {
00528         if (apr_file_open(&fpw, pwfilename, APR_READ | APR_BUFFERED,
00529                           APR_OS_DEFAULT, pool) != APR_SUCCESS) {
00530             apr_file_printf(errfile, "%s: unable to read file %s\n", 
00531                             argv[0], pwfilename);
00532             exit(ERR_FILEPERM);
00533         }
00534         while (apr_file_gets(line, sizeof(line), fpw) == APR_SUCCESS) {
00535             char *colon;
00536 
00537             strcpy(cp, line);
00538             scratch = cp;
00539             while (apr_isspace(*scratch)) {
00540                 ++scratch;
00541             }
00542 
00543             if (!*scratch || (*scratch == '#')) {
00544                 putline(ftemp, line);
00545                 continue;
00546             }
00547             /*
00548              * See if this is our user.
00549              */
00550             colon = strchr(scratch, ':');
00551             if (colon != NULL) {
00552                 *colon = '\0';
00553             }
00554             else {
00555                 /*
00556                  * If we've not got a colon on the line, this could well 
00557                  * not be a valid htpasswd file.
00558                  * We should bail at this point.
00559                  */
00560                 apr_file_printf(errfile, "\n%s: The file %s does not appear "
00561                                          "to be a valid htpasswd file.\n",
00562                                 argv[0], pwfilename);
00563                 apr_file_close(fpw);
00564                 exit(ERR_INVALID);
00565             }
00566             if (strcmp(user, scratch) != 0) {
00567                 putline(ftemp, line);
00568                 continue;
00569             }
00570             else {
00571                 if (!(mask & APHTP_DELUSER)) {
00572                     /* We found the user we were looking for.
00573                      * Add him to the file.
00574                     */
00575                     apr_file_printf(errfile, "Updating ");
00576                     putline(ftemp, record);
00577                     found++;
00578                 }
00579                 else {
00580                     /* We found the user we were looking for.
00581                      * Delete them from the file.
00582                      */
00583                     apr_file_printf(errfile, "Deleting ");
00584                     found++;
00585                 }
00586             }
00587         }
00588         apr_file_close(fpw);
00589     }
00590     if (!found && !(mask & APHTP_DELUSER)) {
00591         apr_file_printf(errfile, "Adding ");
00592         putline(ftemp, record);
00593     }
00594     else if (!found && (mask & APHTP_DELUSER)) {
00595         apr_file_printf(errfile, "User %s not found\n", user);
00596         exit(0);
00597     }
00598     apr_file_printf(errfile, "password for user %s\n", user);
00599 
00600     /* The temporary file has all the data, just copy it to the new location.
00601      */
00602     if (apr_file_copy(dirname, pwfilename, APR_FILE_SOURCE_PERMS, pool) !=
00603         APR_SUCCESS) {
00604         apr_file_printf(errfile, "%s: unable to update file %s\n", 
00605                         argv[0], pwfilename);
00606         exit(ERR_FILEPERM);
00607     }
00608     apr_file_close(ftemp);
00609     return 0;
00610 }