root/usr/src/ucbcmd/vipw/vipw.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T     */
/*        All Rights Reserved   */

/*
 * Portions of this source code were derived from Berkeley 4.3 BSD
 * under license from the Regents of the University of California.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/fcntl.h>

#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <strings.h>

/*
 * Password file editor with locking.
 */

#define DEFAULT_EDITOR  "/usr/bin/vi"

static int copyfile(char *, char *);
static int editfile(char *, char *, char *, time_t *);
static int sanity_check(char *, time_t *, char *);
static int validsh(char *);

char    *ptemp = "/etc/ptmp";
char    *stemp = "/etc/stmp";
char    *passwd = "/etc/passwd";
char    *shadow = "/etc/shadow";
char    buf[BUFSIZ];

int
main(void)
{
        int fd;
        FILE *ft, *fp;
        char *editor;
        int ok = 0;
        time_t o_mtime, n_mtime;
        struct stat osbuf, sbuf, oshdbuf, shdbuf;
        char c;

        (void)signal(SIGINT, SIG_IGN);
        (void)signal(SIGQUIT, SIG_IGN);
        (void)signal(SIGHUP, SIG_IGN);
        setbuf(stderr, (char *)NULL);

        editor = getenv("VISUAL");
        if (editor == 0)
                editor = getenv("EDITOR");
        if (editor == 0)
                editor = DEFAULT_EDITOR;

        (void)umask(0077);
        if (stat(passwd, &osbuf) < 0) {
                (void)fprintf(stderr,"vipw: can't stat passwd file.\n");
                goto bad;
        }

        if (copyfile(passwd, ptemp))
                goto bad;

        if (stat(ptemp, &sbuf) < 0) {
                (void)fprintf(stderr,
                        "vipw: can't stat ptemp file, %s unchanged\n",
                        passwd);
                goto bad;
        }

        o_mtime = sbuf.st_mtime;

        if (editfile(editor, ptemp, passwd, &n_mtime)) {
                if (sanity_check(ptemp, &n_mtime, passwd))
                        goto bad;
                if (o_mtime >= n_mtime)
                        goto bad;
        }

        ok++;
        if (o_mtime < n_mtime) {
                fprintf(stdout, "\nYou have modified the password file.\n");
                fprintf(stdout,
        "Press 'e' to edit the shadow file for consistency,\n 'q' to quit: ");
                if ((c = getchar()) == 'q') {
                        if (chmod(ptemp, (osbuf.st_mode & 0644)) < 0) {
                                (void) fprintf(stderr, "vipw: %s: ", ptemp);
                                perror("chmod");
                                goto bad;
                        }
                        if (rename(ptemp, passwd) < 0) {
                                (void) fprintf(stderr, "vipw: %s: ", ptemp);
                                perror("rename");
                                goto bad;
                        }
                        if (((osbuf.st_gid != sbuf.st_gid) ||
                                        (osbuf.st_uid != sbuf.st_uid)) &&
                        (chown(passwd, osbuf.st_uid, osbuf.st_gid) < 0)) {
                                (void) fprintf(stderr, "vipw: %s ", ptemp);
                                perror("chown");
                        }
                        goto bad;
                } else if (c == 'e') {
                        if (stat(shadow, &oshdbuf) < 0) {
                                (void) fprintf(stderr,
                                        "vipw: can't stat shadow file.\n");
                                goto bad;
                        }

                        if (copyfile(shadow, stemp))
                                goto bad;
                        if (stat(stemp, &shdbuf) < 0) {
                                (void) fprintf(stderr,
                                        "vipw: can't stat stmp file.\n");
                                goto bad;
                        }

                        if (editfile(editor, stemp, shadow, &o_mtime))
                                goto bad;
                        ok++;
                        if (chmod(ptemp, (osbuf.st_mode & 0644)) < 0) {
                                (void) fprintf(stderr, "vipw: %s: ", ptemp);
                                perror("chmod");
                                goto bad;
                        }
                        if (chmod(stemp, (oshdbuf.st_mode & 0400)) < 0) {
                                (void) fprintf(stderr, "vipw: %s: ", stemp);
                                perror("chmod");
                                goto bad;
                        }
                        if (rename(ptemp, passwd) < 0) {
                                (void) fprintf(stderr, "vipw: %s: ", ptemp);
                                perror("rename");
                                goto bad;
                        }
                        if (((osbuf.st_gid != sbuf.st_gid) ||
                                        (osbuf.st_uid != sbuf.st_uid)) &&
                        (chown(passwd, osbuf.st_uid, osbuf.st_gid) < 0)) {
                                (void) fprintf(stderr, "vipw: %s ", ptemp);
                                perror("chown");
                        }
                        if (rename(stemp, shadow) < 0) {
                                (void) fprintf(stderr, "vipw: %s: ", stemp);
                                perror("rename");
                                goto bad;
                        } else if (((oshdbuf.st_gid != shdbuf.st_gid) ||
                                        (oshdbuf.st_uid != shdbuf.st_uid)) &&
                        (chown(shadow, oshdbuf.st_uid, oshdbuf.st_gid) < 0)) {
                                (void) fprintf(stderr, "vipw: %s ", stemp);
                                perror("chown");
                                }
                }
        }
bad:
        (void) unlink(ptemp);
        (void) unlink(stemp);
        return (ok ? 0 : 1);
        /* NOTREACHED */
}


