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