]> granicus.if.org Git - apache/blob - support/htpasswd.c
merged latest changes in 2.4.x
[apache] / support / htpasswd.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /******************************************************************************
18  ******************************************************************************
19  * NOTE! This program is not safe as a setuid executable!  Do not make it
20  * setuid!
21  ******************************************************************************
22  *****************************************************************************/
23 /*
24  * htpasswd.c: simple program for manipulating password file for
25  * the Apache HTTP server
26  *
27  * Originally by Rob McCool
28  *
29  * Exit values:
30  *  0: Success
31  *  1: Failure; file access/permission problem
32  *  2: Failure; command line syntax problem (usage message issued)
33  *  3: Failure; password verification failure
34  *  4: Failure; operation interrupted (such as with CTRL/C)
35  *  5: Failure; buffer would overflow (username, filename, or computed
36  *     record too long)
37  *  6: Failure; username contains illegal or reserved characters
38  *  7: Failure; file is not a valid htpasswd file
39  */
40
41 #include "passwd_common.h"
42 #include "apr_signal.h"
43 #include "apr_getopt.h"
44
45 #if APR_HAVE_STDIO_H
46 #include <stdio.h>
47 #endif
48
49 #include "apr_md5.h"
50 #include "apr_sha1.h"
51
52 #if APR_HAVE_STDLIB_H
53 #include <stdlib.h>
54 #endif
55 #if APR_HAVE_STRING_H
56 #include <string.h>
57 #endif
58 #if APR_HAVE_UNISTD_H
59 #include <unistd.h>
60 #endif
61
62 #ifdef WIN32
63 #include <conio.h>
64 #define unlink _unlink
65 #endif
66
67 #define APHTP_NEWFILE        1
68 #define APHTP_NOFILE         2
69 #define APHTP_DELUSER        4
70 #define APHTP_VERIFY         8
71
72 apr_file_t *ftemp = NULL;
73
74 static int mkrecord(struct passwd_ctx *ctx, char *user)
75 {
76     char hash_str[MAX_STRING_LEN];
77     int ret;
78     ctx->out = hash_str;
79     ctx->out_len = sizeof(hash_str);
80
81     ret = mkhash(ctx);
82     if (ret)
83         return ret;
84
85     ctx->out = apr_pstrcat(ctx->pool, user, ":", hash_str, NL, NULL);
86     if (strlen(ctx->out) >= MAX_STRING_LEN) {
87         ctx->errstr = "resultant record too long";
88         return ERR_OVERFLOW;
89     }
90     return 0;
91 }
92
93 static void usage(void)
94 {
95     apr_file_printf(errfile, "Usage:" NL
96         "\thtpasswd [-cimBdpsDv] [-C cost] passwordfile username" NL
97         "\thtpasswd -b[cmBdpsDv] [-C cost] passwordfile username password" NL
98         NL
99         "\thtpasswd -n[imBdps] [-C cost] username" NL
100         "\thtpasswd -nb[mBdps] [-C cost] username password" NL
101         " -c  Create a new file." NL
102         " -n  Don't update file; display results on stdout." NL
103         " -b  Use the password from the command line rather than prompting "
104             "for it." NL
105         " -i  Read password from stdin without verification (for script usage)." NL
106         " -m  Force MD5 encryption of the password (default)." NL
107         " -B  Force bcrypt encryption of the password (very secure)." NL
108         " -C  Set the computing time used for the bcrypt algorithm" NL
109         "     (higher is more secure but slower, default: %d, valid: 4 to 31)." NL
110         " -d  Force CRYPT encryption of the password (8 chars max, insecure)." NL
111         " -s  Force SHA encryption of the password (insecure)." NL
112         " -p  Do not encrypt the password (plaintext, insecure)." NL
113         " -D  Delete the specified user." NL
114         " -v  Verify password for the specified user." NL
115         "On other systems than Windows and NetWare the '-p' flag will "
116             "probably not work." NL
117         "The SHA algorithm does not use a salt and is less secure than the "
118             "MD5 algorithm." NL,
119         BCRYPT_DEFAULT_COST
120     );
121     exit(ERR_SYNTAX);
122 }
123
124 /*
125  * Check to see if the specified file can be opened for the given
126  * access.
127  */
128 static int accessible(apr_pool_t *pool, char *fname, int mode)
129 {
130     apr_file_t *f = NULL;
131
132     if (apr_file_open(&f, fname, mode, APR_OS_DEFAULT, pool) != APR_SUCCESS) {
133         return 0;
134     }
135     apr_file_close(f);
136     return 1;
137 }
138
139 /*
140  * Return true if the named file exists, regardless of permissions.
141  */
142 static int exists(char *fname, apr_pool_t *pool)
143 {
144     apr_finfo_t sbuf;
145     apr_status_t check;
146
147     check = apr_stat(&sbuf, fname, APR_FINFO_TYPE, pool);
148     return ((check || sbuf.filetype != APR_REG) ? 0 : 1);
149 }
150
151 static void terminate(void)
152 {
153     apr_terminate();
154 #ifdef NETWARE
155     pressanykey();
156 #endif
157 }
158
159 static void check_args(int argc, const char *const argv[],
160                        struct passwd_ctx *ctx, unsigned *mask, char **user,
161                        char **pwfilename)
162 {
163     const char *arg;
164     int args_left = 2;
165     int i, ret;
166     apr_getopt_t *state;
167     apr_status_t rv;
168     char opt;
169     const char *opt_arg;
170     apr_pool_t *pool = ctx->pool;
171
172     rv = apr_getopt_init(&state, pool, argc, argv);
173     if (rv != APR_SUCCESS)
174         exit(ERR_SYNTAX);
175
176     while ((rv = apr_getopt(state, "cnmspdBbDiC:v", &opt, &opt_arg)) == APR_SUCCESS) {
177         switch (opt) {
178         case 'c':
179             *mask |= APHTP_NEWFILE;
180             break;
181         case 'n':
182             args_left--;
183             *mask |= APHTP_NOFILE;
184             break;
185         case 'D':
186             *mask |= APHTP_DELUSER;
187             break;
188         case 'v':
189             *mask |= APHTP_VERIFY;
190             break;
191         default:
192             ret = parse_common_options(ctx, opt, opt_arg);
193             if (ret) {
194                 apr_file_printf(errfile, "%s: %s" NL, argv[0], ctx->errstr);
195                 exit(ret);
196             }
197         }
198     }
199     if (ctx->passwd_src == PW_ARG)
200         args_left++;
201     if (rv != APR_EOF)
202         usage();
203
204     if ((*mask) & (*mask - 1)) {
205         /* not a power of two, i.e. more than one flag specified */
206         apr_file_printf(errfile, "%s: only one of -c -n -v -D may be specified" NL,
207             argv[0]);
208         exit(ERR_SYNTAX);
209     }
210     if ((*mask & APHTP_VERIFY) && ctx->passwd_src == PW_PROMPT)
211         ctx->passwd_src = PW_PROMPT_VERIFY;
212
213     /*
214      * Make sure we still have exactly the right number of arguments left
215      * (the filename, the username, and possibly the password if -b was
216      * specified).
217      */
218     i = state->ind;
219     if ((argc - i) != args_left) {
220         usage();
221     }
222
223     if (!(*mask & APHTP_NOFILE)) {
224         if (strlen(argv[i]) > (APR_PATH_MAX - 1)) {
225             apr_file_printf(errfile, "%s: filename too long" NL, argv[0]);
226             exit(ERR_OVERFLOW);
227         }
228         *pwfilename = apr_pstrdup(pool, argv[i++]);
229     }
230     if (strlen(argv[i]) > (MAX_STRING_LEN - 1)) {
231         apr_file_printf(errfile, "%s: username too long (> %d)" NL,
232                         argv[0], MAX_STRING_LEN - 1);
233         exit(ERR_OVERFLOW);
234     }
235     *user = apr_pstrdup(pool, argv[i++]);
236     if ((arg = strchr(*user, ':')) != NULL) {
237         apr_file_printf(errfile, "%s: username contains illegal "
238                         "character '%c'" NL, argv[0], *arg);
239         exit(ERR_BADUSER);
240     }
241     if (ctx->passwd_src == PW_ARG) {
242         if (strlen(argv[i]) > (MAX_STRING_LEN - 1)) {
243             apr_file_printf(errfile, "%s: password too long (> %d)" NL,
244                 argv[0], MAX_STRING_LEN);
245             exit(ERR_OVERFLOW);
246         }
247         ctx->passwd = apr_pstrdup(pool, argv[i]);
248     }
249 }
250
251 static int verify(struct passwd_ctx *ctx, const char *hash)
252 {
253     apr_status_t rv;
254     int ret;
255
256     if (ctx->passwd == NULL && (ret = get_password(ctx)) != 0)
257        return ret;
258     rv = apr_password_validate(ctx->passwd, hash);
259     if (rv == APR_SUCCESS)
260         return 0;
261     if (APR_STATUS_IS_EMISMATCH(rv)) {
262         ctx->errstr = "password verification failed";
263         return ERR_PWMISMATCH;
264     }
265     ctx->errstr = apr_psprintf(ctx->pool, "Could not verify password: %pm",
266                                &rv);
267     return ERR_GENERAL;
268 }
269
270 /*
271  * Let's do it.  We end up doing a lot of file opening and closing,
272  * but what do we care?  This application isn't run constantly.
273  */
274 int main(int argc, const char * const argv[])
275 {
276     apr_file_t *fpw = NULL;
277     char line[MAX_STRING_LEN];
278     char *pwfilename = NULL;
279     char *user = NULL;
280     char tn[] = "htpasswd.tmp.XXXXXX";
281     char *dirname;
282     char *scratch, cp[MAX_STRING_LEN];
283     int found = 0;
284     int i;
285     unsigned mask = 0;
286     apr_pool_t *pool;
287     int existing_file = 0;
288     struct passwd_ctx ctx = { 0 };
289 #if APR_CHARSET_EBCDIC
290     apr_status_t rv;
291     apr_xlate_t *to_ascii;
292 #endif
293
294     apr_app_initialize(&argc, &argv, NULL);
295     atexit(terminate);
296     apr_pool_create(&pool, NULL);
297     apr_pool_abort_set(abort_on_oom, pool);
298     apr_file_open_stderr(&errfile, pool);
299     ctx.pool = pool;
300     ctx.alg = ALG_APMD5;
301
302 #if APR_CHARSET_EBCDIC
303     rv = apr_xlate_open(&to_ascii, "ISO-8859-1", APR_DEFAULT_CHARSET, pool);
304     if (rv) {
305         apr_file_printf(errfile, "apr_xlate_open(to ASCII)->%d" NL, rv);
306         exit(1);
307     }
308     rv = apr_SHA1InitEBCDIC(to_ascii);
309     if (rv) {
310         apr_file_printf(errfile, "apr_SHA1InitEBCDIC()->%d" NL, rv);
311         exit(1);
312     }
313     rv = apr_MD5InitEBCDIC(to_ascii);
314     if (rv) {
315         apr_file_printf(errfile, "apr_MD5InitEBCDIC()->%d" NL, rv);
316         exit(1);
317     }
318 #endif /*APR_CHARSET_EBCDIC*/
319
320     check_args(argc, argv, &ctx, &mask, &user, &pwfilename);
321
322     /*
323      * Only do the file checks if we're supposed to frob it.
324      */
325     if (!(mask & APHTP_NOFILE)) {
326         existing_file = exists(pwfilename, pool);
327         if (existing_file) {
328             /*
329              * Check that this existing file is readable and writable.
330              */
331             if (!accessible(pool, pwfilename, APR_FOPEN_READ|APR_FOPEN_WRITE)) {
332                 apr_file_printf(errfile, "%s: cannot open file %s for "
333                                 "read/write access" NL, argv[0], pwfilename);
334                 exit(ERR_FILEPERM);
335             }
336         }
337         else {
338             /*
339              * Error out if -c was omitted for this non-existant file.
340              */
341             if (!(mask & APHTP_NEWFILE)) {
342                 apr_file_printf(errfile,
343                         "%s: cannot modify file %s; use '-c' to create it" NL,
344                         argv[0], pwfilename);
345                 exit(ERR_FILEPERM);
346             }
347             /*
348              * As it doesn't exist yet, verify that we can create it.
349              */
350             if (!accessible(pool, pwfilename, APR_FOPEN_WRITE|APR_FOPEN_CREATE)) {
351                 apr_file_printf(errfile, "%s: cannot create file %s" NL,
352                                 argv[0], pwfilename);
353                 exit(ERR_FILEPERM);
354             }
355         }
356     }
357
358     /*
359      * All the file access checks (if any) have been made.  Time to go to work;
360      * try to create the record for the username in question.  If that
361      * fails, there's no need to waste any time on file manipulations.
362      * Any error message text is returned in the record buffer, since
363      * the mkrecord() routine doesn't have access to argv[].
364      */
365     if ((mask & (APHTP_DELUSER|APHTP_VERIFY)) == 0) {
366         i = mkrecord(&ctx, user);
367         if (i != 0) {
368             apr_file_printf(errfile, "%s: %s" NL, argv[0], ctx.errstr);
369             exit(i);
370         }
371         if (mask & APHTP_NOFILE) {
372             printf("%s" NL, ctx.out);
373             exit(0);
374         }
375     }
376
377     if ((mask & APHTP_VERIFY) == 0) {
378         /*
379          * We can access the files the right way, and we have a record
380          * to add or update.  Let's do it..
381          */
382         if (apr_temp_dir_get((const char**)&dirname, pool) != APR_SUCCESS) {
383             apr_file_printf(errfile, "%s: could not determine temp dir" NL,
384                             argv[0]);
385             exit(ERR_FILEPERM);
386         }
387         dirname = apr_psprintf(pool, "%s/%s", dirname, tn);
388
389         if (apr_file_mktemp(&ftemp, dirname, 0, pool) != APR_SUCCESS) {
390             apr_file_printf(errfile, "%s: unable to create temporary file %s" NL,
391                             argv[0], dirname);
392             exit(ERR_FILEPERM);
393         }
394     }
395
396     /*
397      * If we're not creating a new file, copy records from the existing
398      * one to the temporary file until we find the specified user.
399      */
400     if (existing_file && !(mask & APHTP_NEWFILE)) {
401         if (apr_file_open(&fpw, pwfilename, APR_READ | APR_BUFFERED,
402                           APR_OS_DEFAULT, pool) != APR_SUCCESS) {
403             apr_file_printf(errfile, "%s: unable to read file %s" NL,
404                             argv[0], pwfilename);
405             exit(ERR_FILEPERM);
406         }
407         while (apr_file_gets(line, sizeof(line), fpw) == APR_SUCCESS) {
408             char *colon;
409
410             strcpy(cp, line);
411             scratch = cp;
412             while (apr_isspace(*scratch)) {
413                 ++scratch;
414             }
415
416             if (!*scratch || (*scratch == '#')) {
417                 putline(ftemp, line);
418                 continue;
419             }
420             /*
421              * See if this is our user.
422              */
423             colon = strchr(scratch, ':');
424             if (colon != NULL) {
425                 *colon = '\0';
426             }
427             else {
428                 /*
429                  * If we've not got a colon on the line, this could well
430                  * not be a valid htpasswd file.
431                  * We should bail at this point.
432                  */
433                 apr_file_printf(errfile, "%s: The file %s does not appear "
434                                          "to be a valid htpasswd file." NL,
435                                 argv[0], pwfilename);
436                 apr_file_close(fpw);
437                 exit(ERR_INVALID);
438             }
439             if (strcmp(user, scratch) != 0) {
440                 putline(ftemp, line);
441                 continue;
442             }
443             else {
444                 /* We found the user we were looking for */
445                 found++;
446                 if ((mask & APHTP_DELUSER)) {
447                     /* Delete entry from the file */
448                     apr_file_printf(errfile, "Deleting ");
449                 }
450                 else if ((mask & APHTP_VERIFY)) {
451                     /* Verify */
452                     char *hash = colon + 1;
453                     size_t len;
454
455                     len = strcspn(hash, "\r\n");
456                     if (len == 0) {
457                         apr_file_printf(errfile, "Empty hash for user %s" NL,
458                                         user);
459                         exit(ERR_INVALID);
460                     }
461                     hash[len] = '\0';
462
463                     i = verify(&ctx, hash);
464                     if (i != 0) {
465                         apr_file_printf(errfile, "%s" NL, ctx.errstr);
466                         exit(i);
467                     }
468                 }
469                 else {
470                     /* Update entry */
471                     apr_file_printf(errfile, "Updating ");
472                     putline(ftemp, ctx.out);
473                 }
474             }
475         }
476         apr_file_close(fpw);
477     }
478     if (!found) {
479         if (mask & APHTP_DELUSER) {
480             apr_file_printf(errfile, "User %s not found" NL, user);
481             exit(0);
482         }
483         else if (mask & APHTP_VERIFY) {
484             apr_file_printf(errfile, "User %s not found" NL, user);
485             exit(ERR_BADUSER);
486         }
487         else {
488             apr_file_printf(errfile, "Adding ");
489             putline(ftemp, ctx.out);
490         }
491     }
492     if (mask & APHTP_VERIFY) {
493         apr_file_printf(errfile, "Password for user %s correct." NL, user);
494         exit(0);
495     }
496
497     apr_file_printf(errfile, "password for user %s" NL, user);
498
499     /* The temporary file has all the data, just copy it to the new location.
500      */
501     if (apr_file_copy(dirname, pwfilename, APR_FILE_SOURCE_PERMS, pool) !=
502         APR_SUCCESS) {
503         apr_file_printf(errfile, "%s: unable to update file %s" NL,
504                         argv[0], pwfilename);
505         exit(ERR_FILEPERM);
506     }
507     apr_file_close(ftemp);
508     return 0;
509 }