| /* Copyright 1999-2004 The Apache Software Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /****************************************************************************** |
| ****************************************************************************** |
| * NOTE! This program is not safe as a setuid executable! Do not make it |
| * setuid! |
| ****************************************************************************** |
| *****************************************************************************/ |
| /* |
| * htpasswd.c: simple program for manipulating password file for |
| * the Apache HTTP server |
| * |
| * Originally by Rob McCool |
| * |
| * Exit values: |
| * 0: Success |
| * 1: Failure; file access/permission problem |
| * 2: Failure; command line syntax problem (usage message issued) |
| * 3: Failure; password verification failure |
| * 4: Failure; operation interrupted (such as with CTRL/C) |
| * 5: Failure; buffer would overflow (username, filename, or computed |
| * record too long) |
| * 6: Failure; username contains illegal or reserved characters |
| * 7: Failure; file is not a valid htpasswd file |
| */ |
| |
| #include "apr.h" |
| #include "apr_lib.h" |
| #include "apr_strings.h" |
| #include "apr_errno.h" |
| #include "apr_file_io.h" |
| #include "apr_general.h" |
| #include "apr_signal.h" |
| |
| #if APR_HAVE_STDIO_H |
| #include <stdio.h> |
| #endif |
| |
| #include "apr_md5.h" |
| #include "apr_sha1.h" |
| #include <time.h> |
| |
| #if APR_HAVE_CRYPT_H |
| #include <crypt.h> |
| #endif |
| #if APR_HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #if APR_HAVE_STRING_H |
| #include <string.h> |
| #endif |
| #if APR_HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| |
| #ifdef WIN32 |
| #include <conio.h> |
| #define unlink _unlink |
| #endif |
| |
| #if !APR_CHARSET_EBCDIC |
| #define LF 10 |
| #define CR 13 |
| #else /*APR_CHARSET_EBCDIC*/ |
| #define LF '\n' |
| #define CR '\r' |
| #endif /*APR_CHARSET_EBCDIC*/ |
| |
| #define MAX_STRING_LEN 256 |
| #define ALG_PLAIN 0 |
| #define ALG_CRYPT 1 |
| #define ALG_APMD5 2 |
| #define ALG_APSHA 3 |
| |
| #define ERR_FILEPERM 1 |
| #define ERR_SYNTAX 2 |
| #define ERR_PWMISMATCH 3 |
| #define ERR_INTERRUPTED 4 |
| #define ERR_OVERFLOW 5 |
| #define ERR_BADUSER 6 |
| #define ERR_INVALID 7 |
| |
| #define APHTP_NEWFILE 1 |
| #define APHTP_NOFILE 2 |
| #define APHTP_NONINTERACTIVE 4 |
| #define APHTP_DELUSER 8 |
| |
| apr_file_t *errfile; |
| apr_file_t *ftemp = NULL; |
| |
| static void to64(char *s, unsigned long v, int n) |
| { |
| static unsigned char itoa64[] = /* 0 ... 63 => ASCII - 64 */ |
| "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; |
| |
| while (--n >= 0) { |
| *s++ = itoa64[v&0x3f]; |
| v >>= 6; |
| } |
| } |
| |
| static void putline(apr_file_t *f, const char *l) |
| { |
| apr_file_puts(l, f); |
| } |
| |
| /* |
| * Make a password record from the given information. A zero return |
| * indicates success; failure means that the output buffer contains an |
| * error message instead. |
| */ |
| static int mkrecord(char *user, char *record, apr_size_t rlen, char *passwd, |
| int alg) |
| { |
| char *pw; |
| char cpw[120]; |
| char pwin[MAX_STRING_LEN]; |
| char pwv[MAX_STRING_LEN]; |
| char salt[9]; |
| apr_size_t bufsize; |
| |
| if (passwd != NULL) { |
| pw = passwd; |
| } |
| else { |
| bufsize = sizeof(pwin); |
| if (apr_password_get("New password: ", pwin, &bufsize) != 0) { |
| apr_snprintf(record, (rlen - 1), "password too long (>%" |
| APR_SIZE_T_FMT ")", sizeof(pwin) - 1); |
| return ERR_OVERFLOW; |
| } |
| bufsize = sizeof(pwv); |
| apr_password_get("Re-type new password: ", pwv, &bufsize); |
| if (strcmp(pwin, pwv) != 0) { |
| apr_cpystrn(record, "password verification error", (rlen - 1)); |
| return ERR_PWMISMATCH; |
| } |
| pw = pwin; |
| memset(pwv, '\0', sizeof(pwin)); |
| } |
| switch (alg) { |
| |
| case ALG_APSHA: |
| /* XXX cpw >= 28 + strlen(sha1) chars - fixed len SHA */ |
| apr_sha1_base64(pw,strlen(pw),cpw); |
| break; |
| |
| case ALG_APMD5: |
| (void) srand((int) time((time_t *) NULL)); |
| to64(&salt[0], rand(), 8); |
| salt[8] = '\0'; |
| |
| apr_md5_encode((const char *)pw, (const char *)salt, |
| cpw, sizeof(cpw)); |
| break; |
| |
| case ALG_PLAIN: |
| /* XXX this len limitation is not in sync with any HTTPd len. */ |
| apr_cpystrn(cpw,pw,sizeof(cpw)); |
| break; |
| |
| #if !(defined(WIN32) || defined(NETWARE)) |
| case ALG_CRYPT: |
| default: |
| (void) srand((int) time((time_t *) NULL)); |
| to64(&salt[0], rand(), 8); |
| salt[8] = '\0'; |
| |
| apr_cpystrn(cpw, (char *)crypt(pw, salt), sizeof(cpw) - 1); |
| break; |
| #endif |
| } |
| memset(pw, '\0', strlen(pw)); |
| |
| /* |
| * Check to see if the buffer is large enough to hold the username, |
| * hash, and delimiters. |
| */ |
| if ((strlen(user) + 1 + strlen(cpw)) > (rlen - 1)) { |
| apr_cpystrn(record, "resultant record too long", (rlen - 1)); |
| return ERR_OVERFLOW; |
| } |
| strcpy(record, user); |
| strcat(record, ":"); |
| strcat(record, cpw); |
| strcat(record, "\n"); |
| return 0; |
| } |
| |
| static void usage(void) |
| { |
| apr_file_printf(errfile, "Usage:\n"); |
| apr_file_printf(errfile, "\thtpasswd [-cmdpsD] passwordfile username\n"); |
| apr_file_printf(errfile, "\thtpasswd -b[cmdpsD] passwordfile username " |
| "password\n\n"); |
| apr_file_printf(errfile, "\thtpasswd -n[mdps] username\n"); |
| apr_file_printf(errfile, "\thtpasswd -nb[mdps] username password\n"); |
| apr_file_printf(errfile, " -c Create a new file.\n"); |
| apr_file_printf(errfile, " -n Don't update file; display results on " |
| "stdout.\n"); |
| apr_file_printf(errfile, " -m Force MD5 encryption of the password" |
| #if defined(WIN32) || defined(TPF) || defined(NETWARE) |
| " (default)" |
| #endif |
| ".\n"); |
| apr_file_printf(errfile, " -d Force CRYPT encryption of the password" |
| #if (!(defined(WIN32) || defined(TPF) || defined(NETWARE))) |
| " (default)" |
| #endif |
| ".\n"); |
| apr_file_printf(errfile, " -p Do not encrypt the password (plaintext).\n"); |
| apr_file_printf(errfile, " -s Force SHA encryption of the password.\n"); |
| apr_file_printf(errfile, " -b Use the password from the command line " |
| "rather than prompting for it.\n"); |
| apr_file_printf(errfile, " -D Delete the specified user.\n"); |
| apr_file_printf(errfile, |
| "On Windows, NetWare and TPF systems the '-m' flag is used by " |
| "default.\n"); |
| apr_file_printf(errfile, |
| "On all other systems, the '-p' flag will probably not work.\n"); |
| exit(ERR_SYNTAX); |
| } |
| |
| /* |
| * Check to see if the specified file can be opened for the given |
| * access. |
| */ |
| static int accessible(apr_pool_t *pool, char *fname, int mode) |
| { |
| apr_file_t *f = NULL; |
| |
| if (apr_file_open(&f, fname, mode, APR_OS_DEFAULT, pool) != APR_SUCCESS) { |
| return 0; |
| } |
| apr_file_close(f); |
| return 1; |
| } |
| |
| /* |
| * Return true if the named file exists, regardless of permissions. |
| */ |
| static int exists(char *fname, apr_pool_t *pool) |
| { |
| apr_finfo_t sbuf; |
| apr_status_t check; |
| |
| check = apr_stat(&sbuf, fname, APR_FINFO_TYPE, pool); |
| return ((check || sbuf.filetype != APR_REG) ? 0 : 1); |
| } |
| |
| static void terminate(void) |
| { |
| apr_terminate(); |
| #ifdef NETWARE |
| pressanykey(); |
| #endif |
| } |
| |
| static void check_args(apr_pool_t *pool, int argc, const char *const argv[], |
| int *alg, int *mask, char **user, char **pwfilename, |
| char **password) |
| { |
| const char *arg; |
| int args_left = 2; |
| int i; |
| |
| /* |
| * Preliminary check to make sure they provided at least |
| * three arguments, we'll do better argument checking as |
| * we parse the command line. |
| */ |
| if (argc < 3) { |
| usage(); |
| } |
| |
| /* |
| * Go through the argument list and pick out any options. They |
| * have to precede any other arguments. |
| */ |
| for (i = 1; i < argc; i++) { |
| arg = argv[i]; |
| if (*arg != '-') { |
| break; |
| } |
| while (*++arg != '\0') { |
| if (*arg == 'c') { |
| *mask |= APHTP_NEWFILE; |
| } |
| else if (*arg == 'n') { |
| *mask |= APHTP_NOFILE; |
| args_left--; |
| } |
| else if (*arg == 'm') { |
| *alg = ALG_APMD5; |
| } |
| else if (*arg == 's') { |
| *alg = ALG_APSHA; |
| } |
| else if (*arg == 'p') { |
| *alg = ALG_PLAIN; |
| } |
| else if (*arg == 'd') { |
| *alg = ALG_CRYPT; |
| } |
| else if (*arg == 'b') { |
| *mask |= APHTP_NONINTERACTIVE; |
| args_left++; |
| } |
| else if (*arg == 'D') { |
| *mask |= APHTP_DELUSER; |
| } |
| else { |
| usage(); |
| } |
| } |
| } |
| |
| if ((*mask & APHTP_NEWFILE) && (*mask & APHTP_NOFILE)) { |
| apr_file_printf(errfile, "%s: -c and -n options conflict\n", argv[0]); |
| exit(ERR_SYNTAX); |
| } |
| if ((*mask & APHTP_NEWFILE) && (*mask & APHTP_DELUSER)) { |
| apr_file_printf(errfile, "%s: -c and -D options conflict\n", argv[0]); |
| exit(ERR_SYNTAX); |
| } |
| if ((*mask & APHTP_NOFILE) && (*mask & APHTP_DELUSER)) { |
| apr_file_printf(errfile, "%s: -n and -D options conflict\n", argv[0]); |
| exit(ERR_SYNTAX); |
| } |
| /* |
| * Make sure we still have exactly the right number of arguments left |
| * (the filename, the username, and possibly the password if -b was |
| * specified). |
| */ |
| if ((argc - i) != args_left) { |
| usage(); |
| } |
| |
| if (*mask & APHTP_NOFILE) { |
| i--; |
| } |
| else { |
| if (strlen(argv[i]) > (APR_PATH_MAX - 1)) { |
| apr_file_printf(errfile, "%s: filename too long\n", argv[0]); |
| exit(ERR_OVERFLOW); |
| } |
| *pwfilename = apr_pstrdup(pool, argv[i]); |
| if (strlen(argv[i + 1]) > (MAX_STRING_LEN - 1)) { |
| apr_file_printf(errfile, "%s: username too long (> %d)\n", |
| argv[0], MAX_STRING_LEN - 1); |
| exit(ERR_OVERFLOW); |
| } |
| } |
| *user = apr_pstrdup(pool, argv[i + 1]); |
| if ((arg = strchr(*user, ':')) != NULL) { |
| apr_file_printf(errfile, "%s: username contains illegal " |
| "character '%c'\n", argv[0], *arg); |
| exit(ERR_BADUSER); |
| } |
| if (*mask & APHTP_NONINTERACTIVE) { |
| if (strlen(argv[i + 2]) > (MAX_STRING_LEN - 1)) { |
| apr_file_printf(errfile, "%s: password too long (> %d)\n", |
| argv[0], MAX_STRING_LEN); |
| exit(ERR_OVERFLOW); |
| } |
| *password = apr_pstrdup(pool, argv[i + 2]); |
| } |
| } |
| |
| /* |
| * Let's do it. We end up doing a lot of file opening and closing, |
| * but what do we care? This application isn't run constantly. |
| */ |
| int main(int argc, const char * const argv[]) |
| { |
| apr_file_t *fpw = NULL; |
| char record[MAX_STRING_LEN]; |
| char line[MAX_STRING_LEN]; |
| char *password = NULL; |
| char *pwfilename = NULL; |
| char *user = NULL; |
| char tn[] = "htpasswd.tmp.XXXXXX"; |
| char *dirname; |
| char *scratch, cp[MAX_STRING_LEN]; |
| int found = 0; |
| int i; |
| int alg = ALG_CRYPT; |
| int mask = 0; |
| apr_pool_t *pool; |
| int existing_file = 0; |
| #if APR_CHARSET_EBCDIC |
| apr_status_t rv; |
| apr_xlate_t *to_ascii; |
| #endif |
| |
| apr_app_initialize(&argc, &argv, NULL); |
| atexit(terminate); |
| apr_pool_create(&pool, NULL); |
| apr_file_open_stderr(&errfile, pool); |
| |
| #if APR_CHARSET_EBCDIC |
| rv = apr_xlate_open(&to_ascii, "ISO-8859-1", APR_DEFAULT_CHARSET, pool); |
| if (rv) { |
| apr_file_printf(errfile, "apr_xlate_open(to ASCII)->%d\n", rv); |
| exit(1); |
| } |
| rv = apr_SHA1InitEBCDIC(to_ascii); |
| if (rv) { |
| apr_file_printf(errfile, "apr_SHA1InitEBCDIC()->%d\n", rv); |
| exit(1); |
| } |
| rv = apr_MD5InitEBCDIC(to_ascii); |
| if (rv) { |
| apr_file_printf(errfile, "apr_MD5InitEBCDIC()->%d\n", rv); |
| exit(1); |
| } |
| #endif /*APR_CHARSET_EBCDIC*/ |
| |
| check_args(pool, argc, argv, &alg, &mask, &user, &pwfilename, &password); |
| |
| |
| #if defined(WIN32) || defined(NETWARE) |
| if (alg == ALG_CRYPT) { |
| alg = ALG_APMD5; |
| apr_file_printf(errfile, "Automatically using MD5 format.\n"); |
| } |
| #endif |
| |
| #if (!(defined(WIN32) || defined(TPF) || defined(NETWARE))) |
| if (alg == ALG_PLAIN) { |
| apr_file_printf(errfile,"Warning: storing passwords as plain text " |
| "might just not work on this platform.\n"); |
| } |
| #endif |
| |
| /* |
| * Only do the file checks if we're supposed to frob it. |
| */ |
| if (!(mask & APHTP_NOFILE)) { |
| existing_file = exists(pwfilename, pool); |
| if (existing_file) { |
| /* |
| * Check that this existing file is readable and writable. |
| */ |
| if (!accessible(pool, pwfilename, APR_READ | APR_APPEND)) { |
| apr_file_printf(errfile, "%s: cannot open file %s for " |
| "read/write access\n", argv[0], pwfilename); |
| exit(ERR_FILEPERM); |
| } |
| } |
| else { |
| /* |
| * Error out if -c was omitted for this non-existant file. |
| */ |
| if (!(mask & APHTP_NEWFILE)) { |
| apr_file_printf(errfile, |
| "%s: cannot modify file %s; use '-c' to create it\n", |
| argv[0], pwfilename); |
| exit(ERR_FILEPERM); |
| } |
| /* |
| * As it doesn't exist yet, verify that we can create it. |
| */ |
| if (!accessible(pool, pwfilename, APR_CREATE | APR_WRITE)) { |
| apr_file_printf(errfile, "%s: cannot create file %s\n", |
| argv[0], pwfilename); |
| exit(ERR_FILEPERM); |
| } |
| } |
| } |
| |
| /* |
| * All the file access checks (if any) have been made. Time to go to work; |
| * try to create the record for the username in question. If that |
| * fails, there's no need to waste any time on file manipulations. |
| * Any error message text is returned in the record buffer, since |
| * the mkrecord() routine doesn't have access to argv[]. |
| */ |
| if (!(mask & APHTP_DELUSER)) { |
| i = mkrecord(user, record, sizeof(record) - 1, |
| password, alg); |
| if (i != 0) { |
| apr_file_printf(errfile, "%s: %s\n", argv[0], record); |
| exit(i); |
| } |
| if (mask & APHTP_NOFILE) { |
| printf("%s\n", record); |
| exit(0); |
| } |
| } |
| |
| /* |
| * We can access the files the right way, and we have a record |
| * to add or update. Let's do it.. |
| */ |
| if (apr_temp_dir_get((const char**)&dirname, pool) != APR_SUCCESS) { |
| apr_file_printf(errfile, "%s: could not determine temp dir\n", |
| argv[0]); |
| exit(ERR_FILEPERM); |
| } |
| dirname = apr_psprintf(pool, "%s/%s", dirname, tn); |
| |
| if (apr_file_mktemp(&ftemp, dirname, 0, pool) != APR_SUCCESS) { |
| apr_file_printf(errfile, "%s: unable to create temporary file %s\n", |
| argv[0], dirname); |
| exit(ERR_FILEPERM); |
| } |
| |
| /* |
| * If we're not creating a new file, copy records from the existing |
| * one to the temporary file until we find the specified user. |
| */ |
| if (existing_file && !(mask & APHTP_NEWFILE)) { |
| if (apr_file_open(&fpw, pwfilename, APR_READ | APR_BUFFERED, |
| APR_OS_DEFAULT, pool) != APR_SUCCESS) { |
| apr_file_printf(errfile, "%s: unable to read file %s\n", |
| argv[0], pwfilename); |
| exit(ERR_FILEPERM); |
| } |
| while (apr_file_gets(line, sizeof(line), fpw) == APR_SUCCESS) { |
| char *colon; |
| |
| strcpy(cp, line); |
| scratch = cp; |
| while (apr_isspace(*scratch)) { |
| ++scratch; |
| } |
| |
| if (!*scratch || (*scratch == '#')) { |
| putline(ftemp, line); |
| continue; |
| } |
| /* |
| * See if this is our user. |
| */ |
| colon = strchr(scratch, ':'); |
| if (colon != NULL) { |
| *colon = '\0'; |
| } |
| else { |
| /* |
| * If we've not got a colon on the line, this could well |
| * not be a valid htpasswd file. |
| * We should bail at this point. |
| */ |
| apr_file_printf(errfile, "\n%s: The file %s does not appear " |
| "to be a valid htpasswd file.\n", |
| argv[0], pwfilename); |
| apr_file_close(fpw); |
| exit(ERR_INVALID); |
| } |
| if (strcmp(user, scratch) != 0) { |
| putline(ftemp, line); |
| continue; |
| } |
| else { |
| if (!(mask & APHTP_DELUSER)) { |
| /* We found the user we were looking for. |
| * Add him to the file. |
| */ |
| apr_file_printf(errfile, "Updating "); |
| putline(ftemp, record); |
| found++; |
| } |
| else { |
| /* We found the user we were looking for. |
| * Delete them from the file. |
| */ |
| apr_file_printf(errfile, "Deleting "); |
| found++; |
| } |
| } |
| } |
| apr_file_close(fpw); |
| } |
| if (!found && !(mask & APHTP_DELUSER)) { |
| apr_file_printf(errfile, "Adding "); |
| putline(ftemp, record); |
| } |
| else if (!found && (mask & APHTP_DELUSER)) { |
| apr_file_printf(errfile, "User %s not found\n", user); |
| exit(0); |
| } |
| apr_file_printf(errfile, "password for user %s\n", user); |
| |
| /* The temporary file has all the data, just copy it to the new location. |
| */ |
| if (apr_file_copy(dirname, pwfilename, APR_FILE_SOURCE_PERMS, pool) != |
| APR_SUCCESS) { |
| apr_file_printf(errfile, "%s: unable to update file %s\n", |
| argv[0], pwfilename); |
| exit(ERR_FILEPERM); |
| } |
| apr_file_close(ftemp); |
| return 0; |
| } |