prettyness police
[unix-history] / usr / src / usr.bin / chpass / chpass.c
index d366329..2e66285 100644 (file)
@@ -1,99 +1,69 @@
-/*
- * Copyright (c) 1988 The Regents of the University of California.
- * All rights reserved.
+/*-
+ * Copyright (c) 1988, 1993
+ *     The Regents of the University of California.  All rights reserved.
  *
  *
- * Redistribution and use in source and binary forms are permitted
- * provided that the above copyright notice and this paragraph are
- * duplicated in all such forms and that any documentation,
- * advertising materials, and other materials related to such
- * distribution and use acknowledge that the software was developed
- * by the University of California, Berkeley.  The name of the
- * University may not be used to endorse or promote products derived
- * from this software without specific prior written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ * %sccs.include.redist.c%
  */
 
 #ifndef lint
  */
 
 #ifndef lint
-char copyright[] =
-"@(#) Copyright (c) 1988 The Regents of the University of California.\n\
- All rights reserved.\n";
+static char copyright[] =
+"@(#) Copyright (c) 1988, 1993\n\
      The Regents of the University of California.  All rights reserved.\n";
 #endif /* not lint */
 
 #ifndef lint
 #endif /* not lint */
 
 #ifndef lint
-static char sccsid[] = "@(#)chpass.c   5.8 (Berkeley) %G%";
+static char sccsid[] = "@(#)chpass.c   8.2 (Berkeley) %G%";
 #endif /* not lint */
 
 #include <sys/param.h>
 #endif /* not lint */
 
 #include <sys/param.h>
-#include <sys/file.h>
 #include <sys/stat.h>
 #include <sys/signal.h>
 #include <sys/time.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/signal.h>
 #include <sys/time.h>
 #include <sys/resource.h>
-#include <pwd.h>
+
+#include <ctype.h>
+#include <err.h>
 #include <errno.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
 #include <stdio.h>
 #include <stdio.h>
-#include <ctype.h>
-#include <chpass.h>
-#include <strings.h>
-
-char e1[] = ": ";
-char e2[] = ":,";
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
 
-int p_change(), p_class(), p_expire(), p_gecos(), p_gid(), p_hdir();
-int p_login(), p_passwd(), p_shell(), p_uid();
+#include <pw_scan.h>
+#include <pw_util.h>
 
 
-struct entry list[] = {
-       { "Login",              p_login,  1,   5, e1,   },
-       { "Password",           p_passwd, 1,   8, e1,   },
-       { "Uid",                p_uid,    1,   3, e1,   },
-       { "Gid",                p_gid,    1,   3, e1,   },
-       { "Class",              p_class,  1,   5, e1,   },
-       { "Change",             p_change, 1,   6, NULL, },
-       { "Expire",             p_expire, 1,   6, NULL, },
-#define        E_NAME          7
-       { "Full Name",          p_gecos,  0,   9, e2,   },
-#define        E_BPHONE        8
-       { "Office Phone",       p_gecos,  0,  12, e2,   },
-#define        E_HPHONE        9
-       { "Home Phone",         p_gecos,  0,  10, e2,   },
-#define        E_LOCATE        10
-       { "Location",           p_gecos,  0,   8, e2,   },
-       { "Home directory",     p_hdir,   1,  14, e1,   },
-       { "Shell",              p_shell,  0,   5, e1,   },
-       { NULL, 0, },
-};
+#include "chpass.h"
+#include "pathnames.h"
 
 
+char *progname = "chpass";
+char *tempname;
 uid_t uid;
 
 uid_t uid;
 
