]> granicus.if.org Git - apache/blob - support/htpasswd.c
htdbm, htpasswd: print error message if out of memory
[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
71 apr_file_t *ftemp = NULL;
72
73 static int mkrecord(struct passwd_ctx *ctx, char *user)
74 {
75     char hash_str[MAX_STRING_LEN];
76     int ret;
77     ctx->out = hash_str;
78     ctx->out_len = sizeof(hash_str);
79
80     ret = mkhash(ctx);
81     if (ret)
82         return ret;
83
84     ctx->out = apr_pstrcat(ctx->pool, user, ":", hash_str, NL, NULL);
85     if (strlen(ctx->out) >= MAX_STRING_LEN) {
86         ctx->errstr = "resultant record too long";
87         return ERR_OVERFLOW;
88     }
89     return 0;
90 }
91
92 static void usage(void)
93 {
94     apr_file_printf(errfile, "Usage:" NL
95         "\thtpasswd [-cimBdpsD] [-C cost] passwordfile username" NL
96         "\thtpasswd -b[cmBdpsD] [-C cost] passwordfile username password" NL
97         NL
98         "\thtpasswd -n[imBdps] [-C cost] username" NL
99         "\thtpasswd -nb[mBdps] [-C cost] username password" NL
100         " -c  Create a new file." NL
101         " -n  Don't update file; display results on stdout." NL
102         " -b  Use the password from the command line rather than prompting "
103             "for it." NL
104         " -i  Read password from stdin without verification (for script usage)." NL
105         " -m  Force MD5 encryption of the password (default)." NL
106         " -B  Force bcrypt encryption of the password (very secure)." NL
107         " -C  Set the computing time used for the bcrypt algorithm" NL
108         "     (higher is more secure but slower, default: %d, valid: 4 to 31)." NL
109         " -d  Force CRYPT encryption of the password (8 chars max, insecure)." NL
110         " -s  Force SHA encryption of the password (insecure)." NL
111         " -p  Do not encrypt the password (plaintext, insecure)." NL
112         " -D  Delete the specified user." NL
113         "On other systems than Windows and NetWare the '-p' flag will "
114             "probably not work." NL
115         "The SHA algorithm does not use a salt and is less secure than the "
116             "MD5 algorithm." NL,
117         BCRYPT_DEFAULT_COST
118     );
119     exit(ERR_SYNTAX);
120 }
121
122 /*
123  * Check to see if the specified file can be opened for the given
124  * access.
125  */
126 static int accessible(apr_pool_t *pool, char *fname, int mode)
127 {
128     apr_file_t *f = NULL;
129
130     if (apr_file_open(&f, fname, mode, APR_OS_DEFAULT, pool) != APR_SUCCESS) {
131         return 0;
132     }
133     apr_file_close(f);
134     return 1;
135 }
136
137 /*
138  * Return true if the named file exists, regardless of permissions.
139  */
140 static int exists(char *fname, apr_pool_t *pool)
141 {
142     apr_finfo_t sbuf;
143     apr_status_t check;
144
145     check = apr_stat(&sbuf, fname, APR_FINFO_TYPE, pool);
146     return ((check || sbuf.filetype != APR_REG) ? 0 : 1);
147 }
148
149 static void terminate(void)
150 {
151     apr_terminate();
152 #ifdef NETWARE
153     pressanykey();
154 #endif
155 }
156
157 static void check_args(int argc, const char *const argv[],
158                        struct passwd_ctx *ctx, int *mask, char **user,
159                        char **pwfilename)
160 {
161     const char *arg;
162     int args_left = 2;
163     int i, ret;
164     apr_getopt_t *state;
165     apr_status_t rv;
166     char opt;
167     const char *opt_arg;
168     apr_pool_t *pool = ctx->pool;
169
170     rv = apr_getopt_init(&state, pool, argc, argv);
171     if (rv != APR_SUCCESS)
172         exit(ERR_SYNTAX);
173
174     while ((rv = apr_getopt(state, "cnmspdBbDiC:", &opt, &opt_arg)) == APR_SUCCESS) {
175         switch (opt) {
176         case 'c':
177             *mask |= APHTP_NEWFILE;
178             break;
179         case 'n':
180             args_left--;
181             *mask |= APHTP_NOFILE;
182             break;
183         case 'D':
184             *mask |= APHTP_DELUSER;
185             break;
186         default:
187             ret = parse_common_options(ctx, opt, opt_arg);
188             if (ret) {
189                 apr_file_printf(errfile, "%s: %s" NL, argv[0], ctx->errstr);
190                 exit(ret);
191             }
192         }
193     }
194     if (ctx->passwd_src == PW_ARG)
195         args_left++;
196     if (rv != APR_EOF)
197         usage();
198
199     if ((*mask & APHTP_NEWFILE) && (*mask & APHTP_NOFILE)) {
200         apr_file_printf(errfile, "%s: -c and -n options conflict" NL, argv[0]);
201         exit(ERR_SYNTAX);
202     }
203     if ((*mask & APHTP_NEWFILE) && (*mask & APHTP_DELUSER)) {
204         apr_file_printf(errfile, "%s: -c and -D options conflict" NL, argv[0]);
205         exit(ERR_SYNTAX);
206     }
207     if ((*mask & APHTP_NOFILE) && (*mask & APHTP_DELUSER)) {
208         apr_file_printf(errfile, "%s: -n and -D options conflict" NL, argv[0]);
209         exit(ERR_SYNTAX);
210     }
211     /*
212      * Make sure we still have exactly the right number of arguments left
213      * (the filename, the username, and possibly the password if -b was
214      * specified).
215      */
216     i = state->ind;
217     if ((argc - i) != args_left) {
218         usage();
219     }
220
221     if (!(*mask & APHTP_NOFILE)) {
222         if (strlen(argv[i]) > (APR_PATH_MAX - 1)) {
223             apr_file_printf(errfile, "%s: filename too long" NL, argv[0]);
224             exit(ERR_OVERFLOW);
225         }
226         *pwfilename = apr_pstrdup(pool, argv[i++]);
227     }
228     if (strlen(argv[i]) > (MAX_STRING_LEN - 1)) {
229         apr_file_printf(errfile, "%s: username too long (> %d)" NL,
230                         argv[0], MAX_STRING_LEN - 1);
231         exit(ERR_OVERFLOW);
232     }
233     *user = apr_pstrdup(pool, argv[i++]);
234     if ((arg = strchr(*user, ':')) != NULL) {
235         apr_file_printf(errfile, "%s: username contains illegal "
236                         "character '%c'" NL, argv[0], *arg);
237         exit(ERR_BADUSER);
238     }
239     if (ctx->passwd_src == PW_ARG) {
240         if (strlen(argv[i]) > (MAX_STRING_LEN - 1)) {
241             apr_file_printf(errfile, "%s: password too long (> %d)" NL,
242                 argv[0], MAX_STRING_LEN);
243             exit(ERR_OVERFLOW);
244         }
245         ctx->passwd = apr_pstrdup(pool, argv[i]);
246     }
247 }
248
249 /*
250  * Let's do it.  We end up doing a lot of file opening and closing,
251  * but what do we care?  This application isn't run constantly.
252  */
253 int main(int argc, const char * const argv[])
254 {
255     apr_file_t *fpw = NULL;
256     const char *errstr = NULL;
257     char line[MAX_STRING_LEN];
258     char *pwfilename = NULL;
259     char *user = NULL;
260     char tn[] = "htpasswd.tmp.XXXXXX";
261     char *dirname;
262     char *scratch, cp[MAX_STRING_LEN];
263     int found = 0;
264     int i;
265     int mask = 0;
266     apr_pool_t *pool;
267     int existing_file = 0;
268     struct passwd_ctx ctx = { 0 };
269 #if APR_CHARSET_EBCDIC
270     apr_status_t rv;
271     apr_xlate_t *to_ascii;
272 #endif
273
274     apr_app_initialize(&argc, &argv, NULL);
275     atexit(terminate);
276     apr_pool_create(&pool, NULL);
277     apr_pool_abort_set(abort_on_oom, pool);
278     apr_file_open_stderr(&errfile, pool);
279     ctx.pool = pool;
280     ctx.alg = ALG_APMD5;
281
282 #if APR_CHARSET_EBCDIC
283     rv = apr_xlate_open(&to_ascii, "ISO-8859-1", APR_DEFAULT_CHARSET, pool);
284     if (rv) {
285         apr_file_printf(errfile, "apr_xlate_open(to ASCII)->%d" NL, rv);
286         exit(1);
287     }
288     rv = apr_SHA1InitEBCDIC(to_ascii);
289     if (rv) {
290         apr_file_printf(errfile, "apr_SHA1InitEBCDIC()->%d" NL, rv);
291         exit(1);
292     }
293     rv = apr_MD5InitEBCDIC(to_ascii);
294     if (rv) {
295         apr_file_printf(errfile, "apr_MD5InitEBCDIC()->%d" NL, rv);
296         exit(1);
297     }
298 #endif /*APR_CHARSET_EBCDIC*/
299
300     check_args(argc, argv, &ctx, &mask, &user, &pwfilename);
301
302     /*
303      * Only do the file checks if we're supposed to frob it.
304      */
305     if (!(mask & APHTP_NOFILE)) {
306         existing_file = exists(pwfilename, pool);
307         if (existing_file) {
308             /*
309              * Check that this existing file is readable and writable.
310              */
311             if (!accessible(pool, pwfilename, APR_FOPEN_READ|APR_FOPEN_WRITE)) {
312                 apr_file_printf(errfile, "%s: cannot open file %s for "
313                                 "read/write access" NL, argv[0], pwfilename);
314                 exit(ERR_FILEPERM);
315             }
316         }
317         else {
318             /*
319              * Error out if -c was omitted for this non-existant file.
320              */
321             if (!(mask & APHTP_NEWFILE)) {
322                 apr_file_printf(errfile,
323                         "%s: cannot modify file %s; use '-c' to create it" NL,
324                         argv[0], pwfilename);
325                 exit(ERR_FILEPERM);
326             }
327             /*
328              * As it doesn't exist yet, verify that we can create it.
329              */
330             if (!accessible(pool, pwfilename, APR_FOPEN_WRITE|APR_FOPEN_CREATE)) {
331                 apr_file_printf(errfile, "%s: cannot create file %s" NL,
332                                 argv[0], pwfilename);
333                 exit(ERR_FILEPERM);
334             }
335         }
336     }
337
338     /*
339      * All the file access checks (if any) have been made.  Time to go to work;
340      * try to create the record for the username in question.  If that
341      * fails, there's no need to waste any time on file manipulations.
342      * Any error message text is returned in the record buffer, since
343      * the mkrecord() routine doesn't have access to argv[].
344      */
345     if (!(mask & APHTP_DELUSER)) {
346         i = mkrecord(&ctx, user);
347         if (i != 0) {
348             apr_file_printf(errfile, "%s: %s" NL, argv[0], errstr);
349             exit(i);
350         }
351         if (mask & APHTP_NOFILE) {
352             printf("%s" NL, ctx.out);
353             exit(0);
354         }
355     }
356
357     /*
358      * We can access the files the right way, and we have a record
359      * to add or update.  Let's do it..
360      */
361     if (apr_temp_dir_get((const char**)&dirname, pool) != APR_SUCCESS) {
362         apr_file_printf(errfile, "%s: could not determine temp dir" NL,
363                         argv[0]);
364         exit(ERR_FILEPERM);
365     }
366     dirname = apr_psprintf(pool, "%s/%s", dirname, tn);
367
368     if (apr_file_mktemp(&ftemp, dirname, 0, pool) != APR_SUCCESS) {
369         apr_file_printf(errfile, "%s: unable to create temporary file %s" NL,
370                         argv[0], dirname);
371         exit(ERR_FILEPERM);
372     }
373
374     /*
375      * If we're not creating a new file, copy records from the existing
376      * one to the temporary file until we find the specified user.
377      */
378     if (existing_file && !(mask & APHTP_NEWFILE)) {
379         if (apr_file_open(&fpw, pwfilename, APR_READ | APR_BUFFERED,
380                           APR_OS_DEFAULT, pool) != APR_SUCCESS) {
381             apr_file_printf(errfile, "%s: unable to read file %s" NL,
382                             argv[0], pwfilename);
383             exit(ERR_FILEPERM);
384         }
385         while (apr_file_gets(line, sizeof(line), fpw) == APR_SUCCESS) {
386             char *colon;
387
388             strcpy(cp, line);
389             scratch = cp;
390             while (apr_isspace(*scratch)) {
391                 ++scratch;
392             }
393
394             if (!*scratch || (*scratch == '#')) {
395                 putline(ftemp, line);
396                 continue;
397             }
398             /*
399              * See if this is our user.
400              */
401             colon = strchr(scratch, ':');
402             if (colon != NULL) {
403                 *colon = '\0';
404             }
405             else {
406                 /*
407                  * If we've not got a colon on the line, this could well
408                  * not be a valid htpasswd file.
409                  * We should bail at this point.
410                  */
411                 apr_file_printf(errfile, "%s: The file %s does not appear "
412                                          "to be a valid htpasswd file." NL,
413                                 argv[0], pwfilename);
414                 apr_file_close(fpw);
415                 exit(ERR_INVALID);
416             }
417             if (strcmp(user, scratch) != 0) {
418                 putline(ftemp, line);
419                 continue;
420             }
421             else {
422                 if (!(mask & APHTP_DELUSER)) {
423                     /* We found the user we were looking for.
424                      * Add him to the file.
425                     */
426                     apr_file_printf(errfile, "Updating ");
427                     putline(ftemp, ctx.out);
428                     found++;
429                 }
430                 else {
431                     /* We found the user we were looking for.
432                      * Delete them from the file.
433                      */
434                     apr_file_printf(errfile, "Deleting ");
435                     found++;
436                 }
437             }
438         }
439         apr_file_close(fpw);
440     }
441     if (!found && !(mask & APHTP_DELUSER)) {
442         apr_file_printf(errfile, "Adding ");
443         putline(ftemp, ctx.out);
444     }
445     else if (!found && (mask & APHTP_DELUSER)) {
446         apr_file_printf(errfile, "User %s not found" NL, user);
447         exit(0);
448     }
449     apr_file_printf(errfile, "password for user %s" NL, user);
450
451     /* The temporary file has all the data, just copy it to the new location.
452      */
453     if (apr_file_copy(dirname, pwfilename, APR_FILE_SOURCE_PERMS, pool) !=
454         APR_SUCCESS) {
455         apr_file_printf(errfile, "%s: unable to update file %s" NL,
456                         argv[0], pwfilename);
457         exit(ERR_FILEPERM);
458     }
459     apr_file_close(ftemp);
460     return 0;
461 }