]> granicus.if.org Git - apache/blobdiff - support/htpasswd.c
Follow up to r1715876: core directives are ASCII.
[apache] / support / htpasswd.c
index 1363f747bd7d74f4fff55dcaf451d255e0d76dd7..11023499a478741a7e82f26ecebcaa3e21491433 100644 (file)
@@ -1,59 +1,17 @@
-/* ====================================================================
- * The Apache Software License, Version 1.1
+/* 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
  *
- * Copyright (c) 2000 The Apache Software Foundation.  All rights
- * reserved.
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * 3. The end-user documentation included with the redistribution,
- *    if any, must include the following acknowledgment:
- *       "This product includes software developed by the
- *        Apache Software Foundation (http://www.apache.org/)."
- *    Alternately, this acknowledgment may appear in the software itself,
- *    if and wherever such third-party acknowledgments normally appear.
- *
- * 4. The names "Apache" and "Apache Software Foundation" must
- *    not be used to endorse or promote products derived from this
- *    software without prior written permission. For written
- *    permission, please contact apache@apache.org.
- *
- * 5. Products derived from this software may not be called "Apache",
- *    nor may "Apache" appear in their name, without prior written
- *    permission of the Apache Software Foundation.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation.  For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- * Portions of this software are based upon public domain software
- * originally written at the National Center for Supercomputing Applications,
- * University of Illinois, Urbana-Champaign.
+ * 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.
  */
 
 /******************************************************************************
@@ -65,7 +23,7 @@
 /*
  * htpasswd.c: simple program for manipulating password file for
  * the Apache HTTP server
- * 
+ *
  * Originally by Rob McCool
  *
  * Exit values:
  *  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_lib.h"
-#include "apr_errno.h"
-#include "ap_config.h"
+#include "passwd_common.h"
+#include "apr_signal.h"
+#include "apr_getopt.h"
+
+#if APR_HAVE_STDIO_H
+#include <stdio.h>
+#endif
+
 #include "apr_md5.h"
-#include "ap_sha1.h"
-#include <signal.h>
+#include "apr_sha1.h"
 
-#ifdef HAVE_CRYPT_H
-#include <crypt.h>
+#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
 #define unlink _unlink
 #endif
 
-#ifndef CHARSET_EBCDIC
-#define LF 10
-#define CR 13
-#else /*CHARSET_EBCDIC*/
-#define LF '\n'
-#define CR '\r'
-#endif /*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 APHTP_NEWFILE        1
+#define APHTP_NOFILE         2
+#define APHTP_DELUSER        4
+#define APHTP_VERIFY         8
 
-/*
- * This needs to be declared statically so the signal handler can
- * access it.
- */
-static char *tempfilename;
-/*
- * If our platform knows about the tmpnam() external buffer size, create
- * a buffer to pass in.  This is needed in a threaded environment, or
- * one that thinks it is (like HP-UX).
- */
-#ifdef L_tmpnam
-static char tname_buf[L_tmpnam];
-#else
-static char *tname_buf = NULL;
-#endif
+apr_file_t *ftemp = NULL;
 
