[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#325547: /usr/bin/htdigest: noninteractive use of htdigest



Package: apache2-utils
Version: 2.0.54-4
Severity: wishlist
File: /usr/bin/htdigest
Tags: patch


In some cases it may be useful to manage digest files noninteractively.
The attached patch prototypes a way of doing that (well, it works, but
it doesn't use APR as extensively as it probably should).  It adds an
option "-f" that causes the new password to be read from a file--or from
stdin if "-" is used as a filename--without prompting for confirmation.

Some minor cleanups were necessary; all argv "arithmetic" is in one
place now, as is reading of the actual password.  Some buffer copying of
command line arguments looks like it may be unneeded, but I left it in
place to reduce the risk of screwups.

-- System Information:
Debian Release: 3.1
  APT prefers unstable
  APT policy: (50, 'unstable')
Architecture: i386 (i686)
Shell:  /bin/sh linked to /bin/bash
Kernel: Linux 2.6.11
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8)

Versions of packages apache2-utils depends on:
ii  libapr0                2.0.54-4          the Apache Portable Runtime
ii  libc6                  2.3.2.ds1-22      GNU C Library: Shared libraries an
ii  libdb4.2               4.2.52-18         Berkeley v4.2 Database Libraries [
ii  libexpat1              1.95.8-3          XML parsing C library - runtime li
ii  libldap2               2.1.30-10         OpenLDAP libraries
ii  libpcre3               5.0-1.1           Perl 5 Compatible Regular Expressi
ii  libssl0.9.7            0.9.7g-1          SSL shared libraries
ii  zlib1g                 1:1.2.2-4.sarge.2 compression library - runtime

-- no debconf information
diff -ru httpd-2.0.54.org/docs/manual/programs/htdigest.xml httpd-2.0.54.patch/docs/manual/programs/htdigest.xml
--- httpd-2.0.54.org/docs/manual/programs/htdigest.xml	2005-02-05 03:21:18.000000000 +0700
+++ httpd-2.0.54.patch/docs/manual/programs/htdigest.xml	2005-08-29 14:37:12.807750434 +0700
@@ -43,7 +43,9 @@
 <section id="synopsis"><title>Synopsis</title>
     <p><code><strong>htdigest</strong> [ -<strong>c</strong> ]
     <var>passwdfile</var> <var>realm</var> <var>username</var></code></p>
-</section>
+    <p><code><strong>htdigest</strong> -<strong>f</strong> [ -<strong>c</strong> ]
+    <var>passwdfile</var> <var>realm</var> <var>username</var> <var>inputfile</var>
+</code></p></section>
 
 <section id="options"><title>Options</title>
     <dl>
@@ -64,6 +66,14 @@
     <var>username</var> does not exist is this file, an entry is added. If it
     does exist, the password is changed.</dd>
     </dl>
+    <dl>
+    <dt><code>-f</code></dt>
+    <dd>Don't prompt for password; open <var>inputfile</var> and read the new
+    password from there instead.  Only the first line is read, which may not be
+    empty.  As a special case, if <var>inputfile</var> is just a single hyphen
+    (<strong>-</strong>), standard input will be read as if it were a regular
+    file.  The user is not prompted to repeat the password for confirmation.</dd>
+    </dl>
 </section>
 
 </manualpage>
diff -ru httpd-2.0.54.org/support/htdigest.c httpd-2.0.54.patch/support/htdigest.c
--- httpd-2.0.54.org/support/htdigest.c	2005-03-01 17:02:06.000000000 +0700
+++ httpd-2.0.54.patch/support/htdigest.c	2005-08-29 17:41:51.095844118 +0700
@@ -67,6 +67,8 @@
 apr_xlate_t *to_ascii;
 #endif
 
+
+
 static void cleanup_tempfile_and_exit(int rc)
 {
     if (tfp) {
@@ -119,28 +121,94 @@
 }
 
 
-static void add_password(const char *user, const char *realm, apr_file_t *f)
+/* Ask for password entered interactively.
+ *
+ * Returns a program exit code on failure, or 0 for success.
+ */
+static int getpw_interactive(char buf[], apr_size_t len)
+{
+    char confirmbuf[MAX_STRING_LEN];
+
+    apr_size_t tmplen = len;
+    if (apr_password_get("New password: ", buf, &tmplen) != APR_SUCCESS) {
+        apr_file_printf(errfile, "password too long\n");
+        return 5;
+    }
+    tmplen = sizeof(confirmbuf);
+    apr_password_get("Re-type new password: ", confirmbuf, &tmplen);
+    if (strcmp(buf, confirmbuf) != 0) {
+        apr_file_printf(errfile, "They don't match, sorry.\n");
+        return 1;
+    }
+
+    return 0;
+}
+
+
+/* Read new password from file called pwfile.
+ *
+ * Returns a program exit code on failure, or 0 for success.
+ */
+static int getpw_fromfile(char buf[], apr_size_t len, const char pw_inputfile[])
+{
+    FILE *infile = NULL;
+    const char *c;
+    int close_infile = 0;
+    int e;
+    size_t s;
+
+    /* TODO: So far, these return codes are entirely arbitrary. */
+
+    /* Open password input file ("-" means standard input) */
+    if (strcmp(pw_inputfile, "-") == 0) {
+        infile = stdin;
+        if (!infile) {
+            apr_file_printf(errfile, "standard input not available\n");
+            return 1;
+        }
+    }
+    else {
+        infile = fopen(pw_inputfile, "rb");
+        if (!infile) {
+            const int e = errno;
+            apr_file_printf(errfile,
+                            "could not open '%s' for reading: %s\n",
+                            pw_inputfile,
+                            strerror(e));
+            return 1;
+        }
+        close_infile = 1;
+    }
+
+    c = fgets(buf, len, infile);
+    e = errno;
+    if (close_infile) fclose(infile);
+
+    if (!c || !buf[0]) {
+        apr_file_printf(errfile, 
+                        "could not read password: %s\n",
+                        strerror(e));
+        return 1;
+    }
+
+    s = strlen(buf);
+    if (s && buf[s-1] == '\n') buf[s-1] = '\0';
+
+    return 0;
+}
+
+
+static void add_password(const char *user,
+                         const char *realm,
+                         apr_file_t *f,
+                         const char pw[])
 {
-    char *pw;
     apr_md5_ctx_t context;
     unsigned char digest[16];
     char string[MAX_STRING_LEN];
-    char pwin[MAX_STRING_LEN];
-    char pwv[MAX_STRING_LEN];
-    unsigned int i;
-    apr_size_t len = sizeof(pwin);
-
-    if (apr_password_get("New password: ", pwin, &len) != APR_SUCCESS) {
-        apr_file_printf(errfile, "password too long");
-        cleanup_tempfile_and_exit(5);
-    }
-    len = sizeof(pwin);
-    apr_password_get("Re-type new password: ", pwv, &len);
-    if (strcmp(pwin, pwv) != 0) {
-        apr_file_printf(errfile, "They don't match, sorry.\n");
-        cleanup_tempfile_and_exit(1);
-    }
-    pw = pwin;
+    int i;
+    int errcode;
+
     apr_file_printf(f, "%s:%s:", user, realm);
 
     /* Do MD5 stuff */
@@ -162,7 +230,10 @@
 static void usage(void)
 {
     apr_file_printf(errfile, "Usage: htdigest [-c] passwordfile realm username\n");
-    apr_file_printf(errfile, "The -c flag creates a new file.\n");
+    apr_file_printf(errfile, "or     htdigest -f [-c] passwordfile realm username inputfile\n");
+    apr_file_printf(errfile, "The -c flag creates a new file.  The -f flag will cause the new password to be\n"
+	"read from a given input file (or stdin if the given filename is '-'), in which\n"
+	"case there will be no prompt for confirmation.\n");
     exit(1);
 }
 
@@ -192,7 +263,15 @@
     char l[MAX_STRING_LEN];
     char w[MAX_STRING_LEN];
     char x[MAX_STRING_LEN];
+    char pw[MAX_STRING_LEN];
     int found;
+    int createfile = 0, readfromfile = 0;
+    int pwres;
+    int i;
+    const char *arg_passwdfile = NULL;
+    const char *arg_realm = NULL;
+    const char *arg_username = NULL;
+    const char *arg_inputfile = NULL;
    
     apr_app_initialize(&argc, &argv, NULL);
     atexit(terminate); 
@@ -209,27 +288,50 @@
 #endif
     
     apr_signal(SIGINT, (void (*)(int)) interrupted);
-    if (argc == 5) {
-        if (strcmp(argv[1], "-c"))
-            usage();
-        rv = apr_file_open(&f, argv[2], APR_WRITE | APR_CREATE,
+
+    for (i = 1; i < 3 && argv[i]; ++i) {
+        if (strcmp(argv[i],"-c")==0) ++createfile;
+        else if (strcmp(argv[i],"-f")==0) ++readfromfile;
+    }
+
+    /* The -c option adds 1 argument to the list; -f adds two (itself plus an
+     * input filename).  Given that, check for the right number of remaining
+     * arguments.
+     */
+    if (createfile > 1 ||
+        readfromfile > 1 ||
+        (argc-1-createfile-2*readfromfile) != 3) {
+        usage();
+        exit(2);
+    }
+
+    memset(pw, 0, sizeof(pw));
+    arg_passwdfile = argv[1+createfile+readfromfile];
+    arg_realm = argv[2+createfile+readfromfile];
+    arg_username = argv[3+createfile+readfromfile];
+    if (readfromfile) arg_inputfile = argv[4+createfile+readfromfile];
+
+    
+    if (readfromfile) pwres = getpw_fromfile(pw, sizeof(pw), arg_inputfile);
+    else              pwres = getpw_interactive(pw, sizeof(pw));
+    if (pwres != 0) return pwres;
+    if (createfile) {
+        rv = apr_file_open(&f, arg_passwdfile, 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],
+                    arg_passwdfile,
                     apr_strerror(rv, errmsg, sizeof errmsg));
             exit(1);
         }
         apr_file_printf(errfile, "Adding password for %s in realm %s.\n", 
-                    argv[4], argv[3]);
-        add_password(argv[4], argv[3], f);
+                    arg_username, arg_realm);
+        add_password(arg_username, arg_realm, f, pw);
         apr_file_close(f);
         exit(0);
     }
-    else if (argc != 4)
-        usage();
 
     if (apr_temp_dir_get((const char**)&dirname, cntxt) != APR_SUCCESS) {
         apr_file_printf(errfile, "%s: could not determine temp dir\n",
@@ -243,14 +345,14 @@
         exit(1);
     }
 
-    if (apr_file_open(&f, argv[1], APR_READ, APR_OS_DEFAULT, cntxt) != APR_SUCCESS) {
+    if (apr_file_open(&f, arg_passwdfile, APR_READ, APR_OS_DEFAULT, cntxt) != APR_SUCCESS) {
         apr_file_printf(errfile,
                 "Could not open passwd file %s for reading.\n", argv[1]);
         apr_file_printf(errfile, "Use -c option to create new one.\n");
         cleanup_tempfile_and_exit(1);
     }
-    apr_cpystrn(user, argv[3], sizeof(user));
-    apr_cpystrn(realm, argv[2], sizeof(realm));
+    apr_cpystrn(user, arg_username, sizeof(user));
+    apr_cpystrn(realm, arg_realm, sizeof(realm));
 
     found = 0;
     while (!(get_line(line, MAX_STRING_LEN, f))) {
@@ -268,22 +370,22 @@
         else {
             apr_file_printf(errfile, "Changing password for user %s in realm %s\n", 
                     user, realm);
-            add_password(user, realm, tfp);
+            add_password(user, realm, tfp, pw);
             found = 1;
         }
     }
     if (!found) {
         apr_file_printf(errfile, "Adding user %s in realm %s\n", user, realm);
-        add_password(user, realm, tfp);
+        add_password(user, realm, tfp, pw);
     }
     apr_file_close(f);
 
     /* The temporary file has all the data, just copy it to the new location.
      */
-    if (apr_file_copy(dirname, argv[1], APR_FILE_SOURCE_PERMS, cntxt) !=
+    if (apr_file_copy(dirname, arg_passwdfile, APR_FILE_SOURCE_PERMS, cntxt) !=
                 APR_SUCCESS) {
         apr_file_printf(errfile, "%s: unable to update file %s\n", 
-                        argv[0], argv[1]);
+                        argv[0], arg_passwdfile);
     }
     apr_file_close(tfp);
 

Reply to: