]> granicus.if.org Git - apache/blob - support/htpasswd.c
htdbm:
[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_file_open_stderr(&errfile, pool);
278     ctx.pool = pool;
279     ctx.alg = ALG_APMD5;
280
281 #if APR_CHARSET_EBCDIC
282     rv = apr_xlate_open(&to_ascii, "ISO-8859-1", APR_DEFAULT_CHARSET, pool);
283     if (rv) {
284         apr_file_printf(errfile, "apr_xlate_open(to ASCII)->%d" NL, rv);
285         exit(1);
286     }
287     rv = apr_SHA1InitEBCDIC(to_ascii);
288     if (rv) {
289         apr_file_printf(errfile, "apr_SHA1InitEBCDIC()->%d" NL, rv);
290         exit(1);
291     }
292     rv = apr_MD5InitEBCDIC(to_ascii);
293     if (rv) {
294         apr_file_printf(errfile, "apr_MD5InitEBCDIC()->%d" NL, rv);
295         exit(1);
296     }
297 #endif /*APR_CHARSET_EBCDIC*/
298
299     check_args(argc, argv, &ctx, &mask, &user, &pwfilename);
300
301     /*
302      * Only do the file checks if we're supposed to frob it.
303      */
304     if (!(mask & APHTP_NOFILE)) {
305         existing_file = exists(pwfilename, pool);
306         if (existing_file) {
307             /*
308              * Check that this existing file is readable and writable.
309              */
310             if (!accessible(pool, pwfilename, APR_FOPEN_READ|APR_FOPEN_WRITE)) {
311                 apr_file_printf(errfile, "%s: cannot open file %s for "
312                                 "read/write access" NL, argv[0], pwfilename);
313                 exit(ERR_FILEPERM);
314             }
315         }
316         else {
317             /*
318              * Error out if -c was omitted for this non-existant file.
319              */
320             if (!(mask & APHTP_NEWFILE)) {
321                 apr_file_printf(errfile,
322                         "%s: cannot modify file %s; use '-c' to create it" NL,
323                         argv[0], pwfilename);
324                 exit(ERR_FILEPERM);
325             }
326             /*
327              * As it doesn't exist yet, verify that we can create it.
328              */
329             if (!accessible(pool, pwfilename, APR_FOPEN_WRITE|APR_FOPEN_CREATE)) {
330                 apr_file_printf(errfile, "%s: cannot create file %s" NL,
331                                 argv[0], pwfilename);
332                 exit(ERR_FILEPERM);
333             }
334         }
335     }
336
337     /*
338      * All the file access checks (if any) have been made.  Time to go to work;
339      * try to create the record for the username in question.  If that
340      * fails, there's no need to waste any time on file manipulations.
341      * Any error message text is returned in the record buffer, since
342      * the mkrecord() routine doesn't have access to argv[].
343      */
344     if (!(mask & APHTP_DELUSER)) {
345         i = mkrecord(&ctx, user);
346         if (i != 0) {
347             apr_file_printf(errfile, "%s: %s" NL, argv[0], errstr);
348             exit(i);
349         }
350         if (mask & APHTP_NOFILE) {
351             printf("%s" NL, ctx.out);
352             exit(0);
353         }
354     }
355
356     /*
357      * We can access the files the right way, and we have a record
358      * to add or update.  Let's do it..
359      */
360     if (apr_temp_dir_get((const char**)&dirname, pool) != APR_SUCCESS) {
361         apr_file_printf(errfile, "%s: could not determine temp dir" NL,
362                         argv[0]);
363         exit(ERR_FILEPERM);
364     }
365     dirname = apr_psprintf(pool, "%s/%s", dirname, tn);
366
367     if (apr_file_mktemp(&ftemp, dirname, 0, pool) != APR_SUCCESS) {
368         apr_file_printf(errfile, "%s: unable to create temporary file %s" NL,
369                         argv[0], dirname);
370         exit(ERR_FILEPERM);
371     }
372
373     /*
374      * If we're not creating a new file, copy records from the existing
375      * one to the temporary file until we find the specified user.
376      */
377     if (existing_file && !(mask & APHTP_NEWFILE)) {
378         if (apr_file_open(&fpw, pwfilename, APR_READ | APR_BUFFERED,
379                           APR_OS_DEFAULT, pool) != APR_SUCCESS) {
380             apr_file_printf(errfile, "%s: unable to read file %s" NL,
381                             argv[0], pwfilename);
382             exit(ERR_FILEPERM);
383         }
384         while (apr_file_gets(line, sizeof(line), fpw) == APR_SUCCESS) {
385             char *colon;
386
387             strcpy(cp, line);
388             scratch = cp;
389             while (apr_isspace(*scratch)) {
390                 ++scratch;
391             }
392
393             if (!*scratch || (*scratch == '#')) {
394                 putline(ftemp, line);
395                 continue;
396             }
397             /*
398              * See if this is our user.
399              */
400             colon = strchr(scratch, ':');
401             if (colon != NULL) {
402                 *colon = '\0';
403             }
404             else {
405                 /*
406                  * If we've not got a colon on the line, this could well
407                  * not be a valid htpasswd file.
408                  * We should bail at this point.
409                  */
410                 apr_file_printf(errfile, "%s: The file %s does not appear "
411                                          "to be a valid htpasswd file." NL,
412                                 argv[0], pwfilename);
413                 apr_file_close(fpw);
414                 exit(ERR_INVALID);
415             }
416             if (strcmp(user, scratch) != 0) {
417                 putline(ftemp, line);
418                 continue;
419             }
420             else {
421                 if (!(mask & APHTP_DELUSER)) {
422                     /* We found the user we were looking for.
423                      * Add him to the file.
424                     */
425                     apr_file_printf(errfile, "Updating ");
426                     putline(ftemp, ctx.out);
427                     found++;
428                 }
429                 else {
430                     /* We found the user we were looking for.
431                      * Delete them from the file.
432                      */
433                     apr_file_printf(errfile, "Deleting ");
434                     found++;
435                 }
436             }
437         }
438         apr_file_close(fpw);
439     }
440     if (!found && !(mask & APHTP_DELUSER)) {
441         apr_file_printf(errfile, "Adding ");
442         putline(ftemp, ctx.out);
443     }
444     else if (!found && (mask & APHTP_DELUSER)) {
445         apr_file_printf(errfile, "User %s not found" NL, user);
446         exit(0);
447     }
448     apr_file_printf(errfile, "password for user %s" NL, user);
449
450     /* The temporary file has all the data, just copy it to the new location.
451      */
452     if (apr_file_copy(dirname, pwfilename, APR_FILE_SOURCE_PERMS, pool) !=
453         APR_SUCCESS) {
454         apr_file_printf(errfile, "%s: unable to update file %s" NL,
455                         argv[0], pwfilename);
456         exit(ERR_FILEPERM);
457     }
458     apr_file_close(ftemp);
459     return 0;
460 }