]> granicus.if.org Git - apache/blob - support/htpasswd.c
Fix compile time warning. AJP protocol method is byte.
[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 "apr.h"
42 #include "apr_lib.h"
43 #include "apr_strings.h"
44 #include "apr_errno.h"
45 #include "apr_file_io.h"
46 #include "apr_general.h"
47 #include "apr_signal.h"
48
49 #if APR_HAVE_STDIO_H
50 #include <stdio.h>
51 #endif
52
53 #include "apr_md5.h"
54 #include "apr_sha1.h"
55 #include <time.h>
56
57 #if APR_HAVE_CRYPT_H
58 #include <crypt.h>
59 #endif
60 #if APR_HAVE_STDLIB_H
61 #include <stdlib.h>
62 #endif
63 #if APR_HAVE_STRING_H
64 #include <string.h>
65 #endif
66 #if APR_HAVE_UNISTD_H
67 #include <unistd.h>
68 #endif
69
70 #ifdef WIN32
71 #include <conio.h>
72 #define unlink _unlink
73 #endif
74
75 #if !APR_CHARSET_EBCDIC
76 #define LF 10
77 #define CR 13
78 #else /*APR_CHARSET_EBCDIC*/
79 #define LF '\n'
80 #define CR '\r'
81 #endif /*APR_CHARSET_EBCDIC*/
82
83 #define MAX_STRING_LEN 256
84 #define ALG_PLAIN 0
85 #define ALG_CRYPT 1
86 #define ALG_APMD5 2
87 #define ALG_APSHA 3
88
89 #define ERR_FILEPERM 1
90 #define ERR_SYNTAX 2
91 #define ERR_PWMISMATCH 3
92 #define ERR_INTERRUPTED 4
93 #define ERR_OVERFLOW 5
94 #define ERR_BADUSER 6
95 #define ERR_INVALID 7
96
97 #define APHTP_NEWFILE        1
98 #define APHTP_NOFILE         2
99 #define APHTP_NONINTERACTIVE 4
100 #define APHTP_DELUSER        8
101
102 apr_file_t *errfile;
103 apr_file_t *ftemp = NULL;
104
105 #define NL APR_EOL_STR
106
107 static void to64(char *s, unsigned long v, int n)
108 {
109     static unsigned char itoa64[] =         /* 0 ... 63 => ASCII - 64 */
110         "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
111
112     while (--n >= 0) {
113         *s++ = itoa64[v&0x3f];
114         v >>= 6;
115     }
116 }
117
118 static void putline(apr_file_t *f, const char *l)
119 {
120     apr_file_puts(l, f);
121 }
122
123 /*
124  * Make a password record from the given information.  A zero return
125  * indicates success; failure means that the output buffer contains an
126  * error message instead.
127  */
128 static int mkrecord(char *user, char *record, apr_size_t rlen, char *passwd,
129                     int alg)
130 {
131     char *pw;
132     char cpw[120];
133     char pwin[MAX_STRING_LEN];
134     char pwv[MAX_STRING_LEN];
135     char salt[9];
136     apr_size_t bufsize;
137
138     if (passwd != NULL) {
139         pw = passwd;
140     }
141     else {
142         bufsize = sizeof(pwin);
143         if (apr_password_get("New password: ", pwin, &bufsize) != 0) {
144             apr_snprintf(record, (rlen - 1), "password too long (>%"
145                          APR_SIZE_T_FMT ")", sizeof(pwin) - 1);
146             return ERR_OVERFLOW;
147         }
148         bufsize = sizeof(pwv);
149         apr_password_get("Re-type new password: ", pwv, &bufsize);
150         if (strcmp(pwin, pwv) != 0) {
151             apr_cpystrn(record, "password verification error", (rlen - 1));
152             return ERR_PWMISMATCH;
153         }
154         pw = pwin;
155         memset(pwv, '\0', sizeof(pwin));
156     }
157     switch (alg) {
158
159     case ALG_APSHA:
160         /* XXX cpw >= 28 + strlen(sha1) chars - fixed len SHA */
161         apr_sha1_base64(pw,strlen(pw),cpw);
162         break;
163
164     case ALG_APMD5:
165         (void) srand((int) time((time_t *) NULL));
166         to64(&salt[0], rand(), 8);
167         salt[8] = '\0';
168
169         apr_md5_encode((const char *)pw, (const char *)salt,
170                      cpw, sizeof(cpw));
171         break;
172
173     case ALG_PLAIN:
174         /* XXX this len limitation is not in sync with any HTTPd len. */
175         apr_cpystrn(cpw,pw,sizeof(cpw));
176         break;
177
178 #if !(defined(WIN32) || defined(NETWARE))
179     case ALG_CRYPT:
180     default:
181         (void) srand((int) time((time_t *) NULL));
182         to64(&salt[0], rand(), 8);
183         salt[8] = '\0';
184
185         apr_cpystrn(cpw, crypt(pw, salt), sizeof(cpw) - 1);
186         break;
187 #endif
188     }
189     memset(pw, '\0', strlen(pw));
190
191     /*
192      * Check to see if the buffer is large enough to hold the username,
193      * hash, and delimiters.
194      */
195     if ((strlen(user) + 1 + strlen(cpw)) > (rlen - 1)) {
196         apr_cpystrn(record, "resultant record too long", (rlen - 1));
197         return ERR_OVERFLOW;
198     }
199     strcpy(record, user);
200     strcat(record, ":");
201     strcat(record, cpw);
202     strcat(record, "\n");
203     return 0;
204 }
205
206 static void usage(void)
207 {
208     apr_file_printf(errfile, "Usage:" NL);
209     apr_file_printf(errfile, "\thtpasswd [-cmdpsD] passwordfile username" NL);
210     apr_file_printf(errfile, "\thtpasswd -b[cmdpsD] passwordfile username "
211                     "password" NL NL);
212     apr_file_printf(errfile, "\thtpasswd -n[mdps] username" NL);
213     apr_file_printf(errfile, "\thtpasswd -nb[mdps] username password" NL);
214     apr_file_printf(errfile, " -c  Create a new file." NL);
215     apr_file_printf(errfile, " -n  Don't update file; display results on "
216                     "stdout." NL);
217     apr_file_printf(errfile, " -m  Force MD5 encryption of the password"
218 #if defined(WIN32) || defined(TPF) || defined(NETWARE)
219         " (default)"
220 #endif
221         "." NL);
222     apr_file_printf(errfile, " -d  Force CRYPT encryption of the password"
223 #if (!(defined(WIN32) || defined(TPF) || defined(NETWARE)))
224             " (default)"
225 #endif
226             "." NL);
227     apr_file_printf(errfile, " -p  Do not encrypt the password (plaintext)." NL);
228     apr_file_printf(errfile, " -s  Force SHA encryption of the password." NL);
229     apr_file_printf(errfile, " -b  Use the password from the command line "
230             "rather than prompting for it." NL);
231     apr_file_printf(errfile, " -D  Delete the specified user." NL);
232     apr_file_printf(errfile,
233             "On Windows, NetWare and TPF systems the '-m' flag is used by "
234             "default." NL);
235     apr_file_printf(errfile,
236             "On all other systems, the '-p' flag will probably not work." NL);
237     exit(ERR_SYNTAX);
238 }
239
240 /*
241  * Check to see if the specified file can be opened for the given
242  * access.
243  */
244 static int accessible(apr_pool_t *pool, char *fname, int mode)
245 {
246     apr_file_t *f = NULL;
247
248     if (apr_file_open(&f, fname, mode, APR_OS_DEFAULT, pool) != APR_SUCCESS) {
249         return 0;
250     }
251     apr_file_close(f);
252     return 1;
253 }
254
255 /*
256  * Return true if the named file exists, regardless of permissions.
257  */
258 static int exists(char *fname, apr_pool_t *pool)
259 {
260     apr_finfo_t sbuf;
261     apr_status_t check;
262
263     check = apr_stat(&sbuf, fname, APR_FINFO_TYPE, pool);
264     return ((check || sbuf.filetype != APR_REG) ? 0 : 1);
265 }
266
267 static void terminate(void)
268 {
269     apr_terminate();
270 #ifdef NETWARE
271     pressanykey();
272 #endif
273 }
274
275 static void check_args(apr_pool_t *pool, int argc, const char *const argv[],
276                        int *alg, int *mask, char **user, char **pwfilename,
277                        char **password)
278 {
279     const char *arg;
280     int args_left = 2;
281     int i;
282
283     /*
284      * Preliminary check to make sure they provided at least
285      * three arguments, we'll do better argument checking as
286      * we parse the command line.
287      */
288     if (argc < 3) {
289         usage();
290     }
291
292     /*
293      * Go through the argument list and pick out any options.  They
294      * have to precede any other arguments.
295      */
296     for (i = 1; i < argc; i++) {
297         arg = argv[i];
298         if (*arg != '-') {
299             break;
300         }
301         while (*++arg != '\0') {
302             if (*arg == 'c') {
303                 *mask |= APHTP_NEWFILE;
304             }
305             else if (*arg == 'n') {
306                 *mask |= APHTP_NOFILE;
307                 args_left--;
308             }
309             else if (*arg == 'm') {
310                 *alg = ALG_APMD5;
311             }
312             else if (*arg == 's') {
313                 *alg = ALG_APSHA;
314             }
315             else if (*arg == 'p') {
316                 *alg = ALG_PLAIN;
317             }
318             else if (*arg == 'd') {
319                 *alg = ALG_CRYPT;
320             }
321             else if (*arg == 'b') {
322                 *mask |= APHTP_NONINTERACTIVE;
323                 args_left++;
324             }
325             else if (*arg == 'D') {
326                 *mask |= APHTP_DELUSER;
327             }
328             else {
329                 usage();
330             }
331         }
332     }
333
334     if ((*mask & APHTP_NEWFILE) && (*mask & APHTP_NOFILE)) {
335         apr_file_printf(errfile, "%s: -c and -n options conflict" NL, argv[0]);
336         exit(ERR_SYNTAX);
337     }
338     if ((*mask & APHTP_NEWFILE) && (*mask & APHTP_DELUSER)) {
339         apr_file_printf(errfile, "%s: -c and -D options conflict" NL, argv[0]);
340         exit(ERR_SYNTAX);
341     }
342     if ((*mask & APHTP_NOFILE) && (*mask & APHTP_DELUSER)) {
343         apr_file_printf(errfile, "%s: -n and -D options conflict" NL, argv[0]);
344         exit(ERR_SYNTAX);
345     }
346     /*
347      * Make sure we still have exactly the right number of arguments left
348      * (the filename, the username, and possibly the password if -b was
349      * specified).
350      */
351     if ((argc - i) != args_left) {
352         usage();
353     }
354
355     if (*mask & APHTP_NOFILE) {
356         i--;
357     }
358     else {
359         if (strlen(argv[i]) > (APR_PATH_MAX - 1)) {
360             apr_file_printf(errfile, "%s: filename too long" NL, argv[0]);
361             exit(ERR_OVERFLOW);
362         }
363         *pwfilename = apr_pstrdup(pool, argv[i]);
364         if (strlen(argv[i + 1]) > (MAX_STRING_LEN - 1)) {
365             apr_file_printf(errfile, "%s: username too long (> %d)" NL,
366                 argv[0], MAX_STRING_LEN - 1);
367             exit(ERR_OVERFLOW);
368         }
369     }
370     *user = apr_pstrdup(pool, argv[i + 1]);
371     if ((arg = strchr(*user, ':')) != NULL) {
372         apr_file_printf(errfile, "%s: username contains illegal "
373                         "character '%c'" NL, argv[0], *arg);
374         exit(ERR_BADUSER);
375     }
376     if (*mask & APHTP_NONINTERACTIVE) {
377         if (strlen(argv[i + 2]) > (MAX_STRING_LEN - 1)) {
378             apr_file_printf(errfile, "%s: password too long (> %d)" NL,
379                 argv[0], MAX_STRING_LEN);
380             exit(ERR_OVERFLOW);
381         }
382         *password = apr_pstrdup(pool, argv[i + 2]);
383     }
384 }
385
386 /*
387  * Let's do it.  We end up doing a lot of file opening and closing,
388  * but what do we care?  This application isn't run constantly.
389  */
390 int main(int argc, const char * const argv[])
391 {
392     apr_file_t *fpw = NULL;
393     char record[MAX_STRING_LEN];
394     char line[MAX_STRING_LEN];
395     char *password = NULL;
396     char *pwfilename = NULL;
397     char *user = NULL;
398     char tn[] = "htpasswd.tmp.XXXXXX";
399     char *dirname;
400     char *scratch, cp[MAX_STRING_LEN];
401     int found = 0;
402     int i;
403     int alg = ALG_CRYPT;
404     int mask = 0;
405     apr_pool_t *pool;
406     int existing_file = 0;
407 #if APR_CHARSET_EBCDIC
408     apr_status_t rv;
409     apr_xlate_t *to_ascii;
410 #endif
411
412     apr_app_initialize(&argc, &argv, NULL);
413     atexit(terminate);
414     apr_pool_create(&pool, NULL);
415     apr_file_open_stderr(&errfile, pool);
416
417 #if APR_CHARSET_EBCDIC
418     rv = apr_xlate_open(&to_ascii, "ISO-8859-1", APR_DEFAULT_CHARSET, pool);
419     if (rv) {
420         apr_file_printf(errfile, "apr_xlate_open(to ASCII)->%d" NL, rv);
421         exit(1);
422     }
423     rv = apr_SHA1InitEBCDIC(to_ascii);
424     if (rv) {
425         apr_file_printf(errfile, "apr_SHA1InitEBCDIC()->%d" NL, rv);
426         exit(1);
427     }
428     rv = apr_MD5InitEBCDIC(to_ascii);
429     if (rv) {
430         apr_file_printf(errfile, "apr_MD5InitEBCDIC()->%d" NL, rv);
431         exit(1);
432     }
433 #endif /*APR_CHARSET_EBCDIC*/
434
435     check_args(pool, argc, argv, &alg, &mask, &user, &pwfilename, &password);
436
437
438 #if defined(WIN32) || defined(NETWARE)
439     if (alg == ALG_CRYPT) {
440         alg = ALG_APMD5;
441         apr_file_printf(errfile, "Automatically using MD5 format." NL);
442     }
443 #endif
444
445 #if (!(defined(WIN32) || defined(TPF) || defined(NETWARE)))
446     if (alg == ALG_PLAIN) {
447         apr_file_printf(errfile,"Warning: storing passwords as plain text "
448                         "might just not work on this platform." NL);
449     }
450 #endif
451
452     /*
453      * Only do the file checks if we're supposed to frob it.
454      */
455     if (!(mask & APHTP_NOFILE)) {
456         existing_file = exists(pwfilename, pool);
457         if (existing_file) {
458             /*
459              * Check that this existing file is readable and writable.
460              */
461             if (!accessible(pool, pwfilename, APR_READ | APR_APPEND)) {
462                 apr_file_printf(errfile, "%s: cannot open file %s for "
463                                 "read/write access" NL, argv[0], pwfilename);
464                 exit(ERR_FILEPERM);
465             }
466         }
467         else {
468             /*
469              * Error out if -c was omitted for this non-existant file.
470              */
471             if (!(mask & APHTP_NEWFILE)) {
472                 apr_file_printf(errfile,
473                         "%s: cannot modify file %s; use '-c' to create it" NL,
474                         argv[0], pwfilename);
475                 exit(ERR_FILEPERM);
476             }
477             /*
478              * As it doesn't exist yet, verify that we can create it.
479              */
480             if (!accessible(pool, pwfilename, APR_CREATE | APR_WRITE)) {
481                 apr_file_printf(errfile, "%s: cannot create file %s" NL,
482                                 argv[0], pwfilename);
483                 exit(ERR_FILEPERM);
484             }
485         }
486     }
487
488     /*
489      * All the file access checks (if any) have been made.  Time to go to work;
490      * try to create the record for the username in question.  If that
491      * fails, there's no need to waste any time on file manipulations.
492      * Any error message text is returned in the record buffer, since
493      * the mkrecord() routine doesn't have access to argv[].
494      */
495     if (!(mask & APHTP_DELUSER)) {
496         i = mkrecord(user, record, sizeof(record) - 1,
497                      password, alg);
498         if (i != 0) {
499             apr_file_printf(errfile, "%s: %s" NL, argv[0], record);
500             exit(i);
501         }
502         if (mask & APHTP_NOFILE) {
503             printf("%s" NL, record);
504             exit(0);
505         }
506     }
507
508     /*
509      * We can access the files the right way, and we have a record
510      * to add or update.  Let's do it..
511      */
512     if (apr_temp_dir_get((const char**)&dirname, pool) != APR_SUCCESS) {
513         apr_file_printf(errfile, "%s: could not determine temp dir" NL,
514                         argv[0]);
515         exit(ERR_FILEPERM);
516     }
517     dirname = apr_psprintf(pool, "%s/%s", dirname, tn);
518
519     if (apr_file_mktemp(&ftemp, dirname, 0, pool) != APR_SUCCESS) {
520         apr_file_printf(errfile, "%s: unable to create temporary file %s" NL,
521                         argv[0], dirname);
522         exit(ERR_FILEPERM);
523     }
524
525     /*
526      * If we're not creating a new file, copy records from the existing
527      * one to the temporary file until we find the specified user.
528      */
529     if (existing_file && !(mask & APHTP_NEWFILE)) {
530         if (apr_file_open(&fpw, pwfilename, APR_READ | APR_BUFFERED,
531                           APR_OS_DEFAULT, pool) != APR_SUCCESS) {
532             apr_file_printf(errfile, "%s: unable to read file %s" NL,
533                             argv[0], pwfilename);
534             exit(ERR_FILEPERM);
535         }
536         while (apr_file_gets(line, sizeof(line), fpw) == APR_SUCCESS) {
537             char *colon;
538
539             strcpy(cp, line);
540             scratch = cp;
541             while (apr_isspace(*scratch)) {
542                 ++scratch;
543             }
544
545             if (!*scratch || (*scratch == '#')) {
546                 putline(ftemp, line);
547                 continue;
548             }
549             /*
550              * See if this is our user.
551              */
552             colon = strchr(scratch, ':');
553             if (colon != NULL) {
554                 *colon = '\0';
555             }
556             else {
557                 /*
558                  * If we've not got a colon on the line, this could well
559                  * not be a valid htpasswd file.
560                  * We should bail at this point.
561                  */
562                 apr_file_printf(errfile, "%s: The file %s does not appear "
563                                          "to be a valid htpasswd file." NL,
564                                 argv[0], pwfilename);
565                 apr_file_close(fpw);
566                 exit(ERR_INVALID);
567             }
568             if (strcmp(user, scratch) != 0) {
569                 putline(ftemp, line);
570                 continue;
571             }
572             else {
573                 if (!(mask & APHTP_DELUSER)) {
574                     /* We found the user we were looking for.
575                      * Add him to the file.
576                     */
577                     apr_file_printf(errfile, "Updating ");
578                     putline(ftemp, record);
579                     found++;
580                 }
581                 else {
582                     /* We found the user we were looking for.
583                      * Delete them from the file.
584                      */
585                     apr_file_printf(errfile, "Deleting ");
586                     found++;
587                 }
588             }
589         }
590         apr_file_close(fpw);
591     }
592     if (!found && !(mask & APHTP_DELUSER)) {
593         apr_file_printf(errfile, "Adding ");
594         putline(ftemp, record);
595     }
596     else if (!found && (mask & APHTP_DELUSER)) {
597         apr_file_printf(errfile, "User %s not found" NL, user);
598         exit(0);
599     }
600     apr_file_printf(errfile, "password for user %s" NL, user);
601
602     /* The temporary file has all the data, just copy it to the new location.
603      */
604     if (apr_file_copy(dirname, pwfilename, APR_FILE_SOURCE_PERMS, pool) !=
605         APR_SUCCESS) {
606         apr_file_printf(errfile, "%s: unable to update file %s" NL,
607                         argv[0], pwfilename);
608         exit(ERR_FILEPERM);
609     }
610     apr_file_close(ftemp);
611     return 0;
612 }