From: Stefan Fritsch Date: Sun, 7 Oct 2012 09:02:19 +0000 (+0000) Subject: Start refactoring of htpasswd and htdbm X-Git-Tag: 2.5.0-alpha~6226 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=7d8882b4b4ac7f11404d9bc32c5ce8bf85febedd;p=apache Start refactoring of htpasswd and htdbm - Move many common code parts into separate source file. This adds some of htpasswd's recent improvements to htdbm. - Rework salt generation to use the full 48bit of entropy for MD5 Previously, it would only generate 2^32 different salts on a given platform. - Use apr_getopt(). git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1395253 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 9b8875c1e3..f466c36f53 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 + *) htpasswd, htdbm: Put full 48bit of entropy into salt, improve + error handling. Add some of htpasswd's improvements to htdbm, + e.g. warn if password is truncated by crypt(). [Stefan Fritsch] + *) ab: add TLS1.1/TLS1.2 options to -f switch, and adapt output to more accurately report the negotiated protocol. PR 53916. [Nicolás Pernas Maradei , Kaspar Brand] diff --git a/support/Makefile.in b/support/Makefile.in index cef8f00368..635c247df4 100644 --- a/support/Makefile.in +++ b/support/Makefile.in @@ -36,7 +36,9 @@ install: fi ; \ fi -htpasswd_OBJECTS = htpasswd.lo +htpasswd.lo: passwd_common.h +passwd_common.lo: passwd_common.h +htpasswd_OBJECTS = htpasswd.lo passwd_common.lo htpasswd: $(htpasswd_OBJECTS) $(LINK) $(htpasswd_LTFLAGS) $(htpasswd_OBJECTS) $(PROGRAM_LDADD) $(CRYPT_LIBS) @@ -52,7 +54,8 @@ logresolve_OBJECTS = logresolve.lo logresolve: $(logresolve_OBJECTS) $(LINK) $(logresolve_LTFLAGS) $(logresolve_OBJECTS) $(PROGRAM_LDADD) -htdbm_OBJECTS = htdbm.lo +htdbm.lo: passwd_common.h +htdbm_OBJECTS = htdbm.lo passwd_common.lo htdbm: $(htdbm_OBJECTS) $(LINK) $(htdbm_LTFLAGS) $(htdbm_OBJECTS) $(PROGRAM_LDADD) $(CRYPT_LIBS) diff --git a/support/htdbm.c b/support/htdbm.c index 4898ed8c18..95b29ee0cf 100644 --- a/support/htdbm.c +++ b/support/htdbm.c @@ -22,9 +22,7 @@ * 12 Oct 2001 */ -#include "apr.h" -#include "apr_lib.h" -#include "apr_strings.h" +#include "passwd_common.h" #include "apr_file_io.h" #include "apr_file_info.h" #include "apr_pools.h" @@ -32,6 +30,7 @@ #include "apr_md5.h" #include "apr_sha1.h" #include "apr_dbm.h" +#include "apr_getopt.h" #if APR_HAVE_STDLIB_H #include @@ -56,49 +55,20 @@ #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_APMD5 1 -#define ALG_APSHA 2 - -#if (!(defined(WIN32) || defined(NETWARE))) -#define ALG_CRYPT 3 -#endif - - -#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_EMPTY 7 - - typedef struct htdbm_t htdbm_t; struct htdbm_t { apr_dbm_t *dbm; - apr_pool_t *pool; + struct passwd_ctx ctx; #if APR_CHARSET_EBCDIC apr_xlate_t *to_ascii; #endif char *filename; char *username; - char *userpass; char *comment; char *type; int create; int rdonly; - int alg; }; @@ -107,7 +77,6 @@ struct htdbm_t { #define HTDBM_VERIFY 2 #define HTDBM_LIST 3 #define HTDBM_NOFILE 4 -#define HTDBM_STDIN 5 static void terminate(void) { @@ -141,13 +110,14 @@ static apr_status_t htdbm_init(apr_pool_t **pool, htdbm_t **hdbm) #endif apr_pool_create( pool, NULL); + apr_file_open_stderr(&errfile, *pool); apr_signal(SIGINT, (void (*)(int)) htdbm_interrupted); (*hdbm) = (htdbm_t *)apr_pcalloc(*pool, sizeof(htdbm_t)); - (*hdbm)->pool = *pool; + (*hdbm)->ctx.pool = *pool; #if APR_CHARSET_EBCDIC - rv = apr_xlate_open(&((*hdbm)->to_ascii), "ISO-8859-1", APR_DEFAULT_CHARSET, (*hdbm)->pool); + rv = apr_xlate_open(&((*hdbm)->to_ascii), "ISO-8859-1", APR_DEFAULT_CHARSET, (*hdbm)->ctx.pool); if (rv) { fprintf(stderr, "apr_xlate_open(to ASCII)->%d\n", rv); return APR_EGENERAL; @@ -165,7 +135,7 @@ static apr_status_t htdbm_init(apr_pool_t **pool, htdbm_t **hdbm) #endif /*APR_CHARSET_EBCDIC*/ /* Set MD5 as default */ - (*hdbm)->alg = ALG_APMD5; + (*hdbm)->ctx.alg = ALG_APMD5; (*hdbm)->type = "default"; return APR_SUCCESS; } @@ -174,11 +144,11 @@ static apr_status_t htdbm_open(htdbm_t *htdbm) { if (htdbm->create) return apr_dbm_open_ex(&htdbm->dbm, htdbm->type, htdbm->filename, APR_DBM_RWCREATE, - APR_OS_DEFAULT, htdbm->pool); + APR_OS_DEFAULT, htdbm->ctx.pool); else return apr_dbm_open_ex(&htdbm->dbm, htdbm->type, htdbm->filename, htdbm->rdonly ? APR_DBM_READONLY : APR_DBM_READWRITE, - APR_OS_DEFAULT, htdbm->pool); + APR_OS_DEFAULT, htdbm->ctx.pool); } static apr_status_t htdbm_save(htdbm_t *htdbm, int *changed) @@ -193,11 +163,11 @@ static apr_status_t htdbm_save(htdbm_t *htdbm, int *changed) if (apr_dbm_exists(htdbm->dbm, key)) *changed = 1; - val.dsize = strlen(htdbm->userpass); + val.dsize = strlen(htdbm->ctx.passwd); if (!htdbm->comment) - val.dptr = htdbm->userpass; + val.dptr = htdbm->ctx.passwd; else { - val.dptr = apr_pstrcat(htdbm->pool, htdbm->userpass, ":", + val.dptr = apr_pstrcat(htdbm->ctx.pool, htdbm->ctx.passwd, ":", htdbm->comment, NULL); val.dsize += (strlen(htdbm->comment) + 1); } @@ -228,13 +198,13 @@ static apr_status_t htdbm_verify(htdbm_t *htdbm) return APR_ENOENT; if (apr_dbm_fetch(htdbm->dbm, key, &val) != APR_SUCCESS) return APR_ENOENT; - rec = apr_pstrndup(htdbm->pool, val.dptr, val.dsize); + rec = apr_pstrndup(htdbm->ctx.pool, val.dptr, val.dsize); cmnt = strchr(rec, ':'); if (cmnt) - pwd = apr_pstrndup(htdbm->pool, rec, cmnt - rec); + pwd = apr_pstrndup(htdbm->ctx.pool, rec, cmnt - rec); else - pwd = apr_pstrdup(htdbm->pool, rec); - return apr_password_validate(htdbm->userpass, pwd); + pwd = apr_pstrdup(htdbm->ctx.pool, rec); + return apr_password_validate(htdbm->ctx.passwd, pwd); } static apr_status_t htdbm_list(htdbm_t *htdbm) @@ -273,67 +243,20 @@ static apr_status_t htdbm_list(htdbm_t *htdbm) return APR_SUCCESS; } -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 apr_status_t htdbm_make(htdbm_t *htdbm) +static int htdbm_make(htdbm_t *htdbm) { char cpw[MAX_STRING_LEN]; - char salt[9]; -#if (!(defined(WIN32) || defined(NETWARE))) - char *cbuf; -#endif - - switch (htdbm->alg) { - case ALG_APSHA: - /* XXX cpw >= 28 + strlen(sha1) chars - fixed len SHA */ - apr_sha1_base64(htdbm->userpass,strlen(htdbm->userpass),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 *)htdbm->userpass, (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,htdbm->userpass,sizeof(cpw)); -#if (!(defined(WIN32) || defined(NETWARE))) - fprintf(stderr, "Warning: Plain text passwords aren't supported by the " - "server on this platform!\n"); -#endif - break; -#if (!(defined(WIN32) || defined(NETWARE))) - case ALG_CRYPT: - (void) srand((int) time((time_t *) NULL)); - to64(&salt[0], rand(), 8); - salt[8] = '\0'; - cbuf = crypt(htdbm->userpass, salt); - if (cbuf == NULL) { - char errbuf[128]; - - fprintf(stderr, "crypt() failed: %s\n", - apr_strerror(errno, errbuf, sizeof errbuf)); - exit(ERR_PWMISMATCH); - } - apr_cpystrn(cpw, cbuf, sizeof(cpw) - 1); - fprintf(stderr, "CRYPT is now deprecated, use MD5 instead!\n"); -#endif - default: - break; + int ret; + + htdbm->ctx.out = cpw; + htdbm->ctx.out_len = sizeof(cpw); + ret = mkhash(&htdbm->ctx); + if (ret != 0) { + fprintf(stderr, "Error: %s\n", htdbm->ctx.errstr); + return ret; } - htdbm->userpass = apr_pstrdup(htdbm->pool, cpw); - return APR_SUCCESS; + htdbm->ctx.passwd = apr_pstrdup(htdbm->ctx.pool, cpw); + return 0; } static apr_status_t htdbm_valid_username(htdbm_t *htdbm) @@ -351,60 +274,49 @@ static apr_status_t htdbm_valid_username(htdbm_t *htdbm) static void htdbm_usage(void) { - -#if (!(defined(WIN32) || defined(NETWARE))) -#define CRYPT_OPTION "d" -#else -#define CRYPT_OPTION "" -#endif - fprintf(stderr, "htdbm -- program for manipulating DBM password databases.\n\n"); - fprintf(stderr, "Usage: htdbm [-cm"CRYPT_OPTION"pstvx] [-TDBTYPE] database username\n"); - fprintf(stderr, " -b[cm"CRYPT_OPTION"ptsv] [-TDBTYPE] database username password\n"); - fprintf(stderr, " -n[m"CRYPT_OPTION"pst] username\n"); - fprintf(stderr, " -nb[m"CRYPT_OPTION"pst] username password\n"); - fprintf(stderr, " -v[m"CRYPT_OPTION"ps] [-TDBTYPE] database username\n"); - fprintf(stderr, " -vb[m"CRYPT_OPTION"ps] [-TDBTYPE] database username password\n"); - fprintf(stderr, " -x[m"CRYPT_OPTION"ps] [-TDBTYPE] database username\n"); - fprintf(stderr, " -l [-TDBTYPE] database\n"); - fprintf(stderr, "Options:\n"); - fprintf(stderr, " -b Use the password from the command line rather " - "than prompting for it.\n"); - fprintf(stderr, " -c Create a new database.\n"); - fprintf(stderr, " -n Don't update database; display results on stdout.\n"); - fprintf(stderr, " -m Force MD5 encryption of the password (default).\n"); -#if (!(defined(WIN32) || defined(NETWARE))) - fprintf(stderr, " -d Force CRYPT encryption of the password (now deprecated).\n"); -#endif - fprintf(stderr, " -p Do not encrypt the password (plaintext).\n"); - fprintf(stderr, " -s Force SHA encryption of the password.\n"); - fprintf(stderr, " -T DBM Type (SDBM|GDBM|DB|default).\n"); - fprintf(stderr, " -l Display usernames from database on stdout.\n"); - fprintf(stderr, " -t The last param is username comment.\n"); - fprintf(stderr, " -v Verify the username/password.\n"); - fprintf(stderr, " -x Remove the username record from database.\n"); + fprintf(stderr, + "htdbm -- program for manipulating DBM password databases.\n\n" + "Usage: htdbm [-cmdpstvx] [-TDBTYPE] database username\n" + " -b[cmdptsv] [-TDBTYPE] database username password\n" + " -n[mdpst] username\n" + " -nb[mdpst] username password\n" + " -v[mdps] [-TDBTYPE] database username\n" + " -vb[mdps] [-TDBTYPE] database username password\n" + " -x [-TDBTYPE] database username\n" + " -l [-TDBTYPE] database\n" + "Options:\n" + " -b Use the password from the command line rather than prompting for it.\n" + " -c Create a new database.\n" + " -n Don't update database; display results on stdout.\n" + " -m Force MD5 encryption of the password (default).\n" + " -d Force CRYPT encryption of the password (8 chars max, insecure).\n" + " -p Do not encrypt the password (plaintext).\n" + " -s Force SHA encryption of the password (insecure).\n" + " -T DBM Type (SDBM|GDBM|DB|default).\n" + " -l Display usernames from database on stdout.\n" + " -t The last param is username comment.\n" + " -v Verify the username/password.\n" + " -x Remove the username record from database.\n" + "The SHA algorithm does not use a salt and is less secure than the " + "MD5 algorithm.\n"); exit(ERR_SYNTAX); - } - int main(int argc, const char * const argv[]) { apr_pool_t *pool; apr_status_t rv; - apr_size_t l; - char pwi[MAX_STRING_LEN]; - char pwc[MAX_STRING_LEN]; char errbuf[MAX_STRING_LEN]; - const char *arg; int need_file = 1; int need_user = 1; int need_pwd = 1; int need_cmnt = 0; - int pwd_supplied = 0; int changed = 0; int cmd = HTDBM_MAKE; - int i; - int args_left = 2; + int i, ret, args_left = 2; + apr_getopt_t *state; + char opt; + const char *opt_arg; apr_app_initialize(&argc, &argv, NULL); atexit(terminate); @@ -414,131 +326,90 @@ int main(int argc, const char * const argv[]) apr_strerror(rv, errbuf, sizeof(errbuf)); exit(1); } - /* - * 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) - htdbm_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') { - switch (*arg) { - case 'b': - pwd_supplied = 1; - need_pwd = 0; - args_left++; - break; - case 'c': - h->create = 1; - break; - case 'n': - need_file = 0; - cmd = HTDBM_NOFILE; - args_left--; - break; - case 'l': - need_pwd = 0; - need_user = 0; - cmd = HTDBM_LIST; - h->rdonly = 1; + rv = apr_getopt_init(&state, pool, argc, argv); + if (rv != APR_SUCCESS) + exit(ERR_SYNTAX); + + while ((rv = apr_getopt(state, "cnmspdBbDiC:T:", &opt, &opt_arg)) == APR_SUCCESS) { + switch (opt) { + case 'c': + h->create = 1; + break; + case 'n': + need_file = 0; + cmd = HTDBM_NOFILE; args_left--; - break; - case 't': - need_cmnt = 1; - args_left++; - break; - case 'T': - h->type = apr_pstrdup(h->pool, ++arg); - while (*arg != '\0') - ++arg; - --arg; /* so incrementing this in the loop with find a null */ - break; - case 'v': - h->rdonly = 1; - cmd = HTDBM_VERIFY; - break; - case 'x': - need_pwd = 0; - cmd = HTDBM_DELETE; - break; - case 'm': - h->alg = ALG_APMD5; - break; - case 'p': - h->alg = ALG_PLAIN; - break; - case 's': - h->alg = ALG_APSHA; - break; -#if (!(defined(WIN32) || defined(NETWARE))) - case 'd': - h->alg = ALG_CRYPT; - break; -#endif - default: - htdbm_usage(); - break; + break; + case 'l': + need_pwd = 0; + need_user = 0; + cmd = HTDBM_LIST; + h->rdonly = 1; + args_left--; + break; + case 't': + need_cmnt = 1; + args_left++; + break; + case 'T': + h->type = apr_pstrdup(h->ctx.pool, opt_arg); + break; + case 'v': + h->rdonly = 1; + cmd = HTDBM_VERIFY; + break; + case 'x': + need_pwd = 0; + cmd = HTDBM_DELETE; + break; + default: + ret = parse_common_options(&h->ctx, opt, opt_arg); + if (ret) { + fprintf(stderr, "Error: %s\n", h->ctx.errstr); + exit(ret); } } } + if (h->ctx.passwd_src == PW_ARG) { + need_pwd = 0; + args_left++; + } /* * 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) + i = state->ind; + if (rv != APR_EOF || argc - i != args_left) htdbm_usage(); - if (!need_file) - i--; - else { - h->filename = apr_pstrdup(h->pool, argv[i]); - if ((rv = htdbm_open(h)) != APR_SUCCESS) { - fprintf(stderr, "Error opening database %s\n", argv[i]); + if (need_file) { + h->filename = apr_pstrdup(h->ctx.pool, argv[i++]); + if ((rv = htdbm_open(h)) != APR_SUCCESS) { + fprintf(stderr, "Error opening database %s\n", h->filename); apr_strerror(rv, errbuf, sizeof(errbuf)); fprintf(stderr,"%s\n",errbuf); exit(ERR_FILEPERM); } } if (need_user) { - h->username = apr_pstrdup(pool, argv[i+1]); + h->username = apr_pstrdup(pool, argv[i++]); if (htdbm_valid_username(h) != APR_SUCCESS) exit(ERR_BADUSER); } - if (pwd_supplied) - h->userpass = apr_pstrdup(pool, argv[i+2]); + if (h->ctx.passwd_src == PW_ARG) + h->ctx.passwd = apr_pstrdup(pool, argv[i++]); if (need_pwd) { - l = sizeof(pwc); - if (apr_password_get("Enter password : ", pwi, &l) != APR_SUCCESS) { - fprintf(stderr, "Password too long\n"); - exit(ERR_OVERFLOW); - } - l = sizeof(pwc); - if (apr_password_get("Re-type password : ", pwc, &l) != APR_SUCCESS) { - fprintf(stderr, "Password too long\n"); - exit(ERR_OVERFLOW); - } - if (strcmp(pwi, pwc) != 0) { - fprintf(stderr, "Password verification error\n"); - exit(ERR_PWMISMATCH); + ret = get_password(&h->ctx); + if (ret) { + fprintf(stderr, "Error: %s\n", h->ctx.errstr); + exit(ret); } - - h->userpass = apr_pstrdup(pool, pwi); } - if (need_cmnt && pwd_supplied) - h->comment = apr_pstrdup(pool, argv[i+3]); - else if (need_cmnt) - h->comment = apr_pstrdup(pool, argv[i+2]); + if (need_cmnt) + h->comment = apr_pstrdup(pool, argv[i++]); switch (cmd) { case HTDBM_VERIFY: @@ -567,9 +438,10 @@ int main(int argc, const char * const argv[]) htdbm_list(h); break; default: - htdbm_make(h); + ret = htdbm_make(h); + if (ret) + exit(ret); break; - } if (need_file && !h->rdonly) { if ((rv = htdbm_save(h, &changed)) != APR_SUCCESS) { @@ -581,10 +453,10 @@ int main(int argc, const char * const argv[]) } if (cmd == HTDBM_NOFILE) { if (!need_cmnt) { - fprintf(stderr, "%s:%s\n", h->username, h->userpass); + fprintf(stderr, "%s:%s\n", h->username, h->ctx.passwd); } else { - fprintf(stderr, "%s:%s:%s\n", h->username, h->userpass, + fprintf(stderr, "%s:%s:%s\n", h->username, h->ctx.passwd, h->comment); } } diff --git a/support/htpasswd.c b/support/htpasswd.c index 31e766211d..cdf7f311bd 100644 --- a/support/htpasswd.c +++ b/support/htpasswd.c @@ -38,13 +38,9 @@ * 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 "passwd_common.h" #include "apr_signal.h" +#include "apr_getopt.h" #if APR_HAVE_STDIO_H #include @@ -52,11 +48,7 @@ #include "apr_md5.h" #include "apr_sha1.h" -#include -#if APR_HAVE_CRYPT_H -#include -#endif #if APR_HAVE_STDLIB_H #include #endif @@ -72,230 +64,54 @@ #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 +#define APHTP_DELUSER 4 -apr_file_t *errfile; apr_file_t *ftemp = NULL; -#define NL APR_EOL_STR - -#if defined(WIN32) || defined(NETWARE) -#define CRYPT_ALGO_SUPPORTED 0 -#else -#define CRYPT_ALGO_SUPPORTED 1 -#endif - -#if CRYPT_ALGO_SUPPORTED -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; - } -} -#endif - -static void generate_salt(char *s, size_t size) +static int mkrecord(struct passwd_ctx *ctx, char *user) { - static unsigned char tbl[] = - "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - size_t i; - for (i = 0; i < size; ++i) { - int idx = (int) (64.0 * rand() / (RAND_MAX + 1.0)); - s[i] = tbl[idx]; - } -} - -static apr_status_t seed_rand(void) -{ - int seed = 0; - apr_status_t rv; - rv = apr_generate_random_bytes((unsigned char*) &seed, sizeof(seed)); - if (rv) { - apr_file_printf(errfile, "Unable to generate random bytes: %pm" NL, &rv); - return rv; - } - srand(seed); - return rv; -} - -static void putline(apr_file_t *f, const char *l) -{ - apr_status_t rc; - rc = apr_file_puts(l, f); - if (rc != APR_SUCCESS) { - char errstr[MAX_STRING_LEN]; - apr_strerror(rc, errstr, MAX_STRING_LEN); - apr_file_printf(errfile, "Error writing temp file: %s" NL, errstr); - apr_file_close(f); - exit(ERR_FILEPERM); - } -} - -/* - * 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 CRYPT_ALGO_SUPPORTED - char *cbuf; -#endif - - 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: - if (seed_rand()) { - break; - } - generate_salt(&salt[0], 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 CRYPT_ALGO_SUPPORTED - case ALG_CRYPT: - default: - if (seed_rand()) { - break; - } - to64(&salt[0], rand(), 8); - salt[8] = '\0'; - - cbuf = crypt(pw, salt); - if (cbuf == NULL) { - char errbuf[128]; - - apr_snprintf(record, rlen-1, "crypt() failed: %s", - apr_strerror(errno, errbuf, sizeof errbuf)); - return ERR_PWMISMATCH; - } - - apr_cpystrn(cpw, cbuf, sizeof(cpw) - 1); - if (strlen(pw) > 8) { - char *truncpw = strdup(pw); - truncpw[8] = '\0'; - if (!strcmp(cpw, crypt(truncpw, salt))) { - apr_file_printf(errfile, "Warning: Password truncated to 8 characters " - "by CRYPT algorithm." NL); - } - free(truncpw); - } - break; -#endif /* CRYPT_ALGO_SUPPORTED */ - } - 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)); + char hash_str[MAX_STRING_LEN]; + int ret; + ctx->out = hash_str; + ctx->out_len = sizeof(hash_str); + + ret = mkhash(ctx); + if (ret) + return ret; + + ctx->out = apr_pstrcat(ctx->pool, user, ":", hash_str, NL, NULL); + if (strlen(ctx->out) >= MAX_STRING_LEN) { + ctx->errstr = "resultant record too long"; 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:" NL); - apr_file_printf(errfile, "\thtpasswd [-cmdpsD] passwordfile username" NL); - apr_file_printf(errfile, "\thtpasswd -b[cmdpsD] passwordfile username " - "password" NL NL); - apr_file_printf(errfile, "\thtpasswd -n[mdps] username" NL); - apr_file_printf(errfile, "\thtpasswd -nb[mdps] username password" NL); - apr_file_printf(errfile, " -c Create a new file." NL); - apr_file_printf(errfile, " -n Don't update file; display results on " - "stdout." NL); - apr_file_printf(errfile, " -m Force MD5 encryption of the password" - " (default)" - "." NL); - apr_file_printf(errfile, " -d Force CRYPT encryption of the password" - " (8 chars max, insecure)." NL); - apr_file_printf(errfile, " -p Do not encrypt the password (plaintext)." NL); - apr_file_printf(errfile, " -s Force SHA encryption of the password" - " (insecure)." NL); - apr_file_printf(errfile, " -b Use the password from the command line " - "rather than prompting for it." NL); - apr_file_printf(errfile, " -D Delete the specified user." NL); - apr_file_printf(errfile, - "On other systems than Windows and NetWare the '-p' flag will " - "probably not work." NL); - apr_file_printf(errfile, - "The SHA algorithm does not use a salt and is less secure than " - "the MD5 algorithm." NL); + apr_file_printf(errfile, "Usage:" NL + "\thtpasswd [-cmdpsD] passwordfile username" NL + "\thtpasswd -b[cmdpsD] passwordfile username password" NL + NL + "\thtpasswd -n[mdps] username" NL + "\thtpasswd -nb[mdps] username password" NL + " -c Create a new file." NL + " -n Don't update file; display results on stdout." NL + " -m Force MD5 encryption of the password (default)." NL + " -d Force CRYPT encryption of the password (8 chars max, " + "insecure)." NL + " -p Do not encrypt the password (plaintext, insecure)." NL + " -s Force SHA encryption of the password (insecure)." NL + " -b Use the password from the command line rather than prompting " + "for it." NL + " -D Delete the specified user." NL + "On other systems than Windows and NetWare the '-p' flag will " + "probably not work." NL + "The SHA algorithm does not use a salt and is less secure than the " + "MD5 algorithm." NL + ); exit(ERR_SYNTAX); } @@ -334,64 +150,47 @@ static void terminate(void) #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) +static void check_args(int argc, const char *const argv[], + struct passwd_ctx *ctx, int *mask, char **user, + char **pwfilename) { const char *arg; int args_left = 2; - int i; + int i, ret; + apr_getopt_t *state; + apr_status_t rv; + char opt; + const char *opt_arg; + apr_pool_t *pool = ctx->pool; - /* - * 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(); - } + rv = apr_getopt_init(&state, pool, argc, argv); + if (rv != APR_SUCCESS) + exit(ERR_SYNTAX); - /* - * 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 != '-') { + while ((rv = apr_getopt(state, "cnmspdBbDiC:", &opt, &opt_arg)) == APR_SUCCESS) { + switch (opt) { + case 'c': + *mask |= APHTP_NEWFILE; 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(); + case 'n': + args_left--; + *mask |= APHTP_NOFILE; + break; + case 'D': + *mask |= APHTP_DELUSER; + break; + default: + ret = parse_common_options(ctx, opt, opt_arg); + if (ret) { + apr_file_printf(errfile, "%s: %s" NL, argv[0], ctx->errstr); + exit(ret); } } } + if (ctx->passwd_src == PW_ARG) + args_left++; + if (rv != APR_EOF) + usage(); if ((*mask & APHTP_NEWFILE) && (*mask & APHTP_NOFILE)) { apr_file_printf(errfile, "%s: -c and -n options conflict" NL, argv[0]); @@ -410,38 +209,36 @@ static void check_args(apr_pool_t *pool, int argc, const char *const argv[], * (the filename, the username, and possibly the password if -b was * specified). */ + i = state->ind; if ((argc - i) != args_left) { usage(); } - if (*mask & APHTP_NOFILE) { - i--; - } - else { + if (!(*mask & APHTP_NOFILE)) { if (strlen(argv[i]) > (APR_PATH_MAX - 1)) { apr_file_printf(errfile, "%s: filename too long" NL, 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)" NL, - argv[0], MAX_STRING_LEN - 1); - exit(ERR_OVERFLOW); - } + *pwfilename = apr_pstrdup(pool, argv[i++]); + } + if (strlen(argv[i]) > (MAX_STRING_LEN - 1)) { + apr_file_printf(errfile, "%s: username too long (> %d)" NL, + argv[0], MAX_STRING_LEN - 1); + exit(ERR_OVERFLOW); } - *user = apr_pstrdup(pool, argv[i + 1]); + *user = apr_pstrdup(pool, argv[i++]); if ((arg = strchr(*user, ':')) != NULL) { apr_file_printf(errfile, "%s: username contains illegal " "character '%c'" NL, argv[0], *arg); exit(ERR_BADUSER); } - if (*mask & APHTP_NONINTERACTIVE) { - if (strlen(argv[i + 2]) > (MAX_STRING_LEN - 1)) { + if (ctx->passwd_src == PW_ARG) { + if (strlen(argv[i]) > (MAX_STRING_LEN - 1)) { apr_file_printf(errfile, "%s: password too long (> %d)" NL, argv[0], MAX_STRING_LEN); exit(ERR_OVERFLOW); } - *password = apr_pstrdup(pool, argv[i + 2]); + ctx->passwd = apr_pstrdup(pool, argv[i]); } } @@ -452,9 +249,8 @@ static void check_args(apr_pool_t *pool, int argc, const char *const argv[], int main(int argc, const char * const argv[]) { apr_file_t *fpw = NULL; - char record[MAX_STRING_LEN]; + const char *errstr = NULL; char line[MAX_STRING_LEN]; - char *password = NULL; char *pwfilename = NULL; char *user = NULL; char tn[] = "htpasswd.tmp.XXXXXX"; @@ -462,10 +258,10 @@ int main(int argc, const char * const argv[]) char *scratch, cp[MAX_STRING_LEN]; int found = 0; int i; - int alg = ALG_APMD5; int mask = 0; apr_pool_t *pool; int existing_file = 0; + struct passwd_ctx ctx = { 0 }; #if APR_CHARSET_EBCDIC apr_status_t rv; apr_xlate_t *to_ascii; @@ -475,6 +271,8 @@ int main(int argc, const char * const argv[]) atexit(terminate); apr_pool_create(&pool, NULL); apr_file_open_stderr(&errfile, pool); + ctx.pool = pool; + ctx.alg = ALG_APMD5; #if APR_CHARSET_EBCDIC rv = apr_xlate_open(&to_ascii, "ISO-8859-1", APR_DEFAULT_CHARSET, pool); @@ -494,22 +292,7 @@ int main(int argc, const char * const argv[]) } #endif /*APR_CHARSET_EBCDIC*/ - check_args(pool, argc, argv, &alg, &mask, &user, &pwfilename, &password); - - -#if !CRYPT_ALGO_SUPPORTED - if (alg == ALG_CRYPT) { - alg = ALG_APMD5; - apr_file_printf(errfile, "Automatically using MD5 format." NL); - } -#endif - -#if CRYPT_ALGO_SUPPORTED - if (alg == ALG_PLAIN) { - apr_file_printf(errfile,"Warning: storing passwords as plain text " - "might just not work on this platform." NL); - } -#endif + check_args(argc, argv, &ctx, &mask, &user, &pwfilename); /* * Only do the file checks if we're supposed to frob it. @@ -555,14 +338,13 @@ int main(int argc, const char * const argv[]) * the mkrecord() routine doesn't have access to argv[]. */ if (!(mask & APHTP_DELUSER)) { - i = mkrecord(user, record, sizeof(record) - 1, - password, alg); + i = mkrecord(&ctx, user); if (i != 0) { - apr_file_printf(errfile, "%s: %s" NL, argv[0], record); + apr_file_printf(errfile, "%s: %s" NL, argv[0], errstr); exit(i); } if (mask & APHTP_NOFILE) { - printf("%s" NL, record); + printf("%s" NL, ctx.out); exit(0); } } @@ -637,7 +419,7 @@ int main(int argc, const char * const argv[]) * Add him to the file. */ apr_file_printf(errfile, "Updating "); - putline(ftemp, record); + putline(ftemp, ctx.out); found++; } else { @@ -653,7 +435,7 @@ int main(int argc, const char * const argv[]) } if (!found && !(mask & APHTP_DELUSER)) { apr_file_printf(errfile, "Adding "); - putline(ftemp, record); + putline(ftemp, ctx.out); } else if (!found && (mask & APHTP_DELUSER)) { apr_file_printf(errfile, "User %s not found" NL, user); diff --git a/support/passwd_common.c b/support/passwd_common.c new file mode 100644 index 0000000000..5458fad396 --- /dev/null +++ b/support/passwd_common.c @@ -0,0 +1,240 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +#include "passwd_common.h" +#include "apr_strings.h" +#include "apr_errno.h" + +#if APR_HAVE_STDIO_H +#include +#endif + +#include "apr_md5.h" +#include "apr_sha1.h" +#include + +#if APR_HAVE_CRYPT_H +#include +#endif +#if APR_HAVE_STDLIB_H +#include +#endif +#if APR_HAVE_STRING_H +#include +#endif +#if APR_HAVE_UNISTD_H +#include +#endif + +#ifdef WIN32 +#include +#define unlink _unlink +#endif + +apr_file_t *errfile; + +static int generate_salt(char *s, size_t size, const char **errstr, + apr_pool_t *pool) +{ + unsigned char rnd[32]; + static const char itoa64[] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + apr_size_t n; + unsigned int val = 0, bits = 0; + apr_status_t rv; + + n = (size * 6 + 7)/8; + if (n > sizeof(rnd)) { + apr_file_printf(errfile, "%s: BUG: Buffer too small", __func__); + abort(); + } + rv = apr_generate_random_bytes(rnd, n); + if (rv) { + *errstr = apr_psprintf(pool, "Unable to generate random bytes: %pm", + &rv); + return ERR_RANDOM; + } + n = 0; + while (size > 0) { + if (bits < 6) { + val |= (rnd[n++] << bits); + bits += 8; + } + *s++ = itoa64[val & 0x3f]; + size--; + val >>= 6; + bits -= 6; + } + *s = '\0'; + return 0; +} + +void putline(apr_file_t *f, const char *l) +{ + apr_status_t rv; + rv = apr_file_puts(l, f); + if (rv != APR_SUCCESS) { + apr_file_printf(errfile, "Error writing temp file: %pm", &rv); + apr_file_close(f); + exit(ERR_FILEPERM); + } +} + +int get_password(struct passwd_ctx *ctx) +{ + char buf[MAX_STRING_LEN + 1]; + apr_size_t bufsize = sizeof(buf); + if (apr_password_get("New password: ", ctx->out, &ctx->out_len) != 0) + goto err_too_long; + apr_password_get("Re-type new password: ", buf, &bufsize); + if (strcmp(ctx->out, buf) != 0) { + ctx->errstr = "password verification error"; + memset(ctx->out, '\0', ctx->out_len); + memset(buf, '\0', sizeof(buf)); + return ERR_PWMISMATCH; + } + memset(buf, '\0', sizeof(buf)); + return 0; + +err_too_long: + ctx->errstr = apr_psprintf(ctx->pool, + "password too long (>%" APR_SIZE_T_FMT ")", + ctx->out_len - 1); + return ERR_OVERFLOW; +} + +/* + * Make a password record from the given information. A zero return + * indicates success; on failure, ctx->errstr points to the error message. + */ +int mkhash(struct passwd_ctx *ctx) +{ + char *pw; + char pwin[MAX_STRING_LEN]; + char salt[16]; + apr_status_t rv; + int ret = 0; +#if CRYPT_ALGO_SUPPORTED + char *cbuf; +#endif + + if (ctx->passwd != NULL) { + pw = ctx->passwd; + } + else { + if ((ret = get_password(ctx)) != 0) + return ret; + pw = pwin; + } + + switch (ctx->alg) { + case ALG_APSHA: + /* XXX out >= 28 + strlen(sha1) chars - fixed len SHA */ + apr_sha1_base64(pw, strlen(pw), ctx->out); + break; + + case ALG_APMD5: + ret = generate_salt(salt, 8, &ctx->errstr, ctx->pool); + if (ret != 0) + break; + rv = apr_md5_encode(pw, salt, ctx->out, ctx->out_len); + if (rv != APR_SUCCESS) { + ctx->errstr = apr_psprintf(ctx->pool, + "could not encode password: %pm", &rv); + ret = ERR_GENERAL; + } + break; + + case ALG_PLAIN: + /* XXX this len limitation is not in sync with any HTTPd len. */ + apr_cpystrn(ctx->out, pw, ctx->out_len); + break; + +#if CRYPT_ALGO_SUPPORTED + case ALG_CRYPT: + ret = generate_salt(salt, 8, &ctx->errstr, ctx->pool); + if (ret != 0) + break; + cbuf = crypt(pw, salt); + if (cbuf == NULL) { + rv = APR_FROM_OS_ERROR(errno); + ctx->errstr = apr_psprintf(ctx->pool, "crypt() failed: %pm", &rv); + ret = ERR_PWMISMATCH; + break; + } + + apr_cpystrn(ctx->out, cbuf, ctx->out_len - 1); + if (strlen(pw) > 8) { + char *truncpw = strdup(pw); + truncpw[8] = '\0'; + if (!strcmp(ctx->out, crypt(truncpw, salt))) { + apr_file_printf(errfile, "Warning: Password truncated to 8 " + "characters by CRYPT algorithm." NL); + } + memset(truncpw, '\0', strlen(pw)); + free(truncpw); + } + break; +#endif /* CRYPT_ALGO_SUPPORTED */ + default: + apr_file_printf(errfile, "%s: BUG: invalid algorithm %d", __func__, + ctx->alg); + abort(); + } + memset(pw, '\0', strlen(pw)); + return ret; +} + +int parse_common_options(struct passwd_ctx *ctx, char opt, + const char *opt_arg) +{ + switch (opt) { + case 'b': + ctx->passwd_src = PW_ARG; + break; + case 'm': + ctx->alg = ALG_APMD5; + break; + case 's': + ctx->alg = ALG_APSHA; + break; + case 'p': + ctx->alg = ALG_PLAIN; +#if !PLAIN_ALGO_SUPPORTED + /* Backward compatible behavior: Just print a warning */ + apr_file_printf(errfile, + "Warning: storing passwords as plain text might just " + "not work on this platform." NL); +#endif + break; + case 'd': +#if CRYPT_ALGO_SUPPORTED + ctx->alg = ALG_CRYPT; +#else + /* Backward compatible behavior: Use MD5. OK since MD5 is more secure */ + apr_file_printf(errfile, + "Warning: CRYPT algorithm not supported on this " + "platform." NL + "Automatically using MD5 format." NL); + ctx->alg = ALG_APMD5; +#endif + break; + default: + apr_file_printf(errfile, "%s: BUG: invalid option %c", __func__, opt); + abort(); + } + return 0; +} diff --git a/support/passwd_common.h b/support/passwd_common.h new file mode 100644 index 0000000000..79341dda6f --- /dev/null +++ b/support/passwd_common.h @@ -0,0 +1,95 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +#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_version.h" + +#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 ERR_RANDOM 8 +#define ERR_GENERAL 9 +#define ERR_ALG_NOT_SUPP 10 + +#define NL APR_EOL_STR + +#if defined(WIN32) || defined(NETWARE) +#define CRYPT_ALGO_SUPPORTED 0 +#define PLAIN_ALGO_SUPPORTED 1 +#else +#define CRYPT_ALGO_SUPPORTED 1 +#define PLAIN_ALGO_SUPPORTED 0 +#endif + +/* + * Must be initialized with apr_file_open_stderr() before using any of the + * below functions. + */ +extern apr_file_t *errfile; + +struct passwd_ctx { + apr_pool_t *pool; + const char *errstr; + char *out; + apr_size_t out_len; + char *passwd; + int alg; + enum { + PW_PROMPT = 0, + PW_ARG + } passwd_src; +}; + +/* + * Write a line to the file. On error, print a message and exit + */ +void putline(apr_file_t *f, const char *l); + +/* + * The following functions return zero on success; otherwise, one of + * the ERR_* codes is returned and an error message is stored in ctx->errstr. + */ + +/* + * Parse the algorithm specific options. + */ +int parse_common_options(struct passwd_ctx *ctx, char opt, const char *opt_arg); + +/* + * Ask for password with verification. + */ +int get_password(struct passwd_ctx *ctx); + +/* + * Make a password record from the given information. + */ +int mkhash(struct passwd_ctx *ctx);