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: