root/usr/src/cmd/ypcmd/yppasswd/yppasswdd.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <crypt.h>
#include <errno.h>
#include <tiuser.h>
#include <netdir.h>
#include <pwd.h>
#include <shadow.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/yppasswd.h>
#include <netconfig.h>
#include <deflt.h>

/* N2L includes */
#include <ndbm.h>
#include "shim.h"
#include "yptol.h"

/* must match sizes in passwd */
#define STRSIZE 100

#define DEFDIR "/etc/"
#define MYPASSWD "passwd"
#define MYSHADOW "shadow"
#define DEFAULT_YPPASSWDD "/etc/default/yppasswdd"
#define YPPASSWDD_STR "check_restricted_shell_name=1"

/* The guts are in there */
extern void changepasswd(SVCXPRT *);

static void     boilerplate(struct svc_req *rqstp, SVCXPRT *transp);
static void     unlimit(int lim);
bool_t          validloginshell(char *sh, char *arg, int);
int             validstr(char *str, size_t size);

extern char  *getusershell(void);
extern void   setusershell(void);
extern void   endusershell(void);

int  Argc;
char **Argv;
int  mflag;                     /* do a make */
int Mstart;
int single = 0;
int nogecos = 0;
int noshell = 0;
int nopw = 0;
int useadjunct = 0;
int useshadow = 0;

static char *defshell = "/bin/sh";

/* These are the various reasons we might exit. */
enum exitstat {
    Esuccess,
    EminusDandfiles,
    Emissingdir,
    Emissingadjunct,
    Eaccesspasswd,
    Eaccessshadow,
    Echdir,
    Egetnetconfigent,
    Et_open,
    Enetdir_rsvdport,
    Et_sync,
    Et_info,
    Esvc_create,
    Esvc_reg,
    Esvcrun_ret,
    ElockFail,
    EparseFail
};

static char err_usage[] =
"Usage:\n"
"        rpc.yppasswdd [-D directory | passwd [passwd.adjunct]]\n"
"                      [-nopw] [-nogecos]\n"
"                      [-noshell] [-m arg1 arg2 ...]\n"
"where\n"
"        directory is the directory where the passwd, shadow and/or\n"
"        passwd.adjunct files are found (/etc by default)\n"
"        It should match the setting of PWDIR in /var/yp/Makefile\n\n"
"        Alternatively, the old 4.1.x syntax is supported where\n"
"        passwd is the path to the passwd file\n"
"        passwd.adjunct is the patch to the passwd.adjunct file\n"
"        NOTES:\n"
"         1. The -D option and the passwd/passwd.adjunct arguments are\n"
"            mutually exclusive\n"
"         2. The old syntax deprecated and will be removed in a future\n"
"            release\n"
"         3. A shadow file found in the same directory as the passwd\n"
"            will be assumed to contain the password information\n\n"
"        arguments after -m are passed to make(1S) after password changes\n"
"        -nopw passwords may not be changed remotely using passwd\n"
"        -nogecos full name may not be changed remotely using passwd or chfn\n"
"        -noshell shell may not be changed remotely using passwd or chsh\n";

char passwd_file[FILENAME_MAX], shadow_file[FILENAME_MAX];
char lockfile[FILENAME_MAX], adjunct_file[FILENAME_MAX];

