/*      $NetBSD: chpass.c,v 1.35 2011/08/31 16:24:57 plunky Exp $       */

/*-
* Copyright (c) 1988, 1993, 1994
*      The Regents of the University of California.  All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#include <sys/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994\
The Regents of the University of California.  All rights reserved.");
#endif /* not lint */

#ifndef lint
#if 0
static char sccsid[] = "@(#)chpass.c    8.4 (Berkeley) 4/2/94";
#else
__RCSID("$NetBSD: chpass.c,v 1.35 2011/08/31 16:24:57 plunky Exp $");
#endif
#endif /* not lint */

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>
#include <libgen.h>

#include "chpass.h"
#include "pathnames.h"

static char tempname[] = "/tmp/pw.XXXXXX";
uid_t uid;
int use_yp;

void    (*Pw_error)(const char *, int, int);

#ifdef  YP
extern  int _yp_check(char **); /* buried deep inside libc */
#endif

__dead static void      baduser(void);
static void     cleanup(void);
__dead static void      usage(void);

int
main(int argc, char **argv)
{
       enum { NEWSH, LOADENTRY, EDITENTRY } op;
       struct passwd *pw, lpw, old_pw;
       int ch, dfd, pfd, tfd;
#ifdef YP
       int yflag = 0;
#endif
       char *arg, *username = NULL;

#ifdef __GNUC__
       pw = NULL;              /* XXX gcc -Wuninitialized */
       arg = NULL;
#endif
#ifdef  YP
       use_yp = _yp_check(NULL);
#endif

       op = EDITENTRY;
       while ((ch = getopt(argc, argv, "a:s:ly")) != -1)
               switch (ch) {
               case 'a':
                       op = LOADENTRY;
                       arg = optarg;
                       break;
               case 's':
                       op = NEWSH;
                       arg = optarg;
                       break;
               case 'l':
                       use_yp = 0;
                       break;
               case 'y':
#ifdef  YP
                       if (!use_yp)
                               errx(1, "YP not in use.");
                       yflag = 1;
#else
                       errx(1, "YP support not compiled in.");
#endif
                       break;
               default:
                       usage();
               }
       argc -= optind;
       argv += optind;

       uid = getuid();
       switch (argc) {
       case 0:
               /* nothing */
               break;

       case 1:
               username = argv[0];
               break;

       default:
               usage();
       }

#ifdef YP
       /*
        * We need to determine if we _really_ want to use YP.
        * If we defaulted to YP (i.e. were not given the -y flag),
        * and the master is not running rpc.yppasswdd, we check
        * to see if the user exists in the local passwd database.
        * If so, we use it, otherwise we error out.
        */
       if (use_yp && yflag == 0) {
               if (check_yppasswdd()) {
                       /*
                        * We weren't able to contact rpc.yppasswdd.
                        * Check to see if we're in the local
                        * password database.  If we are, use it.
                        */
                       if (username != NULL)
                               pw = getpwnam(username);
                       else
                               pw = getpwuid(uid);
                       if (pw != NULL)
                               use_yp = 0;
                       else {
                               warnx("master YP server not running yppasswd"
                                   " daemon.");
                               errx(1, "Can't change password.");
                       }
               }
       }
#endif

#ifdef YP
       if (use_yp)
               Pw_error = yppw_error;
       else
#endif
               Pw_error = pw_error;

#ifdef  YP
       if (op == LOADENTRY && use_yp)
               errx(1, "cannot load entry using YP.\n"
                   "\tUse the -l flag to load local.");
#endif

       if (op == EDITENTRY || op == NEWSH) {
               if (username != NULL) {
                       pw = getpwnam(username);
                       if (pw == NULL)
                               errx(1, "unknown user: %s", username);
                       if (uid && uid != pw->pw_uid)
                               baduser();
               } else {
                       pw = getpwuid(uid);
                       if (pw == NULL)
                               errx(1, "unknown user: uid %u", uid);
               }

               /* Make a copy for later verification */
               old_pw = *pw;
               old_pw.pw_gecos = strdup(old_pw.pw_gecos);
               if (!old_pw.pw_gecos) {
                       err(1, "strdup");
                       /*NOTREACHED*/
               }
       }

       if (op == NEWSH) {
               /* protect p_shell -- it thinks NULL is /bin/sh */
               if (!arg[0])
                       usage();
               if (p_shell(arg, pw, NULL))
                       (*Pw_error)(NULL, 0, 1);
       }

       if (op == LOADENTRY) {
               if (uid)
                       baduser();
               pw = &lpw;
               if (!pw_scan(arg, pw, NULL))
                       exit(1);
       }

       /* Edit the user passwd information if requested. */
       if (op == EDITENTRY) {
               struct stat sb;

               dfd = mkstemp(tempname);
               if (dfd < 0 || fcntl(dfd, F_SETFD, 1) < 0)
                       (*Pw_error)(tempname, 1, 1);
               if (atexit(cleanup)) {
                       cleanup();
                       errx(1, "couldn't register cleanup");
               }
               if (stat(dirname(tempname), &sb) == -1)
                       err(1, "couldn't stat `%s'", dirname(tempname));
               if (!(sb.st_mode & S_ISTXT))
                       errx(1, "temporary directory `%s' is not sticky",
                           dirname(tempname));

               display(tempname, dfd, pw);
               edit(tempname, pw);
       }

#ifdef  YP
       if (use_yp) {
               if (pw_yp(pw, uid))
                       yppw_error(NULL, 0, 1);
               else
                       exit(0);
               /* Will not exit from this if. */
       }
#endif  /* YP */


       /*
        * Get the passwd lock file and open the passwd file for
        * reading.
        */
       pw_init();
       tfd = pw_lock(0);
       if (tfd < 0) {
               if (errno != EEXIST)
                       err(1, "%s", _PATH_MASTERPASSWD_LOCK);
               warnx("The passwd file is busy, waiting...");
               tfd = pw_lock(10);
               if (tfd < 0) {
                       if (errno != EEXIST)
                               err(1, "%s", _PATH_MASTERPASSWD_LOCK);
                       errx(1, "The passwd file is still busy, "
                            "try again later.");
               }
       }
       if (fcntl(tfd, F_SETFD, 1) < 0)
               pw_error(_PATH_MASTERPASSWD_LOCK, 1, 1);

       pfd = open(_PATH_MASTERPASSWD, O_RDONLY, 0);
       if (pfd < 0 || fcntl(pfd, F_SETFD, 1) < 0)
               pw_error(_PATH_MASTERPASSWD, 1, 1);

       /* Copy the passwd file to the lock file, updating pw. */
       pw_copy(pfd, tfd, pw, (op == LOADENTRY) ? NULL : &old_pw);

       close(pfd);
       close(tfd);

       /* Now finish the passwd file update. */
       if (pw_mkdb(username, 0) < 0)
               pw_error(NULL, 0, 1);

       exit(0);
}

static void
baduser(void)
{

       errx(1, "%s", strerror(EACCES));
}

static void
usage(void)
{

       (void)fprintf(stderr,
           "usage: %s [-a list] [-s shell] [-l] [user]\n"
           "       %s [-a list] [-s shell] [-y] [user]\n",
           getprogname(), getprogname());
       exit(1);
}

static void
cleanup(void)
{

       (void)unlink(tempname);
}