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