int
main(int argc, char **argv)
{
        SVCXPRT *transp4, *transp6, *transpl;
        struct netconfig *nconf4, *nconf6, *nconfl;
        int i, tli4, tli6, stat;
        int errorflag;
        int dfexcl; /* -D or files, not both flag */
        enum exitstat exitstatus = Esuccess;
        int connmaxrec = RPC_MAXDATASIZE;

        strcpy(passwd_file, DEFDIR MYPASSWD);
        strcpy(shadow_file, DEFDIR MYSHADOW);
        strcpy(lockfile, DEFDIR ".pwd.lock");
        strcpy(adjunct_file, DEFDIR "security/passwd.adjunct");

        Argc = argc;
        Argv = argv;

        for (i = 1, errorflag = 0, dfexcl = 0; i < argc; i++) {
                if (argv[i][0] == '-' && argv[i][1] == 'm') {
                    if (access("/usr/ccs/bin/make", X_OK) < 0)
                        fprintf(stderr,
                                "%s: /usr/ccs/bin/make is not available, "
                                "ignoring -m option",
                                argv[0]);
                    else {
                        mflag++;
                        Mstart = i;
                        break;
                    }
                } else if (argv[i][0] == '-' && argv[i][1] == 'D') {
                    switch (dfexcl) {
                    case 0:
                        if (++i < argc) {
                            strcpy(passwd_file, argv[i]);
                            strcpy(shadow_file, argv[i]);
                            strcpy(adjunct_file, argv[i]);
                            strcpy(lockfile, argv[i]);
                            if (argv[i][strlen(argv[i]) - 1] == '/') {
                                strcat(passwd_file, MYPASSWD);
                                strcat(shadow_file, MYSHADOW);
                                strcat(lockfile, ".pwd.lock");
                                strcat(adjunct_file, "security/passwd.adjunct");
                            } else {
                                strcat(passwd_file, "/" MYPASSWD);
                                strcat(shadow_file, "/" MYSHADOW);
                                strcat(lockfile, "/.pwd.lock");
                                strcat(adjunct_file,
                                        "/security/passwd.adjunct");
                            }
                            dfexcl++;
                        } else {
                            fprintf(stderr,
                                "rpc.yppasswdd: -D option requires a "
                                "directory argument\n");
                            errorflag++;
                            exitstatus = Emissingdir;
                        }
                        break;
                    case 1:
                        fprintf(stderr,
                                "rpc.yppasswdd: cannot specify passwd/"
                                "passwd.adjunct pathnames AND use -D\n");
                        errorflag++;
                        dfexcl++;
                        exitstatus = EminusDandfiles;
                        break;
                    default:
                        break;
                    }
        /* -single: Allow user to change only one of password,  */
        /*              shell, or full name at a time.  (WHY?)  */
        /*      else if (strcmp(argv[i], "-single") == 0)       */
        /*          single = 1;                                 */
        /*      else if (strcmp(argv[i], "-nosingle") == 0)     */
        /*          single = 0;                                 */
                } else if (strcmp(argv[i], "-nogecos") == 0)
                    nogecos = 1;
                else if (strcmp(argv[i], "-nopw") == 0)
                    nopw = 1;
                else if (strcmp(argv[i], "-noshell") == 0)
                    noshell = 1;
                else if (argv[i][0] != '-') {
                        /*
                         * If we find a shadow file, we warn that we're
                         * using it in addition to warning that the user
                         * it using a deprecated syntax.
                         */
                    errorflag++;
                    switch (dfexcl) {
                    case 0:
                        strcpy(passwd_file, argv[i]);
                        memset(shadow_file, 0, sizeof (shadow_file));
                        strncpy(shadow_file, argv[i],
                                strrchr(argv[i], '/') - argv[i] + 1);
                        strcat(shadow_file, MYSHADOW);
                        fprintf(stderr,
                                "rpc.yppasswdd: specifying the password file"
                                " on the command line is \n"
                                "               obsolete, "
                                "consider using the -D option instead.\n");
                        if (access(shadow_file, F_OK) == 0) {
                            fprintf(stderr,
                                    "rpc.yppasswdd: found a shadow file in "
                                    "the same directory as %s\n"
                                    "               It will be used.\n",
                                    passwd_file);
                        }
                        if (i + 1 < argc && argv[i+1][0] != '-') {
                            strcpy(adjunct_file, argv[++i]);
                            if (access(adjunct_file, F_OK) != 0) {
                                fprintf(stderr,
                                        "rpc.yppasswdd: adjunct file %s "
                                        "not found\n",
                                        adjunct_file);
                                exitstatus = Emissingadjunct;
                            }
                        }
                        dfexcl++;
                        break;
                    case 1:
                        fprintf(stderr,
                                "rpc.yppasswdd: cannot specify passwd/"
                                "passwd.adjunct pathnames AND use -D\n");
                        dfexcl++;
                        exitstatus = EminusDandfiles;
                        break;
                    default:
                        break;
                    }
                } else {
                    errorflag++;
                    fprintf(stderr,
                            "rpc.yppasswdd: unrecognized option %s ignored\n",
                            argv[i]);
                }
        }

        if (errorflag)
                fprintf(stderr, err_usage);

        if (exitstatus)
                exit(exitstatus);

        if (access(passwd_file, W_OK) < 0) {
                fprintf(stderr, "rpc.yppasswdd: can't access %s\n",
                        passwd_file);
                exitstatus = Eaccesspasswd;
        }
        if (access(shadow_file, W_OK) == 0) {
                useshadow = 1;
        } else {
                /* We don't demand a shadow file unless we're looking at /etc */
                if (strcmp(DEFDIR MYSHADOW, shadow_file) == 0) {
                    fprintf(stderr, "rpc.yppasswdd: can't access %s\n",
                                shadow_file);
                    exitstatus = Eaccessshadow;
                }
        }
        if (access(adjunct_file, W_OK) == 0) {
                /* using an adjunct file */
                useadjunct = 1;
        }

        if (chdir("/var/yp") < 0) {
                fprintf(stderr, "rpc.yppasswdd: can't chdir to /var/yp\n");
                exitstatus = Echdir;
        }

        if (exitstatus)
                exit(exitstatus);

        if (errorflag)
                fprintf(stderr, "\nProceeding.\n");


        /*
         * Initialize locking system.
         * This is required for N2L version which accesses the DBM files.
         * For the non N2L version this sets up some locking which, since non
         * N2L mode does not access the DBM files, will be unused.
         *
         * This also sets up yptol_mode.
         */
        if (!init_lock_system(TRUE)) {
                fprintf(stderr,
                        "rpc.yppasswdd: Cant initialize locking system\n");
                exit(ElockFail);
        }

#ifndef DEBUG
        /* Close everything, but stdin/stdout/stderr */
        closefrom(3);
#endif

        if (yptol_mode) {
                stat = parseConfig(NULL, NTOL_MAP_FILE);
                if (stat == 1) {
                        fprintf(stderr, "yppasswdd : NIS to LDAP mapping"
                                                        " inactive.\n");
                } else if (stat != 0) {
                        fprintf(stderr, "yppasswdd : Aborting after NIS to LDAP"
                                                        " mapping error.\n");
                        exit(EparseFail);
                }
        }

#ifndef DEBUG
        /* Wack umask that we inherited from parent */
        umask(0);

        /* Be a midwife to ourselves */
        if (fork())
                exit(Esuccess);

        /* Disassociation is hard to do, la la la */
        setpgrp();
        setsid();

        /* Ignore stuff */
        signal(SIGHUP, SIG_IGN);
        signal(SIGINT, SIG_IGN);
        signal(SIGWINCH, SIG_IGN);
        signal(SIGTSTP, SIG_IGN);
        signal(SIGTTIN, SIG_IGN);
        signal(SIGTTOU, SIG_IGN);
        signal(SIGCHLD, SIG_IGN);

        /*
         * Just in case that wasn't enough, let's fork
         * again.  (per Stevens).
         */
        if (fork())
                exit(Esuccess);

        /*
         * We need stdin, stdout, and stderr later when we
         * fork a make(1).
         */
        freopen("/dev/null", "r+", stdin);
        freopen("/dev/null", "r+", stdout);
        freopen("/dev/null", "r+", stderr);
#endif

        openlog("yppasswdd", LOG_CONS | LOG_PID, LOG_AUTH);
        unlimit(RLIMIT_CPU);
        unlimit(RLIMIT_FSIZE);

        /*
         * Set non-blocking mode and maximum record size for
         * connection oriented RPC transports.
         */
        if (!rpc_control(RPC_SVC_CONNMAXREC_SET, &connmaxrec)) {
                syslog(LOG_INFO, "unable to set maximum RPC record size");
        }

        nconf4 = getnetconfigent("udp");
        nconf6 = getnetconfigent("udp6");
        if (nconf4 == 0 && nconf6 == 0) {
                syslog(LOG_ERR, "udp/udp6 transport not supported\n");
                exit(Egetnetconfigent);
        }

        tli4 = (nconf4 != 0) ? t_open(nconf4->nc_device, O_RDWR, NULL) : -1;
        tli6 = (nconf6 != 0) ? t_open(nconf6->nc_device, O_RDWR, NULL) : -1;

        if (tli4 == -1 && tli6 == -1) {
                syslog(LOG_ERR, "can\'t open TLI endpoint(s)\n");
                exit(Et_open);
        }

        if (tli4 != -1) {
                if (netdir_options(nconf4, ND_SET_RESERVEDPORT, tli4, NULL)) {
                        syslog(LOG_ERR, "could not set reserved port: %s\n",
                                netdir_sperror());
                        exit(Enetdir_rsvdport);
                }
        }
        if (tli6 != -1) {
                if (netdir_options(nconf6, ND_SET_RESERVEDPORT, tli6, NULL)) {
                        syslog(LOG_ERR, "could not set reserved port: %s\n",
                                netdir_sperror());
                        exit(Enetdir_rsvdport);
                }
        }
#ifdef  DEBUG
        {
                int i, tli[2];
                char *label[2] = {"udp", "udp6"};
                int state;
                struct t_info tinfo;

                tli[0] = tli4;
                tli[1] = tli6;

                for (i = 0; i < sizeof (tli)/sizeof (tli[0]); i++) {
                        fprintf(stderr, "transport %s, fd = %d\n",
                                tli[i], label[i]);
                        if ((state = t_sync(tli[i])) < 0) {
                                fprintf(stderr, "t_sync failed: %s\n",
                                        t_errlist[t_errno]);
                                exit(Et_sync);
                        }
                        if (t_getinfo(tli[i], &tinfo) < 0) {
                                fprintf(stderr, "t_getinfo failed: %s\n",
                                        t_errlist[t_errno]);
                                exit(Et_info);
                        }

                        switch (state) {
                        case T_UNBND:
                                fprintf(stderr, "TLI is unbound\n");
                                break;
                        case T_IDLE:
                                fprintf(stderr, "TLI is idle\n");
                                break;
                        case T_INREL:
                                fprintf(stderr,
                                        "other side wants to release\n");
                                break;
                        case T_INCON:
                                fprintf(stderr, "T_INCON\n");
                                break;
                        case T_DATAXFER:
                                fprintf(stderr, "T_DATAXFER\n");
                                break;
                        default:
                                fprintf(stderr, "no state info, state = %d\n",
                                        state);
                        }
                }
        }
#endif
        if (tli4 != -1) {
                rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
                        nconf4);
                transp4 = svc_tli_create(tli4, nconf4, NULL, 0, 0);
        } else {
                transp4 = 0;
        }
        if (tli6 != -1) {
                rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
                        nconf6);
                transp6 = svc_tli_create(tli6, nconf6, NULL, 0, 0);
        } else {
                transp6 = 0;
        }
        if (transp4 == 0 && transp6 == 0) {
                syslog(LOG_ERR, "yppasswdd: couldn't create an RPC server\n");
                exit(Esvc_create);
        }
        if (transp4 && !svc_reg(transp4, (ulong_t)YPPASSWDPROG,
                        (ulong_t)YPPASSWDVERS, boilerplate, nconf4)) {
                syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
                exit(Esvc_reg);
        }
        if (transp6 && !svc_reg(transp6, (ulong_t)YPPASSWDPROG,
                        (ulong_t)YPPASSWDVERS, boilerplate, nconf6)) {
                syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
                exit(Esvc_reg);
        }

        /*
         * Create a loopback RPC service for secure authentication of local
         * principals -- we need this for accepting passwd updates from
         * root on the master server.
         */
        if ((nconfl = getnetconfigent("ticlts")) == NULL) {
            syslog(LOG_ERR, "transport ticlts not supported\n");
            exit(Egetnetconfigent);
        }
        rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS, nconfl);
        transpl = svc_tli_create(RPC_ANYFD, nconfl, NULL, 0, 0);
        if (transpl == NULL) {
            syslog(LOG_ERR,
                "yppasswdd: couldn't create an loopback RPC server\n");
            exit(Esvc_create);
        }
        if (!svc_reg(transpl, (ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
                        boilerplate, nconfl)) {
            syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
            exit(Esvc_reg);
        }
        __rpc_negotiate_uid(transpl->xp_fd);
        freenetconfigent(nconf4);
        freenetconfigent(nconf6);
        freenetconfigent(nconfl);
        svc_run();
        syslog(LOG_ERR, "yppasswdd: svc_run shouldn't have returned\n");

        return (Esvcrun_ret);
        /* NOTREACHED */
}

static void
boilerplate(struct svc_req *rqstp, SVCXPRT *transp)
{
        switch (rqstp->rq_proc) {
        case NULLPROC:
                if (!svc_sendreply(transp, xdr_void, (char *)0))
                    syslog(LOG_WARNING,
                        "yppasswdd: couldn't reply to RPC call\n");
                break;
        case YPPASSWDPROC_UPDATE:
                if (yptol_mode)
                        shim_changepasswd(transp);
                else
                        changepasswd(transp);
                break;
        }
}

int
validstr(char *str, size_t size)
{
        char c;

        if (str == NULL || strlen(str) > size || strchr(str, ':'))
                return (0);
        while (c = *str++) {
                if (iscntrl(c))
                    return (0);
        }
        return (1);
}

bool_t
validloginshell(char *pw_shell, char *arg, int privileged)
{
        static char newshell[STRSIZE];
        char *cp, *valid;

        if (pw_shell == 0 || *pw_shell == '\0')
                pw_shell = defshell;

        if ((defopen(DEFAULT_YPPASSWDD)) == 0) {
                if ((defread(YPPASSWDD_STR)) != NULL) {
                        cp = strrchr(pw_shell, '/');
                        if (cp)
                                cp++;
                        else
                                cp = pw_shell;

                        if (*cp == 'r') {
                                syslog(LOG_ERR,
                                        "yppasswdd: cannot change "
                                        "from restricted shell %s\n",
                                        pw_shell);
                                return (0);
                        }
                }
                (void) defopen((char *)NULL);
        }

        for (valid = getusershell(); valid; valid = getusershell())
                if (strcmp(pw_shell, valid) == 0)
                    break;

        if (valid == NULL && !privileged) {
                syslog(LOG_ERR, "yppasswdd: Current shell is not valid: %s\n",
                        pw_shell);
                endusershell();
                return (0);
        }

        if (arg != 0) {
                strncpy(newshell, arg, sizeof (newshell) - 1);
                newshell[sizeof (newshell) - 1] = 0;
        } else {
                endusershell();
                return (0);
        }

        /*
         * Allow user to give shell name w/o preceding pathname.
         */
        setusershell();
        for (valid = getusershell(); valid; valid = getusershell()) {
                if (newshell[0] == '/') {
                    cp = valid;
                } else {
                    cp = strrchr(valid, '/');
                    if (cp == 0)
                        cp = valid;
                    else
                        cp++;
                }
                if (strcmp(newshell, cp) == 0)
                    break;
        }

        if (valid == 0) {
                if (!privileged || newshell[0] != '/') {
                        syslog(LOG_WARNING,
                                "%s is unacceptable as a new shell.\n",
                                newshell);
                        endusershell();
                        return (0);
                }
                valid = newshell;
        }

        if (access(valid, X_OK) < 0) {
                syslog(LOG_WARNING, "%s is unavailable.\n", valid);
                endusershell();
                return (0);
        }

        strncpy(newshell, valid, sizeof (newshell));
        pw_shell =  newshell;
        endusershell();
        return (1);
}

static void
unlimit(int lim)
{
        struct rlimit rlim;
        rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
        setrlimit(lim, &rlim);
}