-/*
- * Get a line of input from the user, not including any terminating
- * newline.
- */
-static int getline(char *s, int n, FILE *f)
+static int mkrecord(struct passwd_ctx *ctx, char *user)
 {
-    register int i = 0;
+    char hash_str[MAX_STRING_LEN];
+    int ret;
+    ctx->out = hash_str;
+    ctx->out_len = sizeof(hash_str);
 
-    while (1) {
-       s[i] = (char) fgetc(f);
+    ret = mkhash(ctx);
+    if (ret)
+        return ret;
 
-       if (s[i] == CR) {
-           s[i] = fgetc(f);
-       }
-
-       if ((s[i] == 0x4) || (s[i] == LF) || (i == (n - 1))) {
-           s[i] = '\0';
-           return (feof(f) ? 1 : 0);
-       }
-       ++i;
+    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;
     }
-}
-
-static void putline(FILE *f, char *l)
-{
-    int x;
-
-    for (x = 0; l[x]; x++) {
-       fputc(l[x], f);
-    }
-    fputc('\n', f);
-}
-
-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;
-    }
-}
-
-/*
- * 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, 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];
-    size_t bufsize;
-
-    if (passwd != NULL) {
-       pw = passwd;
-    }
-    else {
-        bufsize = sizeof(pwin);
-       if (ap_getpass("New password: ", pwin, &bufsize) != 0) {
-           ap_snprintf(record, (rlen - 1), "password too long (>%d)",
-                       sizeof(pwin) - 1);
-           return ERR_OVERFLOW;
-       }
-        bufsize = sizeof(pwv);
-       ap_getpass("Re-type new password: ", pwv, &bufsize);
-       if (strcmp(pwin, pwv) != 0) {
-           ap_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 */
-       ap_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';
-
-       ap_MD5Encode((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. */
-       ap_cpystrn(cpw,pw,sizeof(cpw));
-       break;
-
-    case ALG_CRYPT:
-    default:
-        (void) srand((int) time((time_t *) NULL));
-        to64(&salt[0], rand(), 8);
-        salt[8] = '\0';
-
-       ap_cpystrn(cpw, (char *)crypt(pw, salt), sizeof(cpw) - 1);
-       break;
-    }
-    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)) {
-       ap_cpystrn(record, "resultant record too long", (rlen - 1));
-       return ERR_OVERFLOW;
-    }
-    strcpy(record, user);
-    strcat(record, ":");
-    strcat(record, cpw);
     return 0;
 }
 
-static int usage(void)
+static void usage(void)
 {
-    fprintf(stderr, "Usage:\n");
-    fprintf(stderr, "\thtpasswd [-cmdps] passwordfile username\n");
-    fprintf(stderr, "\thtpasswd -b[cmdps] passwordfile username password\n\n");
-    fprintf(stderr, "\thtpasswd -n[mdps] username\n");
-    fprintf(stderr, "\thtpasswd -nb[mdps] username password\n");
-    fprintf(stderr, " -c  Create a new file.\n");
-    fprintf(stderr, " -n  Don't update file; display results on stdout.\n");
-    fprintf(stderr, " -m  Force MD5 encryption of the password"
-#if defined(WIN32) || defined(TPF)
-       " (default)"
-#endif
-       ".\n");
-    fprintf(stderr, " -d  Force CRYPT encryption of the password"
-#if (!(defined(WIN32) || defined(TPF)))
-           " (default)"
-#endif
-           ".\n");
-    fprintf(stderr, " -p  Do not encrypt the password (plaintext).\n");
-    fprintf(stderr, " -s  Force SHA encryption of the password.\n");
-    fprintf(stderr, " -b  Use the password from the command line rather "
-           "than prompting for it.\n");
-    fprintf(stderr,
-           "On Windows and TPF systems the '-m' flag is used by default.\n");
-    fprintf(stderr,
-           "On all other systems, the '-p' flag will probably not work.\n");
-    return ERR_SYNTAX;
-}
-
-static void interrupted(void)
-{
-    fprintf(stderr, "Interrupted.\n");
-    if (tempfilename != NULL) {
-       unlink(tempfilename);
-    }
-    exit(ERR_INTERRUPTED);
+    apr_file_printf(errfile, "Usage:" 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
+        " -c  Create a new file." NL
+        " -n  Don't update file; display results on stdout." NL
+        " -b  Use the password from the command line rather than prompting "
+            "for it." NL
+        " -i  Read password from stdin without verification (for script usage)." NL
+        " -m  Force MD5 encryption of the password (default)." NL
+        " -B  Force bcrypt encryption of the password (very secure)." NL
+        " -C  Set the computing time used for the bcrypt algorithm" NL
+        "     (higher is more secure but slower, default: %d, valid: 4 to 31)." NL
+        " -d  Force CRYPT encryption of the password (8 chars max, insecure)." NL
+        " -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 "
+            "MD5 algorithm." NL,
+        BCRYPT_DEFAULT_COST
+    );
+    exit(ERR_SYNTAX);
 }
 
 /*
  * Check to see if the specified file can be opened for the given
  * access.
  */
