Don't assume mgtq will always be empty, drain mgtq explicitly.
[dragonfly.git] / usr.sbin / pwd_mkdb / pwd_mkdb.c
1 /*-
2  * Copyright (c) 1991, 1993, 1994
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * @(#) Copyright (c) 1991, 1993, 1994 The Regents of the University of California.  All rights reserved.
34  * @(#)pwd_mkdb.c       8.5 (Berkeley) 4/20/94
35  * $FreeBSD: src/usr.sbin/pwd_mkdb/pwd_mkdb.c,v 1.35 2000/03/09 18:11:16 paul Exp $
36  * $DragonFly: src/usr.sbin/pwd_mkdb/pwd_mkdb.c,v 1.5 2005/12/05 02:40:27 swildner Exp $
37  */
38
39 #include <sys/param.h>
40 #include <sys/stat.h>
41
42 #include <db.h>
43 #include <err.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <limits.h>
47 #include <pwd.h>
48 #include <signal.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53
54 #include "pw_scan.h"
55
56 #define INSECURE        1
57 #define SECURE          2
58 #define PERM_INSECURE   (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
59 #define PERM_SECURE     (S_IRUSR|S_IWUSR)
60
61 HASHINFO openinfo = {
62         4096,           /* bsize */
63         32,             /* ffactor */
64         256,            /* nelem */
65         2048 * 1024,    /* cachesize */
66         NULL,           /* hash() */
67         0               /* lorder */
68 };
69
70 static enum state { FILE_INSECURE, FILE_SECURE, FILE_ORIG } clean;
71 static struct passwd pwd;                       /* password structure */
72 static char *pname;                             /* password file name */
73 static char prefix[MAXPATHLEN];
74
75 static int is_comment;  /* flag for comments */
76 static char line[LINE_MAX];
77
78 void    cleanup(void);
79 void    error(char *);
80 void    cp(char *, char *, mode_t mode);
81 void    mv(char *, char *);
82 int     scan(FILE *, struct passwd *);
83 static void     usage(void);
84
85 int
86 main(int argc, char *argv[])
87 {
88         DB *dp, *sdp, *pw_db;
89         DBT data, sdata, key;
90         FILE *fp, *oldfp;
91         sigset_t set;
92         int ch, cnt, ypcnt, len, makeold, tfd, yp_enabled = 0;
93         char *p, *t;
94         char buf[MAX(MAXPATHLEN, LINE_MAX * 2)], tbuf[1024];
95         char sbuf[MAX(MAXPATHLEN, LINE_MAX * 2)];
96         char buf2[MAXPATHLEN];
97         char sbuf2[MAXPATHLEN];
98         char *username;
99         u_int method, methoduid;
100         int Cflag;
101         int nblock = 0;
102
103         Cflag = 0;
104         strcpy(prefix, _PATH_PWD);
105         makeold = 0;
106         username = NULL;
107         while ((ch = getopt(argc, argv, "Cd:ps:u:vN")) != -1)
108                 switch(ch) {
109                 case 'C':                       /* verify only */
110                         Cflag = 1;
111                         break;
112                 case 'd':
113                         strncpy(prefix, optarg, sizeof prefix - 1);
114                         break;
115                 case 'p':                       /* create V7 "file.orig" */
116                         makeold = 1;
117                         break;
118                 case 's':                       /* change default cachesize */
119                         openinfo.cachesize = atoi(optarg) * 1024 * 1024;
120                         break;
121                 case 'u':                       /* only update this record */
122                         username = optarg;
123                         break;
124                 case 'v':                       /* backward compatible */
125                         break;
126                 case 'N':                       /* do not wait for lock */
127                         nblock = LOCK_NB;
128                         break;
129                 default:
130                         usage();
131                 }
132         argc -= optind;
133         argv += optind;
134
135         if (argc != 1 || (username && (*username == '+' || *username == '-')))
136                 usage();
137
138         /*
139          * This could be changed to allow the user to interrupt.
140          * Probably not worth the effort.
141          */
142         sigemptyset(&set);
143         sigaddset(&set, SIGTSTP);
144         sigaddset(&set, SIGHUP);
145         sigaddset(&set, SIGINT);
146         sigaddset(&set, SIGQUIT);
147         sigaddset(&set, SIGTERM);
148         sigprocmask(SIG_BLOCK, &set, (sigset_t *)NULL);
149
150         /* We don't care what the user wants. */
151         umask(0);
152
153         pname = *argv;
154
155         /*
156          * Open and lock the original password file.  We have to check
157          * the hardlink count after we get the lock to handle any potential
158          * unlink/rename race.
159          *
160          * This lock is necessary when someone runs pwd_mkdb manually, directly
161          * on master.passwd, to handle the case where a user might try to
162          * change his password while pwd_mkdb is running. 
163          */
164         for (;;) {
165                 struct stat st;
166
167                 if (!(fp = fopen(pname, "r")))
168                         error(pname);
169                 if (flock(fileno(fp), LOCK_EX|nblock) < 0)
170                         error("flock");
171                 if (fstat(fileno(fp), &st) < 0)
172                         error(pname);
173                 if (st.st_nlink != 0)
174                         break;
175                 fclose(fp);
176                 fp = NULL;
177         }
178
179         /* check only if password database is valid */
180         if (Cflag) {
181                 for (cnt = 1; scan(fp, &pwd); ++cnt);
182                 exit(0);
183         }
184
185         /* Open the temporary insecure password database. */
186         snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
187         snprintf(sbuf, sizeof(sbuf), "%s/%s.tmp", prefix, _SMP_DB);
188         if (username) {
189                 snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB);
190                 snprintf(sbuf2, sizeof(sbuf2), "%s/%s", prefix, _SMP_DB);
191
192                 clean = FILE_INSECURE;
193                 cp(buf2, buf, PERM_INSECURE);
194                 dp = dbopen(buf,
195                     O_RDWR|O_EXCL, PERM_INSECURE, DB_HASH, &openinfo);
196                 if (dp == NULL)
197                         error(buf);
198
199                 clean = FILE_SECURE;
200                 cp(sbuf2, sbuf, PERM_SECURE);
201                 sdp = dbopen(sbuf,
202                     O_RDWR|O_EXCL, PERM_SECURE, DB_HASH, &openinfo);
203                 if (sdp == NULL)
204                         error(sbuf);
205
206                 /*
207                  * Do some trouble to check if we should store this users 
208                  * uid. Don't use getpwnam/getpwuid as that interferes 
209                  * with NIS.
210                  */
211                 pw_db = dbopen(_PATH_MP_DB, O_RDONLY, 0, DB_HASH, NULL);
212                 if (!pw_db)
213                         error(_MP_DB);
214                 buf[0] = _PW_KEYBYNAME;
215                 len = strlen(username);
216
217                 /* Only check that username fits in buffer */
218                 memmove(buf + 1, username, MIN(len, sizeof(buf) - 1));
219                 key.data = (u_char *)buf;
220                 key.size = len + 1;
221                 if ((pw_db->get)(pw_db, &key, &data, 0) == 0) {
222                         p = (char *)data.data;
223
224                         /* jump over pw_name and pw_passwd, to get to pw_uid */
225                         while (*p++)
226                                 ;
227                         while (*p++)
228                                 ;
229
230                         buf[0] = _PW_KEYBYUID;
231                         memmove(buf + 1, p, sizeof(int));
232                         key.data = (u_char *)buf;
233                         key.size = sizeof(int) + 1;
234
235                         if ((pw_db->get)(pw_db, &key, &data, 0) == 0) {
236                                 /* First field of data.data holds pw_pwname */
237                                 if (!strcmp(data.data, username))
238                                         methoduid = 0;
239                                 else
240                                         methoduid = R_NOOVERWRITE;
241                         } else {
242                                 methoduid = R_NOOVERWRITE;
243                         }
244                 } else {
245                         methoduid = R_NOOVERWRITE;
246                 }
247                 if ((pw_db->close)(pw_db))
248                         error("close pw_db");
249                 method = 0;
250         } else {
251                 dp = dbopen(buf,
252                     O_RDWR|O_CREAT|O_EXCL, PERM_INSECURE, DB_HASH, &openinfo);
253                 if (dp == NULL)
254                         error(buf);
255                 clean = FILE_INSECURE;
256
257                 sdp = dbopen(sbuf,
258                     O_RDWR|O_CREAT|O_EXCL, PERM_SECURE, DB_HASH, &openinfo);
259                 if (sdp == NULL)
260                         error(sbuf);
261                 clean = FILE_SECURE;
262
263                 method = R_NOOVERWRITE;
264                 methoduid = R_NOOVERWRITE;
265         }
266
267         /*
268          * Open file for old password file.  Minor trickiness -- don't want to
269          * chance the file already existing, since someone (stupidly) might
270          * still be using this for permission checking.  So, open it first and
271          * fdopen the resulting fd.  The resulting file should be readable by
272          * everyone.
273          */
274         if (makeold) {
275                 snprintf(buf, sizeof(buf), "%s.orig", pname);
276                 if ((tfd = open(buf,
277                     O_WRONLY|O_CREAT|O_EXCL, PERM_INSECURE)) < 0)
278                         error(buf);
279                 if ((oldfp = fdopen(tfd, "w")) == NULL)
280                         error(buf);
281                 clean = FILE_ORIG;
282         }
283
284         /*
285          * The databases actually contain three copies of the original data.
286          * Each password file entry is converted into a rough approximation
287          * of a ``struct passwd'', with the strings placed inline.  This
288          * object is then stored as the data for three separate keys.  The
289          * first key * is the pw_name field prepended by the _PW_KEYBYNAME
290          * character.  The second key is the pw_uid field prepended by the
291          * _PW_KEYBYUID character.  The third key is the line number in the
292          * original file prepended by the _PW_KEYBYNUM character.  (The special
293          * characters are prepended to ensure that the keys do not collide.)
294          */
295         ypcnt = 1;
296         data.data = (u_char *)buf;
297         sdata.data = (u_char *)sbuf;
298         key.data = (u_char *)tbuf;
299         for (cnt = 1; scan(fp, &pwd); ++cnt) {
300                 if (!is_comment && 
301                     (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-'))
302                         yp_enabled = 1;
303                 if (is_comment)
304                         --cnt;
305 #define COMPACT(e)      t = e; while ((*p++ = *t++));
306                 if (!is_comment && 
307                     (!username || (strcmp(username, pwd.pw_name) == 0))) {
308                         /* Create insecure data. */
309                         p = buf;
310                         COMPACT(pwd.pw_name);
311                         COMPACT("*");
312                         memmove(p, &pwd.pw_uid, sizeof(pwd.pw_uid));
313                         p += sizeof(int);
314                         memmove(p, &pwd.pw_gid, sizeof(pwd.pw_gid));
315                         p += sizeof(int);
316                         memmove(p, &pwd.pw_change, sizeof(time_t));
317                         p += sizeof(time_t);
318                         COMPACT(pwd.pw_class);
319                         COMPACT(pwd.pw_gecos);
320                         COMPACT(pwd.pw_dir);
321                         COMPACT(pwd.pw_shell);
322                         memmove(p, &pwd.pw_expire, sizeof(time_t));
323                         p += sizeof(time_t);
324                         memmove(p, &pwd.pw_fields, sizeof pwd.pw_fields);
325                         p += sizeof pwd.pw_fields;
326                         data.size = p - buf;
327
328                         /* Create secure data. */
329                         p = sbuf;
330                         COMPACT(pwd.pw_name);
331                         COMPACT(pwd.pw_passwd);
332                         memmove(p, &pwd.pw_uid, sizeof(pwd.pw_uid));
333                         p += sizeof(int);
334                         memmove(p, &pwd.pw_gid, sizeof(pwd.pw_gid));
335                         p += sizeof(int);
336                         memmove(p, &pwd.pw_change, sizeof(time_t));
337                         p += sizeof(time_t);
338                         COMPACT(pwd.pw_class);
339                         COMPACT(pwd.pw_gecos);
340                         COMPACT(pwd.pw_dir);
341                         COMPACT(pwd.pw_shell);
342                         memmove(p, &pwd.pw_expire, sizeof(time_t));
343                         p += sizeof(time_t);
344                         memmove(p, &pwd.pw_fields, sizeof pwd.pw_fields);
345                         p += sizeof pwd.pw_fields;
346                         sdata.size = p - sbuf;
347
348                         /* Store insecure by name. */
349                         tbuf[0] = _PW_KEYBYNAME;
350                         len = strlen(pwd.pw_name);
351                         memmove(tbuf + 1, pwd.pw_name, len);
352                         key.size = len + 1;
353                         if ((dp->put)(dp, &key, &data, method) == -1)
354                                 error("put");
355
356                         /* Store insecure by number. */
357                         tbuf[0] = _PW_KEYBYNUM;
358                         memmove(tbuf + 1, &cnt, sizeof(cnt));
359                         key.size = sizeof(cnt) + 1;
360                         if ((dp->put)(dp, &key, &data, method) == -1)
361                                 error("put");
362
363                         /* Store insecure by uid. */
364                         tbuf[0] = _PW_KEYBYUID;
365                         memmove(tbuf + 1, &pwd.pw_uid, sizeof(pwd.pw_uid));
366                         key.size = sizeof(pwd.pw_uid) + 1;
367                         if ((dp->put)(dp, &key, &data, methoduid) == -1)
368                                 error("put");
369
370                         /* Store secure by name. */
371                         tbuf[0] = _PW_KEYBYNAME;
372                         len = strlen(pwd.pw_name);
373                         memmove(tbuf + 1, pwd.pw_name, len);
374                         key.size = len + 1;
375                         if ((sdp->put)(sdp, &key, &sdata, method) == -1)
376                                 error("put");
377
378                         /* Store secure by number. */
379                         tbuf[0] = _PW_KEYBYNUM;
380                         memmove(tbuf + 1, &cnt, sizeof(cnt));
381                         key.size = sizeof(cnt) + 1;
382                         if ((sdp->put)(sdp, &key, &sdata, method) == -1)
383                                 error("put");
384
385                         /* Store secure by uid. */
386                         tbuf[0] = _PW_KEYBYUID;
387                         memmove(tbuf + 1, &pwd.pw_uid, sizeof(pwd.pw_uid));
388                         key.size = sizeof(pwd.pw_uid) + 1;
389                         if ((sdp->put)(sdp, &key, &sdata, methoduid) == -1)
390                                 error("put");
391
392                         /* Store insecure and secure special plus and special minus */
393                         if (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-') {
394                                 tbuf[0] = _PW_KEYYPBYNUM;
395                                 memmove(tbuf + 1, &ypcnt, sizeof(cnt));
396                                 ypcnt++;
397                                 key.size = sizeof(cnt) + 1;
398                                 if ((dp->put)(dp, &key, &data, method) == -1)
399                                         error("put");
400                                 if ((sdp->put)(sdp, &key, &sdata, method) == -1)
401                                         error("put");
402                         }
403                 }
404                 /* Create original format password file entry */
405                 if (is_comment && makeold){     /* copy comments */
406                         if (fprintf(oldfp, "%s\n", line) < 0)
407                                 error("write old");
408                 } else if (makeold) {
409                         char uidstr[20];
410                         char gidstr[20];
411
412                         snprintf(uidstr, sizeof(uidstr), "%u", pwd.pw_uid);
413                         snprintf(gidstr, sizeof(gidstr), "%u", pwd.pw_gid);
414
415                         if (fprintf(oldfp, "%s:*:%s:%s:%s:%s:%s\n",
416                             pwd.pw_name, pwd.pw_fields & _PWF_UID ? uidstr : "",
417                             pwd.pw_fields & _PWF_GID ? gidstr : "",
418                             pwd.pw_gecos, pwd.pw_dir, pwd.pw_shell) < 0)
419                                 error("write old");
420                 }
421         }
422         /* If YP enabled, set flag. */
423         if (yp_enabled) {
424                 buf[0] = yp_enabled + 2;
425                 data.size = 1;
426                 tbuf[0] = _PW_KEYYPENABLED;
427                 key.size = 1;
428                 if ((dp->put)(dp, &key, &data, method) == -1)
429                         error("put");
430                 if ((sdp->put)(sdp, &key, &data, method) == -1)
431                         error("put");
432         }
433
434         if ((dp->close)(dp) == -1)
435                 error("close");
436         if ((sdp->close)(sdp) == -1)
437                 error("close");
438         if (makeold) {
439                 fflush(oldfp);
440                 if (fclose(oldfp) == EOF)
441                         error("close old");
442         }
443
444         /* Set master.passwd permissions, in case caller forgot. */
445         fchmod(fileno(fp), S_IRUSR|S_IWUSR);
446
447         /* Install as the real password files. */
448         snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
449         snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB);
450         mv(buf, buf2);
451         snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB);
452         snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _SMP_DB);
453         mv(buf, buf2);
454         if (makeold) {
455                 snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _PASSWD);
456                 snprintf(buf, sizeof(buf), "%s.orig", pname);
457                 mv(buf, buf2);
458         }
459         /*
460          * Move the master password LAST -- chpass(1), passwd(1) and vipw(8)
461          * all use flock(2) on it to block other incarnations of themselves.
462          * The rename means that everything is unlocked, as the original file
463          * can no longer be accessed.
464          */
465         snprintf(buf, sizeof(buf), "%s/%s", prefix, _MASTERPASSWD);
466         mv(pname, buf);
467
468         /*
469          * Close locked password file after rename()
470          */
471         if (fclose(fp) == EOF)
472                 error("close fp");
473
474         exit(0);
475 }
476
477 int
478 scan(FILE *fp, struct passwd *pw)
479 {
480         static int lcnt;
481         char *p;
482
483         if (!fgets(line, sizeof(line), fp))
484                 return (0);
485         ++lcnt;
486         /*
487          * ``... if I swallow anything evil, put your fingers down my
488          * throat...''
489          *      -- The Who
490          */
491         if (!(p = strchr(line, '\n'))) {
492                 warnx("line too long");
493                 goto fmt;
494
495         }
496         *p = '\0';
497
498         /* 
499          * Ignore comments: ^[ \t]*#
500          */
501         for (p = line; *p != '\0'; p++)
502                 if (*p != ' ' && *p != '\t')
503                         break;
504         if (*p == '#' || *p == '\0') {
505                 is_comment = 1;
506                 return(1);
507         } else
508                 is_comment = 0;
509
510         if (!pw_scan(line, pw)) {
511                 warnx("at line #%d", lcnt);
512 fmt:            errno = EFTYPE; /* XXX */
513                 error(pname);
514         }
515
516         return (1);
517 }
518
519 void                    
520 cp(char *from, char *to, mode_t mode)              
521 {               
522         static char buf[MAXBSIZE];
523         int from_fd, rcount, to_fd, wcount;
524
525         if ((from_fd = open(from, O_RDONLY, 0)) < 0)
526                 error(from);
527         if ((to_fd = open(to, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0)
528                 error(to);
529         while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
530                 wcount = write(to_fd, buf, rcount);
531                 if (rcount != wcount || wcount == -1) {
532                         int sverrno = errno;
533
534                         snprintf(buf, sizeof(buf), "%s to %s", from, to);
535                         errno = sverrno;
536                         error(buf);
537                 }
538         }
539         if (rcount < 0) {
540                 int sverrno = errno;
541
542                 snprintf(buf, sizeof(buf), "%s to %s", from, to);
543                 errno = sverrno;
544                 error(buf);
545         }
546 }
547
548
549 void
550 mv(char *from, char *to)
551 {
552         char buf[MAXPATHLEN];
553
554         if (rename(from, to)) {
555                 int sverrno = errno;
556                 snprintf(buf, sizeof(buf), "%s to %s", from, to);
557                 errno = sverrno;
558                 error(buf);
559         }
560 }
561
562 void
563 error(char *name)
564 {
565         warn("%s", name);
566         cleanup();
567         exit(1);
568 }
569
570 void
571 cleanup(void)
572 {
573         char buf[MAXPATHLEN];
574
575         switch(clean) {
576         case FILE_ORIG:
577                 snprintf(buf, sizeof(buf), "%s.orig", pname);
578                 unlink(buf);
579                 /* FALLTHROUGH */
580         case FILE_SECURE:
581                 snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB);
582                 unlink(buf);
583                 /* FALLTHROUGH */
584         case FILE_INSECURE:
585                 snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
586                 unlink(buf);
587         }
588 }
589
590 static void
591 usage(void)
592 {
593
594         fprintf(stderr,
595 "usage: pwd_mkdb [-C] [-N] [-p] [-d <dest dir>] [-s <cachesize>] [-u <local username>] file\n");
596         exit(1);
597 }