+void   baduser __P((void));
+void   usage __P((void));
+
+int
 main(argc, argv)
        int argc;
        char **argv;
 {
 main(argc, argv)
        int argc;
        char **argv;
 {
-       extern int errno, optind;
-       extern char *optarg;
-       register char *p;
-       struct passwd lpw, *pw;
-       struct rlimit rlim;
-       FILE *temp_fp;
-       int aflag, ch, fd;
-       char *fend, *passwd, *temp, *tend;
-       char from[MAXPATHLEN], to[MAXPATHLEN];
-       char *getusershell();
+       enum { NEWSH, LOADENTRY, EDITENTRY } op;
+       struct passwd *pw, lpw;
+       int ch, pfd, tfd;
+       char *arg;
 
 
-       uid = getuid();
-       aflag = 0;
-       while ((ch = getopt(argc, argv, "a:")) != EOF)
+       op = EDITENTRY;
+       while ((ch = getopt(argc, argv, "a:s:")) != EOF)
                switch(ch) {
                case 'a':
                switch(ch) {
                case 'a':
-                       if (uid) {
-                               (void)fprintf(stderr,
-                                   "chpass: %s\n", strerror(EACCES));
-                               exit(1);
-                       }
-                       loadpw(optarg, pw = &lpw);
-                       aflag = 1;
+                       op = LOADENTRY;
+                       arg = optarg;
+                       break;
+               case 's':
+                       op = NEWSH;
+                       arg = optarg;
                        break;
                case '?':
                default:
                        break;
                case '?':
                default:
@@ -102,373 +72,94 @@ main(argc, argv)
        argc -= optind;
        argv += optind;
 
        argc -= optind;
        argv += optind;
 
-       if (!aflag)
+       uid = getuid();
+
+       if (op == EDITENTRY || op == NEWSH)
                switch(argc) {
                case 0:
                switch(argc) {
                case 0:
-                       if (!(pw = getpwuid(uid))) {
-                               (void)fprintf(stderr,
-                                   "chpass: unknown user: uid %u\n", uid);
-                               exit(1);
-                       }
+                       if (!(pw = getpwuid(uid)))
+                               errx(1, "unknown user: uid %u", uid);
                        break;
                case 1:
                        break;
                case 1:
-                       if (!(pw = getpwnam(*argv))) {
-                               (void)fprintf(stderr,
-                                   "chpass: unknown user %s.\n", *argv);
-                               exit(1);
-                       }
-                       if (uid && uid != pw->pw_uid) {
-                               (void)fprintf(stderr,
-                                   "chpass: %s\n", strerror(EACCES));
-                               exit(1);
-                       }
+                       if (!(pw = getpwnam(*argv)))
+                               errx(1, "unknown user: %s", *argv);
+                       if (uid && uid != pw->pw_uid)
+                               baduser();
                        break;
                default:
                        usage();
                }
 
                        break;
                default:
                        usage();
                }
 
-       (void)signal(SIGHUP, SIG_IGN);
-       (void)signal(SIGINT, SIG_IGN);
-       (void)signal(SIGQUIT, SIG_IGN);
-       (void)signal(SIGTSTP, SIG_IGN);
-
-       rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
-       (void)setrlimit(RLIMIT_CPU, &rlim);
-       (void)setrlimit(RLIMIT_FSIZE, &rlim);
-
-       (void)umask(0);
-
-       temp = _PATH_PTMP;
-       if ((fd = open(temp, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) {
-               if (errno == EEXIST) {
-                       (void)fprintf(stderr,
-                           "chpass: password file busy -- try again later.\n");
-                       exit(1);
-               }
-               (void)fprintf(stderr, "chpass: %s: %s; ",
-                   temp, strerror(errno));
-               goto bad;
-       }
-       if (!(temp_fp = fdopen(fd, "w"))) {
-               (void)fprintf(stderr, "chpass: can't write %s; ", temp);
-               goto bad;
+       if (op == NEWSH) {
+               /* protect p_shell -- it thinks NULL is /bin/sh */
+               if (!arg[0])
+                       usage();
+               if (p_shell(arg, pw, (ENTRY *)NULL))
+                       pw_error((char *)NULL, 0, 1);
        }
 
        }
 
-       if (!aflag && !info(pw))
-               goto bad;
-
-       /* root should have a 0 uid and a reasonable shell */
-       if (!strcmp(pw->pw_name, "root")) {
-               if (pw->pw_uid) {
-                       (void)fprintf(stderr, "chpass: root uid should be 0.");
+       if (op == LOADENTRY) {
+               if (uid)
+                       baduser();
+               pw = &lpw;
+               if (!pw_scan(arg, pw))
                        exit(1);
                        exit(1);
-               }
-               setusershell();
-               for (;;)
-                       if (!(p = getusershell())) {
-                               (void)fprintf(stderr,
-                                   "chpass: warning, unknown root shell.");
-                               break;
-                       }
-                       else if (!strcmp(pw->pw_shell, p))
-                               break;
-       }
-
-       passwd = _PATH_MASTERPASSWD;
-       if (!freopen(passwd, "r", stdin)) {
-               (void)fprintf(stderr, "chpass: can't read %s; ", passwd);
-               goto bad;
-       }
-       if (!copy(pw, temp_fp))
-               goto bad;
-
-       (void)fclose(temp_fp);
-       (void)fclose(stdin);
-
-       switch(fork()) {
-       case 0:
-               break;
-       case -1:
-               (void)fprintf(stderr, "chpass: can't fork; ");
-               goto bad;
-               /* NOTREACHED */
-       default:
-               exit(0);
-               /* NOTREACHED */
-       }
-
-       if (makedb(temp)) {
-               (void)fprintf(stderr, "chpass: mkpasswd failed; ");
-bad:           (void)fprintf(stderr, "%s unchanged.\n", _PATH_MASTERPASSWD);
-               (void)unlink(temp);
-               exit(1);
        }
 
        /*
        }
 
        /*
-        * possible race; have to rename four files, and someone could slip
-        * in between them.  LOCK_EX and rename the ``passwd.dir'' file first
-        * so that getpwent(3) can't slip in; the lock should never fail and
-        * it's unclear what to do if it does.  Rename ``ptmp'' last so that
-        * passwd/vipw/chpass can't slip in.
+        * The temporary file/file descriptor usage is a little tricky here.
+        * 1:   We start off with two fd's, one for the master password
+        *      file (used to lock everything), and one for a temporary file.
+        * 2:   Display() gets an fp for the temporary file, and copies the
+        *      user's information into it.  It then gives the temporary file
+        *      to the user and closes the fp, closing the underlying fd.
+        * 3:   The user edits the temporary file some number of times.
+        * 4:   Verify() gets an fp for the temporary file, and verifies the
+        *      contents.  It can't use an fp derived from the step #2 fd,
+        *      because the user's editor may have created a new instance of
+        *      the file.  Once the file is verified, its contents are stored
+        *      in a password structure.  The verify routine closes the fp,
+        *      closing the underlying fd.
+        * 5:   Delete the temporary file.
+        * 6:   Get a new temporary file/fd.  Pw_copy() gets an fp for it
+        *      file and copies the master password file into it, replacing
+        *      the user record with a new one.  We can't use the first
+        *      temporary file for this because it was owned by the user.
+        *      Pw_copy() closes its fp, flushing the data and closing the
+        *      underlying file descriptor.  We can't close the master
+        *      password fp, or we'd lose the lock.
+        * 7:   Call pw_mkdb() (which renames the temporary file) and exit.
+        *      The exit closes the master passwd fp/fd.
         */
         */
-       (void)setpriority(PRIO_PROCESS, 0, -20);
-       fend = strcpy(from, temp) + strlen(temp);
-       tend = strcpy(to, passwd) + strlen(passwd);
-       bcopy(".dir", fend, 5);
-       bcopy(".dir", tend, 5);
-       if ((fd = open(from, O_RDONLY, 0)) >= 0)
-               (void)flock(fd, LOCK_EX);
-       /* here we go... */
-       (void)rename(from, to);
-       bcopy(".pag", fend, 5);
-       bcopy(".pag", tend, 5);
-       (void)rename(from, to);
-       bcopy(".orig", fend, 6);
-       (void)rename(from, _PATH_PASSWD);
-       (void)rename(temp, passwd);
-       /* done! */
-       exit(0);
-}
-
-info(pw)
-       struct passwd *pw;
-{
-       struct stat begin, end;
-       FILE *fp;
-       int fd, rval;
-       char *tfile;
+       pw_init();
+       pfd = pw_lock();
+       tfd = pw_tmp();
 
 
-       tfile = "/tmp/passwd.XXXXXX";
-       if ((fd = mkstemp(tfile)) == -1 || !(fp = fdopen(fd, "w+"))) {
-               (void)fprintf(stderr, "chpass: no temporary file");
-               return(0);
+       if (op == EDITENTRY) {
+               display(tfd, pw);
+               edit(pw);
+               (void)unlink(tempname);
+               tfd = pw_tmp();
        }
        }
+               
+       pw_copy(pfd, tfd, pw);
 
 
-       print(fp, pw);
-       (void)fflush(fp);
-
-       /*
-        * give the file to the real user; setuid permissions
-        * are discarded in edit()
-        */
-       (void)fchown(fd, getuid(), getgid());
-
-       for (rval = 0;;) {
-               (void)fstat(fd, &begin);
-               if (edit(tfile)) {
-                       (void)fprintf(stderr, "chpass: edit failed; ");
-                       break;
-               }
-               (void)fstat(fd, &end);
-               if (begin.st_mtime == end.st_mtime) {
-                       (void)fprintf(stderr, "chpass: no changes made; ");
-                       break;
-               }
-               (void)rewind(fp);
-               if (check(fp, pw)) {
-                       rval = 1;
-                       break;
-               }
-               (void)fflush(stderr);
-               if (prompt())
-                       break;
-       }
-       (void)fclose(fp);
-       (void)unlink(tfile);
-       return(rval);
-}
-
-check(fp, pw)
-       FILE *fp;
-       struct passwd *pw;
-{
-       register struct entry *ep;
-       register char *p;
-       static char buf[1024];
-
-       while (fgets(buf, sizeof(buf), fp)) {
-               if (!buf[0] || buf[0] == '#')
-                       continue;
-               if (!(p = index(buf, '\n'))) {
-                       (void)fprintf(stderr, "chpass: line too long.\n");
-                       return(0);
-               }
-               *p = '\0';
-               for (ep = list;; ++ep) {
-                       if (!ep->prompt) {
-                               (void)fprintf(stderr,
-                                   "chpass: unrecognized field.\n");
-                               return(0);
-                       }
-                       if (!strncasecmp(buf, ep->prompt, ep->len)) {
-                               if (ep->restricted && uid)
-                                       break;
-                               if (!(p = index(buf, ':'))) {
-                                       (void)fprintf(stderr,
-                                           "chpass: line corrupted.\n");
-                                       return(0);
-                               }
-                               while (isspace(*++p));
-                               if (ep->except && strpbrk(p, ep->except)) {
-                                       (void)fprintf(stderr,
-                                          "chpass: illegal character in the \"%s\" field.\n",
-                                           ep->prompt);
-                                       return(0);
-                               }
-                               if ((ep->func)(p, pw, ep))
-                                       return(0);
-                               break;
-                       }
-               }
-       }
-       /*
-        * special checks...
-        *
-        * there has to be a limit on the size of the gecos fields,
-        * otherwise getpwent(3) can choke.
-        * ``if I swallow anything evil, put your fingers down my throat...''
-        *      -- The Who
-        */
-       if (strlen(list[E_NAME].save) + strlen(list[E_BPHONE].save) +
-           strlen(list[E_HPHONE].save) + strlen(list[E_LOCATE].save)
-           > 512) {
-               (void)fprintf(stderr, "chpass: gecos field too large.\n");
-               exit(1);
-       }
-       (void)sprintf(pw->pw_gecos = buf, "%s,%s,%s,%s",
-           list[E_NAME].save, list[E_LOCATE].save, list[E_BPHONE].save,
-           list[E_HPHONE].save);
-       return(1);
-}
-
-copy(pw, fp)
-       struct passwd *pw;
-       FILE *fp;
-{
-       register int done;
-       register char *p;
-       char buf[1024];
-
-       for (done = 0; fgets(buf, sizeof(buf), stdin);) {
-               /* skip lines that are too big */
-               if (!index(buf, '\n')) {
-                       (void)fprintf(stderr, "chpass: line too long; ");
-                       return(0);
-               }
-               if (done) {
-                       (void)fprintf(fp, "%s", buf);
-                       continue;
-               }
-               if (!(p = index(buf, ':'))) {
-                       (void)fprintf(stderr, "chpass: corrupted entry; ");
-                       return(0);
-               }
-               *p = '\0';
-               if (strcmp(buf, pw->pw_name)) {
-                       *p = ':';
-                       (void)fprintf(fp, "%s", buf);
-                       continue;
-               }
-               (void)fprintf(fp, "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n",
-                   pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid,
-                   pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
-                   pw->pw_dir, pw->pw_shell);
-               done = 1;
-       }
-       if (!done)
-               (void)fprintf(fp, "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n",
-                   pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid,
-                   pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
-                   pw->pw_dir, pw->pw_shell);
-       return(1);
-}
-
-makedb(file)
-       char *file;
-{
-       int status, pid, w;
-
-       if (!(pid = vfork())) {
-               execl(_PATH_MKPASSWD, "mkpasswd", "-p", file, NULL);
-               _exit(127);
-       }
-       while ((w = wait(&status)) != pid && w != -1);
-       return(w == -1 || status);
-}
-
-edit(file)
-       char *file;
-{
-       int status, pid, w;
-       char *p, *editor, *getenv();
-
-       if (editor = getenv("EDITOR")) {
-               if (p = rindex(editor, '/'))
-                       ++p;
-               else
-                       p = editor;
-       }
-       else
-               p = editor = "vi";
-       if (!(pid = vfork())) {
-               (void)setuid(getuid());
-               (void)setgid(getgid());
-               execlp(editor, p, file, NULL);
-               _exit(127);
-       }
-       while ((w = wait(&status)) != pid && w != -1);
-       return(w == -1 || status);
-}
-
-loadpw(arg, pw)
-       char *arg;
-       register struct passwd *pw;
-{
-       register char *cp;
-       long atol();
-       char *strsep();
-
-       pw->pw_name = strsep(arg, ":");
-       pw->pw_passwd = strsep((char *)NULL, ":");
-       if (!(cp = strsep((char *)NULL, ":")))
-               goto bad;
-       pw->pw_uid = atoi(cp);
-       if (!(cp = strsep((char *)NULL, ":")))
-               goto bad;
-       pw->pw_gid = atoi(cp);
-       pw->pw_class = strsep((char *)NULL, ":");
-       if (!(cp = strsep((char *)NULL, ":")))
-               goto bad;
-       pw->pw_change = atol(cp);
-       if (!(cp = strsep((char *)NULL, ":")))
-               goto bad;
-       pw->pw_expire = atol(cp);
-       pw->pw_gecos = strsep((char *)NULL, ":");
-       pw->pw_dir = strsep((char *)NULL, ":");
-       pw->pw_shell = strsep((char *)NULL, ":");
-       if (!pw->pw_shell || strsep((char *)NULL, ":")) {
-bad:           (void)fprintf(stderr, "chpass: bad password list.\n");
-               exit(1);
-       }
+       if (!pw_mkdb())
+               pw_error((char *)NULL, 0, 1);
+       exit(0);
 }
 
 }
 
-prompt()
+void
+baduser()
 {
 {
-       register int c;
 
 
-       for (;;) {
-               (void)printf("re-edit the password file? [y]: ");
-               (void)fflush(stdout);
-               c = getchar();
-               if (c != EOF && c != (int)'\n')
-                       while (getchar() != (int)'\n');
-               return(c == (int)'n');
-       }
-       /* NOTREACHED */
+       errx(1, "%s", strerror(EACCES));
 }
 
 }
 
+void
 usage()
 {
 usage()
 {
-       (void)fprintf(stderr, "usage: chpass [-a list] [user]\n");
+
+       (void)fprintf(stderr, "usage: chpass [-a list] [-s shell] [user]\n");
        exit(1);
 }
        exit(1);
 }