-static int accessible(char *fname, char *mode)
+static int accessible(apr_pool_t *pool, char *fname, int mode)
 {
-    FILE *s;
+    apr_file_t *f = NULL;
 
-    s = fopen(fname, mode);
-    if (s == NULL) {
-       return 0;
+    if (apr_file_open(&f, fname, mode, APR_OS_DEFAULT, pool) != APR_SUCCESS) {
+        return 0;
     }
-    fclose(s);
+    apr_file_close(f);
     return 1;
 }
 
 /*
- * Return true if a file is readable.
+ * Return true if the named file exists, regardless of permissions.
  */
-static int readable(char *fname)
+static int exists(char *fname, apr_pool_t *pool)
 {
-    return accessible(fname, "r");
+    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);
 }
 
-/*
- * Return true if the specified file can be opened for write access.
- */
-static int writable(char *fname)
+static void terminate(void)
 {
-    return accessible(fname, "a");
+    apr_terminate();
+#ifdef NETWARE
+    pressanykey();
+#endif
 }
 
-/*
- * Return true if the named file exists, regardless of permissions.
- */
-static int exists(char *fname)
+static void check_args(int argc, const char *const argv[],
+                       struct passwd_ctx *ctx, unsigned *mask, char **user,
+                       char **pwfilename)
 {
-    ap_finfo_t sbuf;
-    int check;
+    const char *arg;
+    int args_left = 2;
+    int i, ret;
+    apr_getopt_t *state;
+    apr_status_t rv;
+    char opt;
+    const char *opt_arg;
+    apr_pool_t *pool = ctx->pool;
+
+    rv = apr_getopt_init(&state, pool, argc, argv);
+    if (rv != APR_SUCCESS)
+        exit(ERR_SYNTAX);
+
+    while ((rv = apr_getopt(state, "cnmspdBbDiC:v", &opt, &opt_arg)) == APR_SUCCESS) {
+        switch (opt) {
+        case 'c':
+            *mask |= APHTP_NEWFILE;
+            break;
+        case 'n':
+            args_left--;
+            *mask |= APHTP_NOFILE;
+            break;
+        case 'D':
+            *mask |= APHTP_DELUSER;
+            break;
+        case 'v':
+            *mask |= APHTP_VERIFY;
+            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) & (*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_VERIFY) && ctx->passwd_src == PW_PROMPT)
+        ctx->passwd_src = PW_PROMPT_VERIFY;
 
-    check = ap_stat(&sbuf, fname, NULL);
-    return ((check == -1) && (errno == ENOENT)) ? 0 : 1;
+    /*
+     * Make sure we still have exactly the right number of arguments left
+     * (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)) {
+        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]) > (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++]);
+    if ((arg = strchr(*user, ':')) != NULL) {
+        apr_file_printf(errfile, "%s: username contains illegal "
+                        "character '%c'" NL, argv[0], *arg);
+        exit(ERR_BADUSER);
+    }
+    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);
+        }
+        ctx->passwd = apr_pstrdup(pool, argv[i]);
+    }
 }
 
-/*
- * Copy from the current position of one file to the current position
- * of another.
- */
-static void copy_file(FILE *target, FILE *source)
+static int verify(struct passwd_ctx *ctx, const char *hash)
 {
-    static char line[MAX_STRING_LEN];
-
-    while (fgets(line, sizeof(line), source) != NULL) {
-       fputs(line, target);
-    }
+    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.
  */
-int main(int argc, char *argv[])
+int main(int argc, const char * const argv[])
 {
-    FILE *ftemp = NULL;
-    FILE *fpw = NULL;
-    char user[MAX_STRING_LEN];
-    char password[MAX_STRING_LEN];
-    char record[MAX_STRING_LEN];
+    apr_file_t *fpw = NULL;
     char line[MAX_STRING_LEN];
-    char pwfilename[MAX_STRING_LEN];
-    char *arg;
+    char *pwfilename = NULL;
+    char *user = NULL;
+    char tn[] = "htpasswd.tmp.XXXXXX";
+    char *dirname;
+    char *scratch, cp[MAX_STRING_LEN];
     int found = 0;
-    int alg = ALG_CRYPT;
-    int newfile = 0;
-    int nofile = 0;
-    int noninteractive = 0;
     int i;
-    int args_left = 2;
-#ifdef CHARSET_EBCDIC
-    ap_pool_t *pool;
-    ap_status_t rv;
-    ap_xlate_t *to_ascii;
+    unsigned 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;
+#endif
 
-    ap_initialize();
-    atexit(ap_terminate);
-    ap_create_pool(&pool, NULL);
+    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;
 
-    rv = ap_xlate_open(&to_ascii, "ISO8859-1", APR_DEFAULT_CHARSET, pool);
+#if APR_CHARSET_EBCDIC
+    rv = apr_xlate_open(&to_ascii, "ISO-8859-1", APR_DEFAULT_CHARSET, pool);
     if (rv) {
-        fprintf(stderr, "ap_xlate_open(to ASCII)->%d\n", rv);
+        apr_file_printf(errfile, "apr_xlate_open(to ASCII)->%d" NL, rv);
         exit(1);
     }
-    rv = ap_SHA1InitEBCDIC(to_ascii);
+    rv = apr_SHA1InitEBCDIC(to_ascii);
     if (rv) {
-        fprintf(stderr, "ap_SHA1InitEBCDIC()->%d\n", rv);
+        apr_file_printf(errfile, "apr_SHA1InitEBCDIC()->%d" NL, rv);
         exit(1);
     }
-    rv = ap_MD5InitEBCDIC(to_ascii);
+    rv = apr_MD5InitEBCDIC(to_ascii);
     if (rv) {
-        fprintf(stderr, "ap_MD5InitEBCDIC()->%d\n", rv);
+        apr_file_printf(errfile, "apr_MD5InitEBCDIC()->%d" NL, rv);
         exit(1);
     }
-#endif /*CHARSET_EBCDIC*/
-
-    tempfilename = NULL;
-    signal(SIGINT, (void (*)(int)) interrupted);
+#endif /*APR_CHARSET_EBCDIC*/
 
-    /*
-     * 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) {
-       return usage();
-    }
+    check_args(argc, argv, &ctx, &mask, &user, &pwfilename);
 
     /*
-     * Go through the argument list and pick out any options.  They
-     * have to precede any other arguments.
+     * Only do the file checks if we're supposed to frob it.
      */
-    for (i = 1; i < argc; i++) {
-       arg = argv[i];
-       if (*arg != '-') {
-           break;
-       }
-       while (*++arg != '\0') {
-           if (*arg == 'c') {
-               newfile++;
-           }
-           else if (*arg == 'n') {
-               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') {
-               noninteractive++;
-               args_left++;
-           }
-           else {
-               return usage();
-           }
-       }
-    }
-
-    /*
-     * 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) {
-       return usage();
-    }
-    if (newfile && nofile) {
-       fprintf(stderr, "%s: -c and -n options conflict\n", argv[0]);
-       return ERR_SYNTAX;
-    }
-    if (nofile) {
-       i--;
-    }
-    else {
-       if (strlen(argv[i]) > (sizeof(pwfilename) - 1)) {
-           fprintf(stderr, "%s: filename too long\n", argv[0]);
-           return ERR_OVERFLOW;
-       }
-       strcpy(pwfilename, argv[i]);
-       if (strlen(argv[i + 1]) > (sizeof(user) - 1)) {
-           fprintf(stderr, "%s: username too long (>%d)\n", argv[0],
-                   sizeof(user) - 1);
-           return ERR_OVERFLOW;
-       }
-    }
-    strcpy(user, argv[i + 1]);
-    if ((arg = strchr(user, ':')) != NULL) {
-       fprintf(stderr, "%s: username contains illegal character '%c'\n",
-               argv[0], *arg);
-       return ERR_BADUSER;
-    }
-    if (noninteractive) {
-       if (strlen(argv[i + 2]) > (sizeof(password) - 1)) {
-           fprintf(stderr, "%s: password too long (>%d)\n", argv[0],
-                   sizeof(password) - 1);
-           return ERR_OVERFLOW;
-       }
-       strcpy(password, argv[i + 2]);
-    }
-
-#ifdef WIN32
-    if (alg == ALG_CRYPT) {
-       alg = ALG_APMD5;
-       fprintf(stderr, "Automatically using MD5 format on Windows.\n");
-    }
-#endif
-
-#if (!(defined(WIN32) || defined(TPF)))
-    if (alg == ALG_PLAIN) {
-       fprintf(stderr,"Warning: storing passwords as plain text might "
-               "just not work on this platform.\n");
-    }
-#endif
-    if (! nofile) {
-       /*
-        * Only do the file checks if we're supposed to frob it.
-        *
-        * Verify that the file exists if -c was omitted.  We give a special
-        * message if it doesn't.
-        */
-       if ((! newfile) && (! exists(pwfilename))) {
-           fprintf(stderr,
-                   "%s: cannot modify file %s; use '-c' to create it\n",
-                   argv[0], pwfilename);
-           perror("fopen");
-           exit(ERR_FILEPERM);
-       }
-       /*
-        * Verify that we can read the existing file in the case of an update
-        * to it (rather than creation of a new one).
-        */
-       if ((! newfile) && (! readable(pwfilename))) {
-           fprintf(stderr, "%s: cannot open file %s for read access\n",
-                   argv[0], pwfilename);
-           perror("fopen");
-           exit(ERR_FILEPERM);
-       }
-       /*
-        * Now check to see if we can preserve an existing file in case
-        * of password verification errors on a -c operation.
-        */
-       if (newfile && exists(pwfilename) && (! readable(pwfilename))) {
-           fprintf(stderr, "%s: cannot open file %s for read access\n"
-                   "%s: existing auth data would be lost on "
-                   "password mismatch",
-                   argv[0], pwfilename, argv[0]);
-           perror("fopen");
-           exit(ERR_FILEPERM);
-       }
-       /*
-        * Now verify that the file is writable!
-        */
-       if (! writable(pwfilename)) {
-           fprintf(stderr, "%s: cannot open file %s for write access\n",
-                   argv[0], pwfilename);
-           perror("fopen");
-           exit(ERR_FILEPERM);
-       }
+    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_FOPEN_READ|APR_FOPEN_WRITE)) {
+                apr_file_printf(errfile, "%s: cannot open file %s for "
+                                "read/write access" NL, 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" NL,
+                        argv[0], pwfilename);
+                exit(ERR_FILEPERM);
+            }
+            /*
+             * As it doesn't exist yet, verify that we can create it.
+             */
+            if (!accessible(pool, pwfilename, APR_FOPEN_WRITE|APR_FOPEN_CREATE)) {
+                apr_file_printf(errfile, "%s: cannot create file %s" NL,
+                                argv[0], pwfilename);
+                exit(ERR_FILEPERM);
+            }
+        }
     }
 
     /*
@@ -559,101 +362,148 @@ int main(int argc, char *argv[])
      * Any error message text is returned in the record buffer, since
      * the mkrecord() routine doesn't have access to argv[].
      */
