]> granicus.if.org Git - apache/blob - support/htdbm.c
Some LDAP servers (wrongly) return LDAP_CONSTRAINT_VIOLATION if a user is
[apache] / support / htdbm.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  * htdbm.c: simple program for manipulating DBM
19  * password databases for the Apache HTTP server
20  *
21  * Contributed by Mladen Turk <mturk mappingsoft.com>
22  * 12 Oct 2001
23  */
24
25 #include "apr.h"
26 #include "apr_lib.h"
27 #include "apr_strings.h"
28 #include "apr_file_io.h"
29 #include "apr_file_info.h"
30 #include "apr_pools.h"
31 #include "apr_signal.h"
32 #include "apr_md5.h"
33 #include "apr_sha1.h"
34 #include "apr_dbm.h"
35
36 #if APR_HAVE_STDLIB_H
37 #include <stdlib.h>
38 #endif
39 #if APR_HAVE_STRING_H
40 #include <string.h>
41 #endif
42 #if APR_HAVE_STRINGS_H
43 #include <strings.h>
44 #endif
45 #include <time.h>
46
47 #if APR_CHARSET_EBCDIC
48 #include "apr_xlate.h"
49 #endif /*APR_CHARSET_EBCDIC*/
50
51 #if APR_HAVE_UNISTD_H
52 #include <unistd.h>
53 #endif
54 #if APR_HAVE_CRYPT_H
55 #include <crypt.h>
56 #endif
57
58
59 #if !APR_CHARSET_EBCDIC
60 #define LF 10
61 #define CR 13
62 #else /*APR_CHARSET_EBCDIC*/
63 #define LF '\n'
64 #define CR '\r'
65 #endif /*APR_CHARSET_EBCDIC*/
66
67 #define MAX_STRING_LEN 256
68 #define ALG_PLAIN 0
69 #define ALG_APMD5 1
70 #define ALG_APSHA 2
71
72 #if (!(defined(WIN32) || defined(NETWARE)))
73 #define ALG_CRYPT 3
74 #endif
75
76
77 #define ERR_FILEPERM    1
78 #define ERR_SYNTAX      2
79 #define ERR_PWMISMATCH  3
80 #define ERR_INTERRUPTED 4
81 #define ERR_OVERFLOW    5
82 #define ERR_BADUSER     6
83 #define ERR_EMPTY       7
84
85
86 typedef struct htdbm_t htdbm_t;
87
88 struct htdbm_t {
89     apr_dbm_t               *dbm;
90     apr_pool_t              *pool;
91 #if APR_CHARSET_EBCDIC
92     apr_xlate_t             *to_ascii;
93 #endif
94     char                    *filename;
95     char                    *username;
96     char                    *userpass;
97     char                    *comment;
98     char                    *type;
99     int                     create;
100     int                     rdonly;
101     int                     alg;
102 };
103
104
105 #define HTDBM_MAKE   0
106 #define HTDBM_DELETE 1
107 #define HTDBM_VERIFY 2
108 #define HTDBM_LIST   3
109 #define HTDBM_NOFILE 4
110 #define HTDBM_STDIN  5
111
112 static void terminate(void)
113 {
114     apr_terminate();
115 #ifdef NETWARE
116     pressanykey();
117 #endif
118 }
119
120 static void htdbm_terminate(htdbm_t *htdbm)
121 {
122     if (htdbm->dbm)
123         apr_dbm_close(htdbm->dbm);
124     htdbm->dbm = NULL;
125 }
126
127 static htdbm_t *h;
128
129 static void htdbm_interrupted(void)
130 {
131     htdbm_terminate(h);
132     fprintf(stderr, "htdbm Interrupted !\n");
133     exit(ERR_INTERRUPTED);
134 }
135
136 static apr_status_t htdbm_init(apr_pool_t **pool, htdbm_t **hdbm)
137 {
138
139 #if APR_CHARSET_EBCDIC
140     apr_status_t rv;
141 #endif
142
143     apr_pool_create( pool, NULL);
144     apr_signal(SIGINT, (void (*)(int)) htdbm_interrupted);
145
146     (*hdbm) = (htdbm_t *)apr_pcalloc(*pool, sizeof(htdbm_t));
147     (*hdbm)->pool = *pool;
148
149 #if APR_CHARSET_EBCDIC
150     rv = apr_xlate_open(&((*hdbm)->to_ascii), "ISO-8859-1", APR_DEFAULT_CHARSET, (*hdbm)->pool);
151     if (rv) {
152         fprintf(stderr, "apr_xlate_open(to ASCII)->%d\n", rv);
153         return APR_EGENERAL;
154     }
155     rv = apr_SHA1InitEBCDIC((*hdbm)->to_ascii);
156     if (rv) {
157         fprintf(stderr, "apr_SHA1InitEBCDIC()->%d\n", rv);
158         return APR_EGENERAL;
159     }
160     rv = apr_MD5InitEBCDIC((*hdbm)->to_ascii);
161     if (rv) {
162         fprintf(stderr, "apr_MD5InitEBCDIC()->%d\n", rv);
163         return APR_EGENERAL;
164     }
165 #endif /*APR_CHARSET_EBCDIC*/
166
167     /* Set MD5 as default */
168     (*hdbm)->alg = ALG_APMD5;
169     (*hdbm)->type = "default";
170     return APR_SUCCESS;
171 }
172
173 static apr_status_t htdbm_open(htdbm_t *htdbm)
174 {
175     if (htdbm->create)
176         return apr_dbm_open_ex(&htdbm->dbm, htdbm->type, htdbm->filename, APR_DBM_RWCREATE,
177                             APR_OS_DEFAULT, htdbm->pool);
178     else
179         return apr_dbm_open_ex(&htdbm->dbm, htdbm->type, htdbm->filename,
180                             htdbm->rdonly ? APR_DBM_READONLY : APR_DBM_READWRITE,
181                             APR_OS_DEFAULT, htdbm->pool);
182 }
183
184 static apr_status_t htdbm_save(htdbm_t *htdbm, int *changed)
185 {
186     apr_datum_t key, val;
187
188     if (!htdbm->username)
189         return APR_SUCCESS;
190
191     key.dptr = htdbm->username;
192     key.dsize = strlen(htdbm->username);
193     if (apr_dbm_exists(htdbm->dbm, key))
194         *changed = 1;
195
196     val.dsize = strlen(htdbm->userpass);
197     if (!htdbm->comment)
198         val.dptr  = htdbm->userpass;
199     else {
200         val.dptr = apr_pstrcat(htdbm->pool, htdbm->userpass, ":",
201                                htdbm->comment, NULL);
202         val.dsize += (strlen(htdbm->comment) + 1);
203     }
204     return apr_dbm_store(htdbm->dbm, key, val);
205 }
206
207 static apr_status_t htdbm_del(htdbm_t *htdbm)
208 {
209     apr_datum_t key;
210
211     key.dptr = htdbm->username;
212     key.dsize = strlen(htdbm->username);
213     if (!apr_dbm_exists(htdbm->dbm, key))
214         return APR_ENOENT;
215
216     return apr_dbm_delete(htdbm->dbm, key);
217 }
218
219 static apr_status_t htdbm_verify(htdbm_t *htdbm)
220 {
221     apr_datum_t key, val;
222     char *pwd;
223     char *rec, *cmnt;
224
225     key.dptr = htdbm->username;
226     key.dsize = strlen(htdbm->username);
227     if (!apr_dbm_exists(htdbm->dbm, key))
228         return APR_ENOENT;
229     if (apr_dbm_fetch(htdbm->dbm, key, &val) != APR_SUCCESS)
230         return APR_ENOENT;
231     rec = apr_pstrndup(htdbm->pool, val.dptr, val.dsize);
232     cmnt = strchr(rec, ':');
233     if (cmnt)
234         pwd = apr_pstrndup(htdbm->pool, rec, cmnt - rec);
235     else
236         pwd = apr_pstrdup(htdbm->pool, rec);
237     return apr_password_validate(htdbm->userpass, pwd);
238 }
239
240 static apr_status_t htdbm_list(htdbm_t *htdbm)
241 {
242     apr_status_t rv;
243     apr_datum_t key, val;
244     char *cmnt;
245     int i = 0;
246
247     rv = apr_dbm_firstkey(htdbm->dbm, &key);
248     if (rv != APR_SUCCESS) {
249         fprintf(stderr, "Empty database -- %s\n", htdbm->filename);
250         return APR_ENOENT;
251     }
252     fprintf(stderr, "Dumping records from database -- %s\n", htdbm->filename);
253     fprintf(stderr, "    %-32s Comment\n", "Username");
254     while (key.dptr != NULL) {
255         rv = apr_dbm_fetch(htdbm->dbm, key, &val);
256         if (rv != APR_SUCCESS) {
257             fprintf(stderr, "Failed getting data from %s\n", htdbm->filename);
258             return APR_EGENERAL;
259         }
260         /* Note: we don't store \0-terminators on our dbm data */
261         fprintf(stderr, "    %-32.*s", (int)key.dsize, key.dptr);
262         cmnt = memchr(val.dptr, ':', val.dsize);
263         if (cmnt)
264             fprintf(stderr, " %.*s", (int)(val.dptr+val.dsize - (cmnt+1)), cmnt + 1);
265         fprintf(stderr, "\n");
266         rv = apr_dbm_nextkey(htdbm->dbm, &key);
267         if (rv != APR_SUCCESS)
268             fprintf(stderr, "Failed getting NextKey\n");
269         ++i;
270     }
271
272     fprintf(stderr, "Total #records : %d\n", i);
273     return APR_SUCCESS;
274 }
275
276 static void to64(char *s, unsigned long v, int n)
277 {
278     static unsigned char itoa64[] =         /* 0 ... 63 => ASCII - 64 */
279     "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
280
281     while (--n >= 0) {
282         *s++ = itoa64[v&0x3f];
283         v >>= 6;
284     }
285 }
286
287 static apr_status_t htdbm_make(htdbm_t *htdbm)
288 {
289     char cpw[MAX_STRING_LEN];
290     char salt[9];
291
292     switch (htdbm->alg) {
293         case ALG_APSHA:
294             /* XXX cpw >= 28 + strlen(sha1) chars - fixed len SHA */
295             apr_sha1_base64(htdbm->userpass,strlen(htdbm->userpass),cpw);
296         break;
297
298         case ALG_APMD5:
299             (void) srand((int) time((time_t *) NULL));
300             to64(&salt[0], rand(), 8);
301             salt[8] = '\0';
302             apr_md5_encode((const char *)htdbm->userpass, (const char *)salt,
303                             cpw, sizeof(cpw));
304         break;
305         case ALG_PLAIN:
306             /* XXX this len limitation is not in sync with any HTTPd len. */
307             apr_cpystrn(cpw,htdbm->userpass,sizeof(cpw));
308 #if (!(defined(WIN32) || defined(NETWARE)))
309             fprintf(stderr, "Warning: Plain text passwords aren't supported by the "
310                     "server on this platform!\n");
311 #endif
312         break;
313 #if (!(defined(WIN32) || defined(NETWARE)))
314         case ALG_CRYPT:
315             (void) srand((int) time((time_t *) NULL));
316             to64(&salt[0], rand(), 8);
317             salt[8] = '\0';
318             apr_cpystrn(cpw, crypt(htdbm->userpass, salt), sizeof(cpw) - 1);
319             fprintf(stderr, "CRYPT is now deprecated, use MD5 instead!\n");
320 #endif
321         default:
322         break;
323     }
324     htdbm->userpass = apr_pstrdup(htdbm->pool, cpw);
325     return APR_SUCCESS;
326 }
327
328 static apr_status_t htdbm_valid_username(htdbm_t *htdbm)
329 {
330     if (!htdbm->username || (strlen(htdbm->username) > 64) || (strlen(htdbm->username) < 1)) {
331         fprintf(stderr, "Invalid username length\n");
332         return APR_EINVAL;
333     }
334     if (strchr(htdbm->username, ':')) {
335         fprintf(stderr, "Username contains invalid characters\n");
336         return APR_EINVAL;
337     }
338     return APR_SUCCESS;
339 }
340
341 static void htdbm_usage(void)
342 {
343
344 #if (!(defined(WIN32) || defined(NETWARE)))
345 #define CRYPT_OPTION "d"
346 #else
347 #define CRYPT_OPTION ""
348 #endif
349     fprintf(stderr, "htdbm -- program for manipulating DBM password databases.\n\n");
350     fprintf(stderr, "Usage: htdbm    [-cm"CRYPT_OPTION"pstvx] [-TDBTYPE] database username\n");
351     fprintf(stderr, "                -b[cm"CRYPT_OPTION"ptsv] [-TDBTYPE] database username password\n");
352     fprintf(stderr, "                -n[m"CRYPT_OPTION"pst]   username\n");
353     fprintf(stderr, "                -nb[m"CRYPT_OPTION"pst]  username password\n");
354     fprintf(stderr, "                -v[m"CRYPT_OPTION"ps]    [-TDBTYPE] database username\n");
355     fprintf(stderr, "                -vb[m"CRYPT_OPTION"ps]   [-TDBTYPE] database username password\n");
356     fprintf(stderr, "                -x[m"CRYPT_OPTION"ps]    [-TDBTYPE] database username\n");
357     fprintf(stderr, "                -l                       [-TDBTYPE] database\n");
358     fprintf(stderr, "Options:\n");
359     fprintf(stderr, "   -b   Use the password from the command line rather "
360                     "than prompting for it.\n");
361     fprintf(stderr, "   -c   Create a new database.\n");
362     fprintf(stderr, "   -n   Don't update database; display results on stdout.\n");
363     fprintf(stderr, "   -m   Force MD5 encryption of the password (default).\n");
364 #if (!(defined(WIN32) || defined(NETWARE)))
365     fprintf(stderr, "   -d   Force CRYPT encryption of the password (now deprecated).\n");
366 #endif
367     fprintf(stderr, "   -p   Do not encrypt the password (plaintext).\n");
368     fprintf(stderr, "   -s   Force SHA encryption of the password.\n");
369     fprintf(stderr, "   -T   DBM Type (SDBM|GDBM|DB|default).\n");
370     fprintf(stderr, "   -l   Display usernames from database on stdout.\n");
371     fprintf(stderr, "   -t   The last param is username comment.\n");
372     fprintf(stderr, "   -v   Verify the username/password.\n");
373     fprintf(stderr, "   -x   Remove the username record from database.\n");
374     exit(ERR_SYNTAX);
375
376 }
377
378
379 int main(int argc, const char * const argv[])
380 {
381     apr_pool_t *pool;
382     apr_status_t rv;
383     apr_size_t l;
384     char pwi[MAX_STRING_LEN];
385     char pwc[MAX_STRING_LEN];
386     char errbuf[MAX_STRING_LEN];
387     const char *arg;
388     int  need_file = 1;
389     int  need_user = 1;
390     int  need_pwd  = 1;
391     int  need_cmnt = 0;
392     int  pwd_supplied = 0;
393     int  changed = 0;
394     int  cmd = HTDBM_MAKE;
395     int  i;
396     int args_left = 2;
397
398     apr_app_initialize(&argc, &argv, NULL);
399     atexit(terminate);
400
401     if ((rv = htdbm_init(&pool, &h)) != APR_SUCCESS) {
402         fprintf(stderr, "Unable to initialize htdbm terminating!\n");
403         apr_strerror(rv, errbuf, sizeof(errbuf));
404         exit(1);
405     }
406     /*
407      * Preliminary check to make sure they provided at least
408      * three arguments, we'll do better argument checking as
409      * we parse the command line.
410      */
411     if (argc < 3)
412        htdbm_usage();
413     /*
414      * Go through the argument list and pick out any options.  They
415      * have to precede any other arguments.
416      */
417     for (i = 1; i < argc; i++) {
418         arg = argv[i];
419         if (*arg != '-')
420             break;
421
422         while (*++arg != '\0') {
423             switch (*arg) {
424             case 'b':
425                 pwd_supplied = 1;
426                 need_pwd = 0;
427                 args_left++;
428                 break;
429             case 'c':
430                 h->create = 1;
431                 break;
432             case 'n':
433                 need_file = 0;
434                 cmd = HTDBM_NOFILE;
435                     args_left--;
436                 break;
437             case 'l':
438                 need_pwd = 0;
439                 need_user = 0;
440                 cmd = HTDBM_LIST;
441                 h->rdonly = 1;
442                 args_left--;
443                 break;
444             case 't':
445                 need_cmnt = 1;
446                 args_left++;
447                 break;
448             case 'T':
449                 h->type = apr_pstrdup(h->pool, ++arg);
450                 while (*arg != '\0')
451                     ++arg;
452                 --arg; /* so incrementing this in the loop with find a null */
453                 break;
454             case 'v':
455                 h->rdonly = 1;
456                 cmd = HTDBM_VERIFY;
457                 break;
458             case 'x':
459                 need_pwd = 0;
460                 cmd = HTDBM_DELETE;
461                 break;
462             case 'm':
463                 h->alg = ALG_APMD5;
464                 break;
465             case 'p':
466                 h->alg = ALG_PLAIN;
467                 break;
468             case 's':
469                 h->alg = ALG_APSHA;
470                 break;
471 #if (!(defined(WIN32) || defined(NETWARE)))
472             case 'd':
473                 h->alg = ALG_CRYPT;
474                 break;
475 #endif
476             default:
477                 htdbm_usage();
478                 break;
479             }
480         }
481     }
482     /*
483      * Make sure we still have exactly the right number of arguments left
484      * (the filename, the username, and possibly the password if -b was
485      * specified).
486      */
487     if ((argc - i) != args_left)
488         htdbm_usage();
489
490     if (!need_file)
491         i--;
492     else {
493         h->filename = apr_pstrdup(h->pool, argv[i]);
494             if ((rv = htdbm_open(h)) != APR_SUCCESS) {
495             fprintf(stderr, "Error opening database %s\n", argv[i]);
496             apr_strerror(rv, errbuf, sizeof(errbuf));
497             fprintf(stderr,"%s\n",errbuf);
498             exit(ERR_FILEPERM);
499         }
500     }
501     if (need_user) {
502         h->username = apr_pstrdup(pool, argv[i+1]);
503         if (htdbm_valid_username(h) != APR_SUCCESS)
504             exit(ERR_BADUSER);
505     }
506     if (pwd_supplied)
507         h->userpass = apr_pstrdup(pool, argv[i+2]);
508
509     if (need_pwd) {
510         l = sizeof(pwc);
511         if (apr_password_get("Enter password        : ", pwi, &l) != APR_SUCCESS) {
512             fprintf(stderr, "Password too long\n");
513             exit(ERR_OVERFLOW);
514         }
515         l = sizeof(pwc);
516         if (apr_password_get("Re-type password      : ", pwc, &l) != APR_SUCCESS) {
517             fprintf(stderr, "Password too long\n");
518             exit(ERR_OVERFLOW);
519         }
520         if (strcmp(pwi, pwc) != 0) {
521             fprintf(stderr, "Password verification error\n");
522             exit(ERR_PWMISMATCH);
523         }
524
525         h->userpass = apr_pstrdup(pool,  pwi);
526     }
527     if (need_cmnt && pwd_supplied)
528         h->comment = apr_pstrdup(pool, argv[i+3]);
529     else if (need_cmnt)
530         h->comment = apr_pstrdup(pool, argv[i+2]);
531
532     switch (cmd) {
533         case HTDBM_VERIFY:
534             if ((rv = htdbm_verify(h)) != APR_SUCCESS) {
535                 if (APR_STATUS_IS_ENOENT(rv)) {
536                     fprintf(stderr, "The user '%s' could not be found in database\n", h->username);
537                     exit(ERR_BADUSER);
538                 }
539                 else {
540                     fprintf(stderr, "Password mismatch for user '%s'\n", h->username);
541                     exit(ERR_PWMISMATCH);
542                 }
543             }
544             else
545                 fprintf(stderr, "Password validated for user '%s'\n", h->username);
546             break;
547         case HTDBM_DELETE:
548             if (htdbm_del(h) != APR_SUCCESS) {
549                 fprintf(stderr, "Cannot find user '%s' in database\n", h->username);
550                 exit(ERR_BADUSER);
551             }
552             h->username = NULL;
553             changed = 1;
554             break;
555         case HTDBM_LIST:
556             htdbm_list(h);
557             break;
558         default:
559             htdbm_make(h);
560             break;
561
562     }
563     if (need_file && !h->rdonly) {
564         if ((rv = htdbm_save(h, &changed)) != APR_SUCCESS) {
565             apr_strerror(rv, errbuf, sizeof(errbuf));
566             exit(ERR_FILEPERM);
567         }
568         fprintf(stdout, "Database %s %s.\n", h->filename,
569                 h->create ? "created" : (changed ? "modified" : "updated"));
570     }
571     if (cmd == HTDBM_NOFILE) {
572         if (!need_cmnt) {
573             fprintf(stderr, "%s:%s\n", h->username, h->userpass);
574         }
575         else {
576             fprintf(stderr, "%s:%s:%s\n", h->username, h->userpass,
577                     h->comment);
578         }
579     }
580     htdbm_terminate(h);
581
582     return 0; /* Suppress compiler warning. */
583 }