int
copyfile(char *from, char *to)
{
        int fd;
        FILE *fp, *ft;

        fd = open(to, O_WRONLY|O_CREAT|O_EXCL, 0600);
        if (fd < 0) {
                if (errno == EEXIST) {
                        (void) fprintf(stderr, "vipw: %s file busy\n", from);
                        exit(1);
                }
                (void) fprintf(stderr, "vipw: "); perror(to);
                exit(1);
        }
        ft = fdopen(fd, "w");
        if (ft == NULL) {
                (void) fprintf(stderr, "vipw: "); perror(to);
                return( 1 );
        }
        fp = fopen(from, "r");
        if (fp == NULL) {
                (void) fprintf(stderr, "vipw: "); perror(from);
                return( 1 );
        }
        while (fgets(buf, sizeof (buf) - 1, fp) != NULL)
                fputs(buf, ft);
        (void) fclose(ft);
        (void) fclose(fp);
        return( 0 );
}

int
editfile(char *editor, char *temp, char *orig, time_t *mtime)
{
        (void)sprintf(buf, "%s %s", editor, temp);
        if (system(buf) == 0) {
                return (sanity_check(temp, mtime, orig));
        }
        return(1);
}


int
validsh(char *rootsh)
{

        char    *sh, *getusershell();
        int     ret = 0;

        setusershell();
        while((sh = getusershell()) != NULL ) {
                if( strcmp( rootsh, sh) == 0 ) {
                        ret = 1;
                        break;
                }
        }
        endusershell();
        return(ret);
}

/*
 * sanity checks
 * return 0 if ok, 1 otherwise
 */
int
sanity_check(char *temp, time_t *mtime, char *orig)
{
        int i, ok = 0;
        FILE *ft;
        struct stat sbuf, statbuf;
        char *ldir;
        int isshadow = 0;

        if (!strcmp(orig, shadow))
                isshadow = 1;

        /* sanity checks */
        if (stat(temp, &sbuf) < 0) {
                (void)fprintf(stderr,
                    "vipw: can't stat %s file, %s unchanged\n",
                    temp, orig);
                return(1);
        }
        *mtime = sbuf.st_mtime;
        if (sbuf.st_size == 0) {
                (void)fprintf(stderr, "vipw: bad %s file, %s unchanged\n",
                    temp, orig);
                return(1);
        }
        ft = fopen(temp, "r");
        if (ft == NULL) {
                (void)fprintf(stderr,
                    "vipw: can't reopen %s file, %s unchanged\n",
                    temp, orig);
                return(1);
        }

        while (fgets(buf, sizeof (buf) - 1, ft) != NULL) {
                char *cp;

                cp = index(buf, '\n');
                if (cp == 0)
                        continue;       /* ??? allow very long lines
                                         * and passwd files that do
                                         * not end in '\n' ???
                                         */
                *cp = '\0';

                cp = index(buf, ':');
                if (cp == 0)            /* lines without colon
                                         * separated fields
                                         */
                        continue;
                *cp = '\0';

                if (strcmp(buf, "root"))
                        continue;

                /* root password */
                *cp = ':';
                cp = index(cp + 1, ':');
                if (cp == 0)
                        goto bad_root;

                /* root uid for password */
                if (!isshadow)
                        if (atoi(cp + 1) != 0) {

                                (void)fprintf(stderr, "root UID != 0:\n%s\n",
                                    buf);
                                break;
                        }
                /* root uid for passwd and sp_lstchg for shadow */
                cp = index(cp + 1, ':');
                if (cp == 0)
                        goto bad_root;

                /* root's gid for passwd and sp_min for shadow*/
                cp = index(cp + 1, ':');
                if (cp == 0)
                        goto bad_root;

                /* root's gecos for passwd and sp_max for shadow*/
                cp = index(cp + 1, ':');
                if (isshadow) {
                        for (i=0; i<3; i++)
                                if ((cp = index(cp + 1, ':')) == 0)
                                        goto bad_root;
                } else {
                        if (cp == 0) {
bad_root:               (void)fprintf(stderr,
                                    "Missing fields in root entry:\n%s\n", buf);
                                break;
                        }
                }
                if (!isshadow) {
                        /* root's login directory */
                        ldir = ++cp;
                        cp = index(cp, ':');
                        if (cp == 0)
                                goto bad_root;
                        *cp = '\0';
                        if (stat(ldir, &statbuf) < 0) {
                                *cp = ':';
                                (void) fprintf(stderr,
                                    "root login dir doesn't exist:\n%s\n",
                                    buf);
                                break;
                        } else if (!S_ISDIR(statbuf.st_mode)) {
                                *cp = ':';
                                (void) fprintf(stderr,
                                    "root login dir is not a directory:\n%s\n",
                                    buf);
                                break;
                        }

                        *cp = ':';
                        /* root's login shell */
                        ++cp;
                        if (*cp && ! validsh(cp)) {
                                (void)fprintf(stderr,
                                    "Invalid root shell:\n%s\n", buf);
                                break;
                        }
                }

                ok++;
        }
        (void)fclose(ft);
        if (ok)
                return(0);
        else {
                (void)fprintf(stderr,
                    "vipw: you mangled the %s file, %s unchanged\n",
                    temp, orig);
                return(1);
        }
}