xref: /dragonfly/lib/libutil/pw_util.c (revision 207ba6700e31c36bb4e38d4da221a4f86e466ee9)
1 /*-
2  * Copyright (c) 1990, 1993, 1994
3  *        The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 2002 Networks Associates Technology, Inc.
5  * All rights reserved.
6  *
7  * Portions of this software were developed for the FreeBSD Project by
8  * ThinkSec AS and NAI Labs, the Security Research Division of Network
9  * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
10  * ("CBOSS"), as part of the DARPA CHATS research program.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  *
36  * @(#)pw_util.c    8.3 (Berkeley) 4/2/94
37  * $FreeBSD: head/lib/libutil/pw_util.c 244744 2012-12-27 20:24:44Z bapt $
38  */
39 
40 /*
41  * This file is used by all the "password" programs; vipw(8), chpass(1),
42  * and passwd(1).
43  */
44 
45 #include <sys/param.h>
46 #include <sys/errno.h>
47 #include <sys/time.h>
48 #include <sys/resource.h>
49 #include <sys/stat.h>
50 #include <sys/wait.h>
51 
52 #include <ctype.h>
53 #include <err.h>
54 #include <fcntl.h>
55 #include <inttypes.h>
56 #include <libgen.h>
57 #include <paths.h>
58 #include <pwd.h>
59 #include <signal.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <unistd.h>
64 #include <libutil.h>
65 
66 static pid_t editpid = -1;
67 static int lockfd = -1;
68 static char masterpasswd[PATH_MAX];
69 static char passwd_dir[PATH_MAX];
70 static char tempname[PATH_MAX];
71 static int initialized;
72 
73 #if 0
74 void
75 pw_cont(int sig)
76 {
77 
78           if (editpid != -1)
79                     kill(editpid, sig);
80 }
81 #endif
82 
83 /*
84  * Initialize statics and set limits, signals & umask to try to avoid
85  * interruptions, crashes etc. that might expose passord data.
86  */
87 int
pw_init(const char * dir,const char * master)88 pw_init(const char *dir, const char *master)
89 {
90 #if 0
91           struct rlimit rlim;
92 #endif
93 
94           if (dir == NULL) {
95                     strcpy(passwd_dir, _PATH_ETC);
96           } else {
97                     if (strlen(dir) >= sizeof(passwd_dir)) {
98                               errno = ENAMETOOLONG;
99                               return (-1);
100                     }
101                     strcpy(passwd_dir, dir);
102           }
103 
104           if (master == NULL) {
105                     if (dir == NULL) {
106                               strcpy(masterpasswd, _PATH_MASTERPASSWD);
107                     } else if (snprintf(masterpasswd, sizeof(masterpasswd), "%s/%s",
108                         passwd_dir, _MASTERPASSWD) > (int)sizeof(masterpasswd)) {
109                               errno = ENAMETOOLONG;
110                               return (-1);
111                     }
112           } else {
113                     if (strlen(master) >= sizeof(masterpasswd)) {
114                               errno = ENAMETOOLONG;
115                               return (-1);
116                     }
117                     strcpy(masterpasswd, master);
118           }
119 
120           /*
121            * The code that follows is extremely disruptive to the calling
122            * process, and is therefore disabled until someone can conceive
123            * of a realistic scenario where it would fend off a compromise.
124            * Race conditions concerning the temporary files can be guarded
125            * against in other ways than masking signals (by checking stat(2)
126            * results after creation).
127            */
128 #if 0
129           /* Unlimited resource limits. */
130           rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
131           (void)setrlimit(RLIMIT_CPU, &rlim);
132           (void)setrlimit(RLIMIT_FSIZE, &rlim);
133           (void)setrlimit(RLIMIT_STACK, &rlim);
134           (void)setrlimit(RLIMIT_DATA, &rlim);
135           (void)setrlimit(RLIMIT_RSS, &rlim);
136 
137           /* Don't drop core (not really necessary, but GP's). */
138           rlim.rlim_cur = rlim.rlim_max = 0;
139           (void)setrlimit(RLIMIT_CORE, &rlim);
140 
141           /* Turn off signals. */
142           (void)signal(SIGALRM, SIG_IGN);
143           (void)signal(SIGHUP, SIG_IGN);
144           (void)signal(SIGINT, SIG_IGN);
145           (void)signal(SIGPIPE, SIG_IGN);
146           (void)signal(SIGQUIT, SIG_IGN);
147           (void)signal(SIGTERM, SIG_IGN);
148           (void)signal(SIGCONT, pw_cont);
149 
150           /* Create with exact permissions. */
151           (void)umask(0);
152 #endif
153           initialized = 1;
154           return (0);
155 }
156 
157 /*
158  * Lock the master password file.
159  */
160 int
pw_lock(void)161 pw_lock(void)
162 {
163 
164           if (*masterpasswd == '\0')
165                     return (-1);
166 
167           /*
168            * If the master password file doesn't exist, the system is hosed.
169            * Might as well try to build one.  Set the close-on-exec bit so
170            * that users can't get at the encrypted passwords while editing.
171            * Open should allow flock'ing the file; see 4.4BSD.        XXX
172            */
173           for (;;) {
174                     struct stat st;
175 
176                     lockfd = flopen(masterpasswd, O_RDONLY|O_NONBLOCK|O_CLOEXEC, 0);
177                     if (lockfd == -1) {
178                               if (errno == EWOULDBLOCK) {
179                                         errx(1, "the password db file is busy");
180                               } else {
181                                         err(1, "could not lock the passwd file: ");
182                               }
183                     }
184 
185                     /*
186                      * If the password file was replaced while we were trying to
187                      * get the lock, our hardlink count will be 0 and we have to
188                      * close and retry.
189                      */
190                     if (fstat(lockfd, &st) == -1)
191                               err(1, "fstat() failed: ");
192                     if (st.st_nlink != 0)
193                               break;
194                     close(lockfd);
195                     lockfd = -1;
196           }
197           return (lockfd);
198 }
199 
200 /*
201  * Create and open a presumably safe temp file for editing the password
202  * data, and copy the master password file into it.
203  */
204 int
pw_tmp(int mfd)205 pw_tmp(int mfd)
206 {
207           char buf[8192];
208           ssize_t nr;
209           const char *p;
210           int tfd;
211 
212           if (*masterpasswd == '\0')
213                     return (-1);
214           if ((p = strrchr(masterpasswd, '/')))
215                     ++p;
216           else
217                     p = masterpasswd;
218           if (snprintf(tempname, sizeof(tempname), "%.*spw.XXXXXX",
219                     (int)(p - masterpasswd), masterpasswd) >= (int)sizeof(tempname)) {
220                     errno = ENAMETOOLONG;
221                     return (-1);
222           }
223           if ((tfd = mkstemp(tempname)) == -1)
224                     return (-1);
225           if (mfd != -1) {
226                     while ((nr = read(mfd, buf, sizeof(buf))) > 0)
227                               if (write(tfd, buf, (size_t)nr) != nr)
228                                         break;
229                     if (nr != 0) {
230                               unlink(tempname);
231                               *tempname = '\0';
232                               close(tfd);
233                               return (-1);
234                     }
235           }
236           return (tfd);
237 }
238 
239 /*
240  * Regenerate the password database.
241  */
242 int
pw_mkdb(const char * user)243 pw_mkdb(const char *user)
244 {
245           int pstat;
246           pid_t pid;
247 
248           (void)fflush(stderr);
249           switch ((pid = fork())) {
250           case -1:
251                     return (-1);
252           case 0:
253                     /* child */
254                     if (user == NULL)
255                               execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p",
256                                   "-d", passwd_dir, tempname, NULL);
257                     else
258                               execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p",
259                                   "-d", passwd_dir, "-u", user, tempname,
260                                   NULL);
261                     _exit(1);
262                     /* NOTREACHED */
263           default:
264                     /* parent */
265                     break;
266           }
267           if (waitpid(pid, &pstat, 0) == -1)
268                     return (-1);
269           if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0)
270                     return (0);
271           errno = 0;
272           return (-1);
273 }
274 
275 /*
276  * Edit the temp file.  Return -1 on error, >0 if the file was modified, 0
277  * if it was not.
278  */
279 int
pw_edit(int notsetuid)280 pw_edit(int notsetuid)
281 {
282           struct sigaction sa, sa_int, sa_quit;
283           sigset_t oldsigset, nsigset;
284           struct stat st1, st2;
285           const char *editor;
286           int pstat;
287 
288           if ((editor = getenv("EDITOR")) == NULL)
289                     editor = _PATH_VI;
290           if (stat(tempname, &st1) == -1)
291                     return (-1);
292           sa.sa_handler = SIG_IGN;
293           sigemptyset(&sa.sa_mask);
294           sa.sa_flags = 0;
295           sigaction(SIGINT, &sa, &sa_int);
296           sigaction(SIGQUIT, &sa, &sa_quit);
297           sigemptyset(&nsigset);
298           sigaddset(&nsigset, SIGCHLD);
299           sigprocmask(SIG_BLOCK, &nsigset, &oldsigset);
300           switch ((editpid = fork())) {
301           case -1:
302                     return (-1);
303           case 0:
304                     sigaction(SIGINT, &sa_int, NULL);
305                     sigaction(SIGQUIT, &sa_quit, NULL);
306                     sigprocmask(SIG_SETMASK, &oldsigset, NULL);
307                     if (notsetuid) {
308                               (void)setgid(getgid());
309                               (void)setuid(getuid());
310                     }
311                     errno = 0;
312                     execlp(editor, basename(strdup(editor)), tempname, NULL);
313                     _exit(errno);
314           default:
315                     /* parent */
316                     break;
317           }
318           for (;;) {
319                     if (waitpid(editpid, &pstat, WUNTRACED) == -1) {
320                               if (errno == EINTR)
321                                         continue;
322                               unlink(tempname);
323                               editpid = -1;
324                               break;
325                     } else if (WIFSTOPPED(pstat)) {
326                               raise(WSTOPSIG(pstat));
327                     } else if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0) {
328                               editpid = -1;
329                               break;
330                     } else {
331                               unlink(tempname);
332                               editpid = -1;
333                               break;
334                     }
335           }
336           sigaction(SIGINT, &sa_int, NULL);
337           sigaction(SIGQUIT, &sa_quit, NULL);
338           sigprocmask(SIG_SETMASK, &oldsigset, NULL);
339           if (stat(tempname, &st2) == -1)
340                     return (-1);
341           return (st1.st_mtim.tv_sec != st2.st_mtim.tv_sec ||
342               st1.st_mtim.tv_nsec != st2.st_mtim.tv_nsec);
343 }
344 
345 /*
346  * Clean up.  Preserve errno for the caller's convenience.
347  */
348 void
pw_fini(void)349 pw_fini(void)
350 {
351           int serrno, status;
352 
353           if (!initialized)
354                     return;
355           initialized = 0;
356           serrno = errno;
357           if (editpid != -1) {
358                     kill(editpid, SIGTERM);
359                     kill(editpid, SIGCONT);
360                     waitpid(editpid, &status, 0);
361                     editpid = -1;
362           }
363           if (*tempname != '\0') {
364                     unlink(tempname);
365                     *tempname = '\0';
366           }
367           if (lockfd != -1)
368                     close(lockfd);
369           errno = serrno;
370 }
371 
372 /*
373  * Compares two struct pwds.
374  */
375 int
pw_equal(const struct passwd * pw1,const struct passwd * pw2)376 pw_equal(const struct passwd *pw1, const struct passwd *pw2)
377 {
378           return (strcmp(pw1->pw_name, pw2->pw_name) == 0 &&
379               pw1->pw_uid == pw2->pw_uid &&
380               pw1->pw_gid == pw2->pw_gid &&
381               strcmp(pw1->pw_class, pw2->pw_class) == 0 &&
382               pw1->pw_change == pw2->pw_change &&
383               pw1->pw_expire == pw2->pw_expire &&
384               strcmp(pw1->pw_gecos, pw2->pw_gecos) == 0 &&
385               strcmp(pw1->pw_dir, pw2->pw_dir) == 0 &&
386               strcmp(pw1->pw_shell, pw2->pw_shell) == 0);
387 }
388 
389 /*
390  * Make a passwd line out of a struct passwd.
391  */
392 char *
pw_make(const struct passwd * pw)393 pw_make(const struct passwd *pw)
394 {
395           char *line;
396 
397           asprintf(&line, "%s:%s:%ju:%ju:%s:%ju:%ju:%s:%s:%s", pw->pw_name,
398               pw->pw_passwd, (uintmax_t)pw->pw_uid, (uintmax_t)pw->pw_gid,
399               pw->pw_class, (uintmax_t)pw->pw_change, (uintmax_t)pw->pw_expire,
400               pw->pw_gecos, pw->pw_dir, pw->pw_shell);
401           return (line);
402 }
403 
404 /*
405  * Make a passwd line (in v7 format) out of a struct passwd
406  */
407 char *
pw_make_v7(const struct passwd * pw)408 pw_make_v7(const struct passwd *pw)
409 {
410           char *line;
411 
412           asprintf(&line, "%s:*:%ju:%ju:%s:%s:%s", pw->pw_name,
413               (uintmax_t)pw->pw_uid, (uintmax_t)pw->pw_gid,
414               pw->pw_gecos, pw->pw_dir, pw->pw_shell);
415           return (line);
416 }
417 
418 /*
419  * Copy password file from one descriptor to another, replacing, deleting
420  * or adding a single record on the way.
421  */
422 int
pw_copy(int ffd,int tfd,const struct passwd * pw,struct passwd * old_pw)423 pw_copy(int ffd, int tfd, const struct passwd *pw, struct passwd *old_pw)
424 {
425           char buf[8192], *end, *line, *p, *q, *r, t;
426           struct passwd *fpw;
427           const struct passwd *spw;
428           size_t len;
429           int eof, readlen;
430 
431           if (old_pw == NULL && pw == NULL)
432                               return (-1);
433 
434           spw = old_pw;
435           /* deleting a user */
436           if (pw == NULL) {
437                     line = NULL;
438           } else {
439           if ((line = pw_make(pw)) == NULL)
440                     return (-1);
441           }
442 
443           /* adding a user */
444           if (spw == NULL)
445                     spw = pw;
446 
447           eof = 0;
448           len = 0;
449           p = q = end = buf;
450           for (;;) {
451                     /* find the end of the current line */
452                     for (p = q; q < end && *q != '\0'; ++q)
453                               if (*q == '\n')
454                                         break;
455 
456                     /* if we don't have a complete line, fill up the buffer */
457                     if (q >= end) {
458                               if (eof)
459                                         break;
460                               if ((size_t)(q - p) >= sizeof(buf)) {
461                                         warnx("passwd line too long");
462                                         errno = EINVAL; /* hack */
463                                         goto err;
464                               }
465                               if (p < end) {
466                                         q = memmove(buf, p, end - p);
467                                         end -= p - buf;
468                               } else {
469                                         p = q = end = buf;
470                               }
471                               readlen = read(ffd, end, sizeof(buf) - (end - buf));
472                               if (readlen == -1)
473                                         goto err;
474                               else
475                                         len = (size_t)readlen;
476                               if (len == 0 && p == buf)
477                                         break;
478                               end += len;
479                               len = end - buf;
480                               if (len < (ssize_t)sizeof(buf)) {
481                                         eof = 1;
482                                         if (len > 0 && buf[len - 1] != '\n')
483                                                   ++len, *end++ = '\n';
484                               }
485                               continue;
486                     }
487 
488                     /* is it a blank line or a comment? */
489                     for (r = p; r < q && isspace(*r); ++r)
490                               /* nothing */ ;
491                     if (r == q || *r == '#') {
492                               /* yep */
493                               if (write(tfd, p, q - p + 1) != q - p + 1)
494                                         goto err;
495                               ++q;
496                               continue;
497                     }
498 
499                     /* is it the one we're looking for? */
500 
501                     t = *q;
502                     *q = '\0';
503 
504                     fpw = pw_scan(r, PWSCAN_MASTER);
505 
506                     /*
507                      * fpw is either the struct passwd for the current line,
508                      * or NULL if the line is malformed.
509                      */
510 
511                     *q = t;
512                     if (fpw == NULL || strcmp(fpw->pw_name, spw->pw_name) != 0) {
513                               /* nope */
514                               if (fpw != NULL)
515                                         free(fpw);
516                               if (write(tfd, p, q - p + 1) != q - p + 1)
517                                         goto err;
518                               ++q;
519                               continue;
520                     }
521                     if (old_pw && !pw_equal(fpw, old_pw)) {
522                               warnx("entry inconsistent");
523                               free(fpw);
524                               errno = EINVAL; /* hack */
525                               goto err;
526                     }
527                     free(fpw);
528 
529                     /* it is, replace or remove it */
530                     if (line != NULL) {
531                     len = strlen(line);
532                     if (write(tfd, line, len) != (int)len)
533                               goto err;
534                     } else {
535                               /* when removed, avoid the \n */
536                               q++;
537                     }
538                     /* we're done, just copy the rest over */
539                     for (;;) {
540                               if (write(tfd, q, end - q) != end - q)
541                                         goto err;
542                               q = buf;
543                               readlen = read(ffd, buf, sizeof(buf));
544                               if (readlen == 0)
545                                         break;
546                               else
547                                         len = (size_t)readlen;
548                               if (readlen == -1)
549                                         goto err;
550                               end = buf + len;
551                     }
552                     goto done;
553           }
554 
555           /* if we got here, we didn't find the old entry */
556           if (line == NULL) {
557                     errno = ENOENT;
558                     goto err;
559           }
560           len = strlen(line);
561           if ((size_t)write(tfd, line, len) != len ||
562               write(tfd, "\n", 1) != 1)
563                     goto err;
564  done:
565           if (line != NULL)
566                     free(line);
567           return (0);
568  err:
569           if (line != NULL)
570                     free(line);
571           return (-1);
572 }
573 
574 /*
575  * Return the current value of tempname.
576  */
577 const char *
pw_tempname(void)578 pw_tempname(void)
579 {
580 
581           return (tempname);
582 }
583 
584 /*
585  * Duplicate a struct passwd.
586  */
587 struct passwd *
pw_dup(const struct passwd * pw)588 pw_dup(const struct passwd *pw)
589 {
590           char *dst;
591           struct passwd *npw;
592           ssize_t len;
593 
594           len = sizeof(*npw);
595           if (pw->pw_name != NULL)
596                     len += strlen(pw->pw_name) + 1;
597           if (pw->pw_passwd != NULL)
598                     len += strlen(pw->pw_passwd) + 1;
599           if (pw->pw_class != NULL)
600                     len += strlen(pw->pw_class) + 1;
601           if (pw->pw_gecos != NULL)
602                     len += strlen(pw->pw_gecos) + 1;
603           if (pw->pw_dir != NULL)
604                     len += strlen(pw->pw_dir) + 1;
605           if (pw->pw_shell != NULL)
606                     len += strlen(pw->pw_shell) + 1;
607           if ((npw = malloc((size_t)len)) == NULL)
608                     return (NULL);
609           memcpy(npw, pw, sizeof(*npw));
610           dst = (char *)npw + sizeof(*npw);
611           if (pw->pw_name != NULL) {
612                     npw->pw_name = dst;
613                     dst = stpcpy(npw->pw_name, pw->pw_name) + 1;
614           }
615           if (pw->pw_passwd != NULL) {
616                     npw->pw_passwd = dst;
617                     dst = stpcpy(npw->pw_passwd, pw->pw_passwd) + 1;
618           }
619           if (pw->pw_class != NULL) {
620                     npw->pw_class = dst;
621                     dst = stpcpy(npw->pw_class, pw->pw_class) + 1;
622           }
623           if (pw->pw_gecos != NULL) {
624                     npw->pw_gecos = dst;
625                     dst = stpcpy(npw->pw_gecos, pw->pw_gecos) + 1;
626           }
627           if (pw->pw_dir != NULL) {
628                     npw->pw_dir = dst;
629                     dst = stpcpy(npw->pw_dir, pw->pw_dir) + 1;
630           }
631           if (pw->pw_shell != NULL) {
632                     npw->pw_shell = dst;
633                     dst = stpcpy(npw->pw_shell, pw->pw_shell) + 1;
634           }
635           return (npw);
636 }
637 
638 #include "pw_scan.h"
639 
640 /*
641  * Wrapper around an internal libc function
642  */
643 struct passwd *
pw_scan(const char * line,int flags)644 pw_scan(const char *line, int flags)
645 {
646           struct passwd pw, *ret;
647           char *bp;
648 
649           if ((bp = strdup(line)) == NULL)
650                     return (NULL);
651           if (!__pw_scan(bp, &pw, flags)) {
652                     free(bp);
653                     return (NULL);
654           }
655           ret = pw_dup(&pw);
656           free(bp);
657           return (ret);
658 }
659