-    i = mkrecord(user, record, sizeof(record) - 1,
-                noninteractive ? password : NULL,
-                alg);
-    if (i != 0) {
-       fprintf(stderr, "%s: %s\n", argv[0], record);
-       exit(i);
-    }
-    if (nofile) {
-       printf("%s\n", record);
-       exit(0);
+    if ((mask & (APHTP_DELUSER|APHTP_VERIFY)) == 0) {
+        i = mkrecord(&ctx, user);
+        if (i != 0) {
+            apr_file_printf(errfile, "%s: %s" NL, argv[0], ctx.errstr);
+            exit(i);
+        }
+        if (mask & APHTP_NOFILE) {
+            printf("%s" NL, ctx.out);
+            exit(0);
+        }
+    }
+
+    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);
+        }
     }
 
-    /*
-     * We can access the files the right way, and we have a record
-     * to add or update.  Let's do it..
-     */
-    errno = 0;
-    tempfilename = tmpnam(tname_buf);
-    if ((tempfilename == NULL) || (*tempfilename == '\0')) {
-       fprintf(stderr, "%s: unable to generate temporary filename\n",
-               argv[0]);
-       if (errno == 0) {
-           errno = ENOENT;
-       }
-       perror("tmpnam");
-       exit(ERR_FILEPERM);
-    }
-    ftemp = fopen(tempfilename, "w+");
-    if (ftemp == NULL) {
-       fprintf(stderr, "%s: unable to create temporary file '%s'\n", argv[0],
-               tempfilename);
-       perror("fopen");
-       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 (! newfile) {
-       char scratch[MAX_STRING_LEN];
-
-       fpw = fopen(pwfilename, "r");
-       while (! (getline(line, sizeof(line), fpw))) {
-           char *colon;
-
-           if ((line[0] == '#') || (line[0] == '\0')) {
-               putline(ftemp, line);
-               continue;
-           }
-           strcpy(scratch, line);
-           /*
-            * See if this is our user.
-            */
-           colon = strchr(scratch, ':');
-           if (colon != NULL) {
-               *colon = '\0';
-           }
-           if (strcmp(user, scratch) != 0) {
-               putline(ftemp, line);
-               continue;
-           }
-           found++;
-           break;
-       }
-    }
-    if (found) {
-       fprintf(stderr, "Updating ");
-    }
-    else {
-       fprintf(stderr, "Adding ");
-    }
-    fprintf(stderr, "password for user %s\n", user);
-    /*
-     * Now add the user record we created.
-     */
-    putline(ftemp, record);
-    /*
-     * If we're updating an existing file, there may be additional
-     * records beyond the one we're updating, so copy them.
+    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" NL,
+                            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, "%s: The file %s does not appear "
+                                         "to be a valid htpasswd file." NL,
+                                argv[0], pwfilename);
+                apr_file_close(fpw);
+                exit(ERR_INVALID);
+            }
+            if (strcmp(user, scratch) != 0) {
+                putline(ftemp, line);
+                continue;
+            }
+            else {
+                /* 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);
+                }
+            }
+        }
+        apr_file_close(fpw);
+    }
+    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);
+        }
+    }
+    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.
      */
-    if (! newfile) {
-       copy_file(ftemp, fpw);
-       fclose(fpw);
+    if (apr_file_copy(dirname, pwfilename, APR_FILE_SOURCE_PERMS, pool) !=
+        APR_SUCCESS) {
+        apr_file_printf(errfile, "%s: unable to update file %s" NL,
+                        argv[0], pwfilename);
+        exit(ERR_FILEPERM);
     }
-    /*
-     * The temporary file now contains the information that should be
-     * in the actual password file.  Close the open files, re-open them
-     * in the appropriate mode, and copy them file to the real one.
-     */
-    fclose(ftemp);
-    fpw = fopen(pwfilename, "w+");
-    ftemp = fopen(tempfilename, "r");
-    copy_file(fpw, ftemp);
-    fclose(fpw);
-    fclose(ftemp);
-    unlink(tempfilename);
+    apr_file_close(ftemp);
     return 0;
 }