Index: support/htdbm.c =================================================================== --- support/htdbm.c (revision 1447461) +++ support/htdbm.c (working copy) @@ -110,6 +110,7 @@ #endif apr_pool_create( pool, NULL); + apr_pool_abort_set(abort_on_oom, *pool); apr_file_open_stderr(&errfile, *pool); apr_signal(SIGINT, (void (*)(int)) htdbm_interrupted); Index: support/passwd_common.c =================================================================== --- support/passwd_common.c (revision 1447461) +++ support/passwd_common.c (working copy) @@ -46,6 +46,24 @@ apr_file_t *errfile; +int abort_on_oom(int rc) +{ + const char *buf = "Error: out of memory\n"; + int written, count = strlen(buf); + do { + written = write(STDERR_FILENO, buf, count); + if (written == count) + break; + if (written > 0) { + buf += written; + count -= written; + } + } while (written >= 0 || errno == EINTR); + abort(); + /* NOTREACHED */ + return 0; +} + static int generate_salt(char *s, size_t size, const char **errstr, apr_pool_t *pool) { @@ -85,6 +103,8 @@ void putline(apr_file_t *f, const char *l) { apr_status_t rv; + if (f == NULL) + return; rv = apr_file_puts(l, f); if (rv != APR_SUCCESS) { apr_file_printf(errfile, "Error writing temp file: %pm", &rv); @@ -95,17 +115,17 @@ int get_password(struct passwd_ctx *ctx) { + char buf[MAX_STRING_LEN + 1]; if (ctx->passwd_src == PW_STDIN) { - char *buf = ctx->out; apr_file_t *file_stdin; apr_size_t nread; if (apr_file_open_stdin(&file_stdin, ctx->pool) != APR_SUCCESS) { ctx->errstr = "Unable to read from stdin."; return ERR_GENERAL; } - if (apr_file_read_full(file_stdin, buf, ctx->out_len - 1, + if (apr_file_read_full(file_stdin, buf, sizeof(buf) - 1, &nread) != APR_EOF - || nread == ctx->out_len - 1) { + || nread == sizeof(buf) - 1) { goto err_too_long; } buf[nread] = '\0'; @@ -115,21 +135,30 @@ buf[nread-2] = '\0'; } apr_file_close(file_stdin); + ctx->passwd = apr_pstrdup(ctx->pool, buf); } + else if (ctx->passwd_src == PW_PROMPT_VERIFY) { + apr_size_t bufsize = sizeof(buf); + if (apr_password_get("Enter password: ", buf, &bufsize) != 0) + goto err_too_long; + ctx->passwd = apr_pstrdup(ctx->pool, buf); + } else { - char buf[MAX_STRING_LEN + 1]; apr_size_t bufsize = sizeof(buf); - if (apr_password_get("New password: ", ctx->out, &ctx->out_len) != 0) + if (apr_password_get("New password: ", buf, &bufsize) != 0) goto err_too_long; + ctx->passwd = apr_pstrdup(ctx->pool, buf); + bufsize = sizeof(buf); + buf[0] = '\0'; apr_password_get("Re-type new password: ", buf, &bufsize); - if (strcmp(ctx->out, buf) != 0) { + if (strcmp(ctx->passwd, buf) != 0) { ctx->errstr = "password verification error"; - memset(ctx->out, '\0', ctx->out_len); + memset(ctx->passwd, '\0', strlen(ctx->passwd)); memset(buf, '\0', sizeof(buf)); return ERR_PWMISMATCH; } - memset(buf, '\0', sizeof(buf)); } + memset(buf, '\0', sizeof(buf)); return 0; err_too_long: @@ -146,7 +175,6 @@ int mkhash(struct passwd_ctx *ctx) { char *pw; - char pwin[MAX_STRING_LEN]; char salt[16]; apr_status_t rv; int ret = 0; @@ -159,14 +187,11 @@ "Warning: Ignoring -C argument for this algorithm." NL); } - if (ctx->passwd != NULL) { - pw = ctx->passwd; - } - else { + if (ctx->passwd == NULL) { if ((ret = get_password(ctx)) != 0) return ret; - pw = pwin; } + pw = ctx->passwd; switch (ctx->alg) { case ALG_APSHA: @@ -206,14 +231,13 @@ apr_cpystrn(ctx->out, cbuf, ctx->out_len - 1); if (strlen(pw) > 8) { - char *truncpw = strdup(pw); + char *truncpw = apr_pstrdup(ctx->pool, 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 */ Index: support/htdigest.c =================================================================== --- support/htdigest.c (revision 1447461) +++ support/htdigest.c (working copy) @@ -96,12 +96,15 @@ char ch; apr_status_t rv = APR_EINVAL; - while (i < (n - 1) && + /* we need 2 remaining bytes in buffer */ + while (i < (n - 2) && ((rv = apr_file_getc(&ch, f)) == APR_SUCCESS) && (ch != '\n')) { s[i++] = ch; } + /* First remaining byte potentially used here */ if (ch == '\n') s[i++] = ch; + /* Second remaining byte used here */ s[i] = '\0'; if (rv != APR_SUCCESS) @@ -202,8 +205,8 @@ #if APR_CHARSET_EBCDIC rv = apr_xlate_open(&to_ascii, "ISO-8859-1", APR_DEFAULT_CHARSET, cntxt); if (rv) { - apr_file_printf(errfile, "apr_xlate_open(): %s (%d)\n", - apr_strerror(rv, line, sizeof(line)), rv); + apr_file_printf(errfile, "apr_xlate_open(): %pm (%d)\n", + &rv, rv); exit(1); } #endif @@ -215,11 +218,8 @@ rv = apr_file_open(&f, argv[2], APR_WRITE | APR_CREATE, APR_OS_DEFAULT, cntxt); if (rv != APR_SUCCESS) { - char errmsg[120]; - - apr_file_printf(errfile, "Could not open passwd file %s for writing: %s\n", - argv[2], - apr_strerror(rv, errmsg, sizeof errmsg)); + apr_file_printf(errfile, "Could not open passwd file %s for writing: %pm\n", + argv[2], &rv); exit(1); } apr_cpystrn(user, argv[4], sizeof(user)); Index: support/passwd_common.h =================================================================== --- support/passwd_common.h (revision 1447461) +++ support/passwd_common.h (working copy) @@ -80,11 +80,18 @@ enum { PW_PROMPT = 0, PW_ARG, - PW_STDIN + PW_STDIN, + PW_PROMPT_VERIFY, } passwd_src; }; + /* + * To be used as apr_pool_abort_fn + */ +int abort_on_oom(int rc); + +/* * Write a line to the file. On error, print a message and exit */ void putline(apr_file_t *f, const char *l); Index: support/htpasswd.c =================================================================== --- support/htpasswd.c (revision 1447461) +++ support/htpasswd.c (working copy) @@ -67,6 +67,7 @@ #define APHTP_NEWFILE 1 #define APHTP_NOFILE 2 #define APHTP_DELUSER 4 +#define APHTP_VERIFY 8 apr_file_t *ftemp = NULL; @@ -92,8 +93,8 @@ static void usage(void) { apr_file_printf(errfile, "Usage:" NL - "\thtpasswd [-cimBdpsD] [-C cost] passwordfile username" NL - "\thtpasswd -b[cmBdpsD] [-C cost] passwordfile username password" NL + "\thtpasswd [-cimBdpsDv] [-C cost] passwordfile username" NL + "\thtpasswd -b[cmBdpsDv] [-C cost] passwordfile username password" NL NL "\thtpasswd -n[imBdps] [-C cost] username" NL "\thtpasswd -nb[mBdps] [-C cost] username password" NL @@ -110,6 +111,7 @@ " -s Force SHA encryption of the password (insecure)." NL " -p Do not encrypt the password (plaintext, insecure)." NL " -D Delete the specified user." NL + " -v Verify password for 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 " @@ -155,7 +157,7 @@ } static void check_args(int argc, const char *const argv[], - struct passwd_ctx *ctx, int *mask, char **user, + struct passwd_ctx *ctx, unsigned *mask, char **user, char **pwfilename) { const char *arg; @@ -171,7 +173,7 @@ if (rv != APR_SUCCESS) exit(ERR_SYNTAX); - while ((rv = apr_getopt(state, "cnmspdBbDiC:", &opt, &opt_arg)) == APR_SUCCESS) { + while ((rv = apr_getopt(state, "cnmspdBbDiC:v", &opt, &opt_arg)) == APR_SUCCESS) { switch (opt) { case 'c': *mask |= APHTP_NEWFILE; @@ -183,6 +185,9 @@ case 'D': *mask |= APHTP_DELUSER; break; + case 'v': + *mask |= APHTP_VERIFY; + break; default: ret = parse_common_options(ctx, opt, opt_arg); if (ret) { @@ -196,18 +201,15 @@ 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]); + if ((*mask) & (*mask - 1)) { + /* not a power of two, i.e. more than one flag specified */ + apr_file_printf(errfile, "%s: only one of -c -n -v -D may be specified" NL, + argv[0]); exit(ERR_SYNTAX); } - if ((*mask & APHTP_NEWFILE) && (*mask & APHTP_DELUSER)) { - apr_file_printf(errfile, "%s: -c and -D options conflict" NL, argv[0]); - exit(ERR_SYNTAX); - } - if ((*mask & APHTP_NOFILE) && (*mask & APHTP_DELUSER)) { - apr_file_printf(errfile, "%s: -n and -D options conflict" NL, argv[0]); - exit(ERR_SYNTAX); - } + if ((*mask & APHTP_VERIFY) && ctx->passwd_src == PW_PROMPT) + ctx->passwd_src = PW_PROMPT_VERIFY; + /* * Make sure we still have exactly the right number of arguments left * (the filename, the username, and possibly the password if -b was @@ -246,6 +248,25 @@ } } +static int verify(struct passwd_ctx *ctx, const char *hash) +{ + apr_status_t rv; + int ret; + + if (ctx->passwd == NULL && (ret = get_password(ctx)) != 0) + return ret; + rv = apr_password_validate(ctx->passwd, hash); + if (rv == APR_SUCCESS) + return 0; + if (APR_STATUS_IS_EMISMATCH(rv)) { + ctx->errstr = "password verification failed"; + return ERR_PWMISMATCH; + } + ctx->errstr = apr_psprintf(ctx->pool, "Could not verify password: %pm", + &rv); + return ERR_GENERAL; +} + /* * 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. @@ -253,7 +274,6 @@ int main(int argc, const char * const argv[]) { apr_file_t *fpw = NULL; - const char *errstr = NULL; char line[MAX_STRING_LEN]; char *pwfilename = NULL; char *user = NULL; @@ -262,7 +282,7 @@ char *scratch, cp[MAX_STRING_LEN]; int found = 0; int i; - int mask = 0; + unsigned mask = 0; apr_pool_t *pool; int existing_file = 0; struct passwd_ctx ctx = { 0 }; @@ -274,6 +294,7 @@ apr_app_initialize(&argc, &argv, NULL); atexit(terminate); apr_pool_create(&pool, NULL); + apr_pool_abort_set(abort_on_oom, pool); apr_file_open_stderr(&errfile, pool); ctx.pool = pool; ctx.alg = ALG_APMD5; @@ -341,10 +362,10 @@ * Any error message text is returned in the record buffer, since * the mkrecord() routine doesn't have access to argv[]. */ - if (!(mask & APHTP_DELUSER)) { + if ((mask & (APHTP_DELUSER|APHTP_VERIFY)) == 0) { i = mkrecord(&ctx, user); if (i != 0) { - apr_file_printf(errfile, "%s: %s" NL, argv[0], errstr); + apr_file_printf(errfile, "%s: %s" NL, argv[0], ctx.errstr); exit(i); } if (mask & APHTP_NOFILE) { @@ -353,21 +374,23 @@ } } - /* - * 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" NL, - argv[0]); - exit(ERR_FILEPERM); - } - dirname = apr_psprintf(pool, "%s/%s", dirname, tn); + if ((mask & APHTP_VERIFY) == 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" NL, + 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" NL, - argv[0], dirname); - exit(ERR_FILEPERM); + if (apr_file_mktemp(&ftemp, dirname, 0, pool) != APR_SUCCESS) { + apr_file_printf(errfile, "%s: unable to create temporary file %s" NL, + argv[0], dirname); + exit(ERR_FILEPERM); + } } /* @@ -418,33 +441,59 @@ continue; } else { - if (!(mask & APHTP_DELUSER)) { - /* We found the user we were looking for. - * Add him to the file. - */ + /* We found the user we were looking for */ + found++; + if ((mask & APHTP_DELUSER)) { + /* Delete entry from the file */ + apr_file_printf(errfile, "Deleting "); + } + else if ((mask & APHTP_VERIFY)) { + /* Verify */ + char *hash = colon + 1; + size_t len; + + len = strcspn(hash, "\r\n"); + if (len == 0) { + apr_file_printf(errfile, "Empty hash for user %s" NL, + user); + exit(ERR_INVALID); + } + hash[len] = '\0'; + + i = verify(&ctx, hash); + if (i != 0) { + apr_file_printf(errfile, "%s" NL, ctx.errstr); + exit(i); + } + } + else { + /* Update entry */ apr_file_printf(errfile, "Updating "); putline(ftemp, ctx.out); - 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, ctx.out); + if (!found) { + if (mask & APHTP_DELUSER) { + apr_file_printf(errfile, "User %s not found" NL, user); + exit(0); + } + else if (mask & APHTP_VERIFY) { + apr_file_printf(errfile, "User %s not found" NL, user); + exit(ERR_BADUSER); + } + else { + apr_file_printf(errfile, "Adding "); + putline(ftemp, ctx.out); + } } - else if (!found && (mask & APHTP_DELUSER)) { - apr_file_printf(errfile, "User %s not found" NL, user); + if (mask & APHTP_VERIFY) { + apr_file_printf(errfile, "Password for user %s correct." NL, user); exit(0); } + apr_file_printf(errfile, "password for user %s" NL, user); /* The temporary file has all the data, just copy it to the new location.