]> granicus.if.org Git - shadow/blobdiff - src/chsh.c
* src/chsh.c: No needto remove lines tarting with '#' from
[shadow] / src / chsh.c
index 54636095c39e6d4b75c1d8e55ded6af4a5f2bf90..a2e8fe7f974451d1d4af241ed9f0d39a7f4d9cfd 100644 (file)
@@ -1,5 +1,8 @@
 /*
- * Copyright 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1989 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * 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 Julianne F. Haugh nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
+ * 3. The name of the copyright holders or contributors may not be used to
+ *    endorse or promote products derived from this software without
+ *    specific prior written permission.
  *
- * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH 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 JULIE HAUGH 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.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+ * HOLDERS 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 <config.h>
 
-#include "rcsid.h"
-RCSID (PKG_VER "$Id: chsh.c,v 1.20 2002/01/05 15:41:43 kloczek Exp $")
-#include <sys/types.h>
-#include <stdio.h>
+#ident "$Id$"
+
 #include <fcntl.h>
-#include <signal.h>
-#include "prototypes.h"
-#include "defines.h"
+#include <getopt.h>
 #include <pwd.h>
-#include "pwio.h"
+#include <stdio.h>
+#include <sys/types.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#include <selinux/av_permissions.h>
+#endif
+#include "defines.h"
 #include "getdef.h"
+#include "nscd.h"
+#include "prototypes.h"
 #include "pwauth.h"
-#ifdef HAVE_SHADOW_H
-#include <shadow.h>
-#endif
+#include "pwio.h"
 #ifdef USE_PAM
 #include "pam_defs.h"
 #endif
+/*@-exitarg@*/
+#include "exitcodes.h"
+
 #ifndef SHELLS_FILE
 #define SHELLS_FILE "/etc/shells"
 #endif
-/* global variables */
-static char *Prog;             /* Program name */
-static int amroot;             /* Real UID is root */
+/*
+ * Global variables
+ */
+const char *Prog;              /* Program name */
+static bool amroot;            /* Real UID is root */
 static char loginsh[BUFSIZ];   /* Name of new login shell */
+/* command line options */
+static bool sflg = false;      /* -s - set shell from command line  */
+static bool pw_locked = false;
 
 /* external identifiers */
 
-#ifdef NDBM
-extern int pw_dbm_mode;
-#endif
-
 /* local function prototypes */
-static void usage (void);
+static void fail_exit (int code);
+static void usage (int status);
 static void new_fields (void);
-static int restricted_shell (const char *);
+static bool shell_is_listed (const char *);
+static bool is_restricted_shell (const char *);
+static void process_flags (int argc, char **argv);
+static void check_perms (const struct passwd *pw);
+static void update_shell (const char *user, char *loginsh);
 
 /*
- * usage - print command line syntax and exit
+ * fail_exit - do some cleanup and exit with the given error code
  */
+static void fail_exit (int code)
+{
+       if (pw_locked) {
+               if (pw_unlock () == 0) {
+                       fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+                       SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+                       /* continue */
+               }
+       }
+
+       closelog ();
 
-static void usage (void)
+       exit (code);
+}
+
+/*
+ * usage - print command line syntax and exit
+ */
+static void usage (int status)
 {
-       fprintf (stderr, _("Usage: %s [-s shell] [name]\n"), Prog);
-       exit (1);
+       fputs (_("Usage: chsh [options] [LOGIN]\n"
+                "\n"
+                "Options:\n"
+                "  -h, --help                    display this help message and exit\n"
+                "  -s, --shell SHELL             new login shell for the user account\n"
+                "\n"), (E_SUCCESS != status) ? stderr : stdout);
+       exit (status);
 }
 
 /*
@@ -82,90 +118,109 @@ static void usage (void)
  * prompt the user for the login shell and change it according to the
  * response, or leave it alone if nothing was entered.
  */
-
 static void new_fields (void)
 {
-       printf (_
-               ("Enter the new value, or press return for the default\n"));
+       puts (_("Enter the new value, or press ENTER for the default"));
        change_field (loginsh, sizeof loginsh, _("Login Shell"));
 }
 
 /*
- * restricted_shell - return true if the named shell begins with 'r' or 'R'
+ * is_restricted_shell - return true if the shell is restricted
  *
- * If the first letter of the filename is 'r' or 'R', the shell is
- * considered to be restricted.
  */
-
-static int restricted_shell (const char *sh)
+static bool is_restricted_shell (const char *sh)
 {
-#if 0
-       char *cp = Basename ((char *) sh);
-
-       return *cp == 'r' || *cp == 'R';
-#else
        /*
         * Shells not listed in /etc/shells are considered to be restricted.
         * Changed this to avoid confusion with "rc" (the plan9 shell - not
         * restricted despite the name starting with 'r').  --marekm
         */
-       return !check_shell (sh);
-#endif
+       return !shell_is_listed (sh);
 }
 
-
 /*
- * chsh - this command controls changes to the user's shell
+ * shell_is_listed - see if the user's login shell is listed in /etc/shells
  *
- *     The only supported option is -s which permits the the login shell to
- *     be set from the command line.
+ * The /etc/shells file is read for valid names of login shells.  If the
+ * /etc/shells file does not exist the user cannot set any shell unless
+ * they are root.
+ *
+ * If getusershell() is available (Linux, *BSD, possibly others), use it
+ * instead of re-implementing it.
  */
-
-int main (int argc, char **argv)
+static bool shell_is_listed (const char *sh)
 {
-       char *user;             /* User name                         */
-       int flag;               /* Current command line flag         */
-       int sflg = 0;           /* -s - set shell from command line  */
-       const struct passwd *pw;        /* Password entry from /etc/passwd   */
-       struct passwd pwent;    /* New password entry                */
+       char *cp;
+       bool found = false;
 
-       sanitize_env ();
-
-       setlocale (LC_ALL, "");
-       bindtextdomain (PACKAGE, LOCALEDIR);
-       textdomain (PACKAGE);
-
-       /*
-        * This command behaves different for root and non-root users.
-        */
-
-       amroot = getuid () == 0;
-#ifdef NDBM
-       pw_dbm_mode = O_RDWR;
+#ifndef HAVE_GETUSERSHELL
+       char buf[BUFSIZ];
+       FILE *fp;
 #endif
 
-       /*
-        * Get the program name. The program name is used as a prefix to
-        * most error messages.
-        */
+#ifdef HAVE_GETUSERSHELL
+       setusershell ();
+       while ((cp = getusershell ())) {
+               if (strcmp (cp, sh) == 0) {
+                       found = true;
+                       break;
+               }
+       }
+       endusershell ();
+#else
+       fp = fopen (SHELLS_FILE, "r");
+       if (NULL == fp) {
+               return false;
+       }
 
-       Prog = Basename (argv[0]);
+       while (fgets (buf, sizeof (buf), fp) == buf) {
+               cp = strrchr (buf, '\n');
+               if (NULL != cp) {
+                       *cp = '\0';
+               }
 
-       OPENLOG ("chsh");
+               if (buf[0] == '#') {
+                       continue;
+               }
 
-       /*
-        * There is only one option, but use getopt() anyway to
-        * keep things consistent.
-        */
+               if (strcmp (buf, sh) == 0) {
+                       found = true;
+                       break;
+               }
+       }
+       fclose (fp);
+#endif
+       return found;
+}
 
-       while ((flag = getopt (argc, argv, "s:")) != EOF) {
-               switch (flag) {
+/*
+ *  * process_flags - parse the command line options
+ *
+ *     It will not return if an error is encountered.
+ */
+static void process_flags (int argc, char **argv)
+{
+       int option_index = 0;
+       int c;
+       static struct option long_options[] = {
+               {"help", no_argument, NULL, 'h'},
+               {"shell", required_argument, NULL, 's'},
+               {NULL, 0, NULL, '\0'}
+       };
+
+       while ((c =
+               getopt_long (argc, argv, "hs:", long_options,
+                            &option_index)) != -1) {
+               switch (c) {
+               case 'h':
+                       usage (E_SUCCESS);
+                       break;
                case 's':
-                       sflg++;
+                       sflg = true;
                        STRFCPY (loginsh, optarg);
                        break;
                default:
-                       usage ();
+                       usage (E_USAGE);
                }
        }
 
@@ -173,135 +228,119 @@ int main (int argc, char **argv)
         * There should be only one remaining argument at most and it should
         * be the user's name.
         */
-
-       if (argc > optind + 1)
-               usage ();
-
-       /*
-        * Get the name of the user to check. It is either the command line
-        * name, or the name getlogin() returns.
-        */
-
-       if (optind < argc) {
-               user = argv[optind];
-               pw = getpwnam (user);
-               if (!pw) {
-                       fprintf (stderr,
-                                _("%s: Unknown user %s\n"), Prog, user);
-                       exit (1);
-               }
-       } else {
-               pw = get_my_pwent ();
-               if (!pw) {
-                       fprintf (stderr,
-                                _
-                                ("%s: Cannot determine your user name.\n"),
-                                Prog);
-                       exit (1);
-               }
-               user = xstrdup (pw->pw_name);
+       if (argc > (optind + 1)) {
+               usage (E_USAGE);
        }
+}
 
-#ifdef USE_NIS
-       /*
-        * Now we make sure this is a LOCAL password entry for this user ...
-        */
-
-       if (__ispwNIS ()) {
-               char *nis_domain;
-               char *nis_master;
-
-               fprintf (stderr,
-                        _("%s: cannot change user `%s' on NIS client.\n"),
-                        Prog, user);
-
-               if (!yp_get_default_domain (&nis_domain) &&
-                   !yp_master (nis_domain, "passwd.byname",
-                               &nis_master)) {
-                       fprintf (stderr,
-                                _
-                                ("%s: `%s' is the NIS master for this client.\n"),
-                                Prog, nis_master);
-               }
-               exit (1);
-       }
+/*
+ * check_perms - check if the caller is allowed to add a group
+ *
+ *     Non-root users are only allowed to change their shell, if their current
+ *     shell is not a restricted shell.
+ *
+ *     Non-root users must be authenticated.
+ *
+ *     It will not return if the user is not allowed.
+ */
+static void check_perms (const struct passwd *pw)
+{
+#ifdef USE_PAM
+       pam_handle_t *pamh = NULL;
+       int retval;
+       struct passwd *pampw;
 #endif
 
        /*
         * Non-privileged users are only allowed to change the shell if the
         * UID of the user matches the current real UID.
         */
-
        if (!amroot && pw->pw_uid != getuid ()) {
-               SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
-               closelog ();
+               SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
                fprintf (stderr,
-                        _("You may not change the shell for %s.\n"),
-                        user);
-               exit (1);
+                        _("You may not change the shell for '%s'.\n"),
+                        pw->pw_name);
+               fail_exit (1);
        }
 
        /*
         * Non-privileged users are only allowed to change the shell if it
         * is not a restricted one.
         */
-
-       if (!amroot && restricted_shell (pw->pw_shell)) {
-               SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
-               closelog ();
+       if (!amroot && is_restricted_shell (pw->pw_shell)) {
+               SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
                fprintf (stderr,
-                        _("You may not change the shell for %s.\n"),
-                        user);
-               exit (1);
+                        _("You may not change the shell for '%s'.\n"),
+                        pw->pw_name);
+               fail_exit (1);
        }
-
+#ifdef WITH_SELINUX
        /*
-          * Non-privileged users are optionally authenticated (must enter
-          * the password of the user whose information is being changed)
-          * before any changes can be made. Idea from util-linux
-          * chfn/chsh.  --marekm
+        * If the UID of the user does not match the current real UID,
+        * check if the change is allowed by SELinux policy.
         */
+       if ((pw->pw_uid != getuid ())
+           && (is_selinux_enabled () > 0)
+           && (selinux_check_passwd_access (PASSWD__CHSH) != 0)) {
+               SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
+               fprintf (stderr,
+                        _("You may not change the shell for '%s'.\n"),
+                        pw->pw_name);
+               fail_exit (1);
+       }
+#endif
 
-       if (!amroot && getdef_bool ("CHFN_AUTH"))
-               passwd_check (pw->pw_name, pw->pw_passwd, "chsh");
-
+#ifndef USE_PAM
        /*
-        * Now get the login shell. Either get it from the password
-        * file, or use the value from the command line.
+        * Non-privileged users are optionally authenticated (must enter
+        * the password of the user whose information is being changed)
+        * before any changes can be made. Idea from util-linux
+        * chfn/chsh.  --marekm
         */
+       if (!amroot && getdef_bool ("CHSH_AUTH")) {
+               passwd_check (pw->pw_name, pw->pw_passwd, "chsh");
+        }
 
-       if (!sflg)
-               STRFCPY (loginsh, pw->pw_shell);
+#else                          /* !USE_PAM */
+       pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
+       if (NULL == pampw) {
+               fprintf (stderr,
+                        _("%s: Cannot determine your user name.\n"),
+                        Prog);
+               exit (E_NOPERM);
+       }
 
-       /*
-        * If the login shell was not set on the command line, let the user
-        * interactively change it.
-        */
+       retval = pam_start ("chsh", pampw->pw_name, &conv, &pamh);
 
-       if (!sflg) {
-               printf (_("Changing the login shell for %s\n"), user);
-               new_fields ();
+       if (PAM_SUCCESS == retval) {
+               retval = pam_authenticate (pamh, 0);
        }
 
-       /*
-        * Check all of the fields for valid information. The shell
-        * field may not contain any illegal characters. Non-privileged
-        * users are restricted to using the shells in /etc/shells.
-        * The shell must be executable by the user.
-        */
+       if (PAM_SUCCESS == retval) {
+               retval = pam_acct_mgmt (pamh, 0);
+       }
 
-       if (valid_field (loginsh, ":,=")) {
-               fprintf (stderr, _("%s: Invalid entry: %s\n"), Prog,
-                        loginsh);
-               closelog ();
-               exit (1);
+       if (NULL != pamh) {
+               (void) pam_end (pamh, retval);
        }
-       if (!amroot
-           && (!check_shell (loginsh) || access (loginsh, X_OK) != 0)) {
-               fprintf (stderr, _("%s is an invalid shell.\n"), loginsh);
-               closelog ();
-               exit (1);
+       if (PAM_SUCCESS != retval) {
+               fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
+               exit (E_NOPERM);
        }
+#endif                         /* USE_PAM */
+}
+
+/*
+ * update_shell - update the user's shell in the passwd database
+ *
+ *     Commit the user's entry after changing her shell field.
+ *
+ *     It will not return in case of error.
+ */
+static void update_shell (const char *user, char *newshell)
+{
+       const struct passwd *pw;        /* Password entry from /etc/passwd   */
+       struct passwd pwent;            /* New password entry                */
 
        /*
         * Before going any further, raise the ulimit to prevent
@@ -309,12 +348,10 @@ int main (int argc, char **argv)
         * to root to protect against unexpected signals. Any
         * keyboard signals are set to be ignored.
         */
-
-       if (setuid (0)) {
+       if (setuid (0) != 0) {
                SYSLOG ((LOG_ERR, "can't setuid(0)"));
-               closelog ();
-               fprintf (stderr, _("Cannot change ID to root.\n"));
-               exit (1);
+               fputs (_("Cannot change ID to root.\n"), stderr);
+               fail_exit (1);
        }
        pwd_init ();
 
@@ -322,21 +359,16 @@ int main (int argc, char **argv)
         * The passwd entry is now ready to be committed back to
         * the password file. Get a lock on the file and open it.
         */
-
-       if (!pw_lock ()) {
-               SYSLOG ((LOG_WARN, "can't lock /etc/passwd"));
-               closelog ();
-               fprintf (stderr,
-                        _
-                        ("Cannot lock the password file; try again later.\n"));
-               exit (1);
+       if (pw_lock () == 0) {
+               fprintf (stderr, _("%s: cannot lock %s; try again later.\n"),
+                        Prog, pw_dbname ());
+               fail_exit (1);
        }
-       if (!pw_open (O_RDWR)) {
-               SYSLOG ((LOG_ERR, "can't open /etc/passwd"));
-               closelog ();
-               fprintf (stderr, _("Cannot open the password file.\n"));
-               pw_unlock ();
-               exit (1);
+       pw_locked = true;
+       if (pw_open (O_RDWR) == 0) {
+               fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
+               SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
+               fail_exit (1);
        }
 
        /*
@@ -346,12 +378,11 @@ int main (int argc, char **argv)
         * enables AUTOSHADOW (or SHADOW_COMPAT in libc).  --marekm
         */
        pw = pw_locate (user);
-       if (!pw) {
-               pw_unlock ();
+       if (NULL == pw) {
                fprintf (stderr,
-                        _("%s: %s not found in /etc/passwd\n"), Prog,
-                        user);
-               exit (1);
+                        _("%s: user '%s' does not exist in %s\n"),
+                        Prog, user, pw_dbname ());
+               fail_exit (1);
        }
 
        /*
@@ -359,53 +390,165 @@ int main (int argc, char **argv)
         * fields remain unchanged.
         */
        pwent = *pw;
-       pwent.pw_shell = loginsh;
+       pwent.pw_shell = newshell;
 
        /*
         * Update the passwd file entry. If there is a DBM file, update
         * that entry as well.
         */
-
-       if (!pw_update (&pwent)) {
-               SYSLOG ((LOG_ERR, "error updating passwd entry"));
-               closelog ();
+       if (pw_update (&pwent) == 0) {
                fprintf (stderr,
-                        _("Error updating the password entry.\n"));
-               pw_unlock ();
-               exit (1);
+                        _("%s: failed to prepare the new %s entry '%s'\n"),
+                        Prog, pw_dbname (), pwent.pw_name);
+               fail_exit (1);
        }
-#ifdef NDBM
-       if (pw_dbm_present () && !pw_dbm_update (&pwent)) {
-               SYSLOG ((LOG_ERR, "error updating DBM passwd entry"));
-               closelog ();
+
+       /*
+        * Changes have all been made, so commit them and unlock the file.
+        */
+       if (pw_close () == 0) {
+               fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
+               SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
+               fail_exit (1);
+       }
+       if (pw_unlock () == 0) {
+               fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
+               SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
+               /* continue */
+       }
+       pw_locked= false;
+}
+
+/*
+ * chsh - this command controls changes to the user's shell
+ *
+ *     The only supported option is -s which permits the the login shell to
+ *     be set from the command line.
+ */
+int main (int argc, char **argv)
+{
+       char *user;             /* User name                         */
+       const struct passwd *pw;        /* Password entry from /etc/passwd   */
+
+       sanitize_env ();
+
+       (void) setlocale (LC_ALL, "");
+       (void) bindtextdomain (PACKAGE, LOCALEDIR);
+       (void) textdomain (PACKAGE);
+
+       /*
+        * This command behaves different for root and non-root users.
+        */
+       amroot = (getuid () == 0);
+
+       /*
+        * Get the program name. The program name is used as a prefix to
+        * most error messages.
+        */
+       Prog = Basename (argv[0]);
+
+       OPENLOG ("chsh");
+
+       /* parse the command line options */
+       process_flags (argc, argv);
+
+       /*
+        * Get the name of the user to check. It is either the command line
+        * name, or the name getlogin() returns.
+        */
+       if (optind < argc) {
+               user = argv[optind];
+               pw = xgetpwnam (user);
+               if (NULL == pw) {
+                       fprintf (stderr,
+                                _("%s: user '%s' does not exist\n"), Prog, user);
+                       fail_exit (1);
+               }
+       } else {
+               pw = get_my_pwent ();
+               if (NULL == pw) {
+                       fprintf (stderr,
+                                _("%s: Cannot determine your user name.\n"),
+                                Prog);
+                       SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
+                                (unsigned long) getuid ()));
+                       fail_exit (1);
+               }
+               user = xstrdup (pw->pw_name);
+       }
+
+#ifdef USE_NIS
+       /*
+        * Now we make sure this is a LOCAL password entry for this user ...
+        */
+       if (__ispwNIS ()) {
+               char *nis_domain;
+               char *nis_master;
+
                fprintf (stderr,
-                        _("Error updating the DBM password entry.\n"));
-               pw_unlock ();
-               exit (1);
+                        _("%s: cannot change user '%s' on NIS client.\n"),
+                        Prog, user);
+
+               if (!yp_get_default_domain (&nis_domain) &&
+                   !yp_master (nis_domain, "passwd.byname", &nis_master)) {
+                       fprintf (stderr,
+                                _("%s: '%s' is the NIS master for this client.\n"),
+                                Prog, nis_master);
+               }
+               fail_exit (1);
        }
-       endpwent ();
 #endif
 
+       check_perms (pw);
+
        /*
-        * Changes have all been made, so commit them and unlock the file.
+        * Now get the login shell. Either get it from the password
+        * file, or use the value from the command line.
         */
+       if (!sflg) {
+               STRFCPY (loginsh, pw->pw_shell);
+       }
 
-       if (!pw_close ()) {
-               SYSLOG ((LOG_ERR, "can't rewrite /etc/passwd"));
-               closelog ();
-               fprintf (stderr,
-                        _("Cannot commit password file changes.\n"));
-               pw_unlock ();
-               exit (1);
+       /*
+        * If the login shell was not set on the command line, let the user
+        * interactively change it.
+        */
+       if (!sflg) {
+               printf (_("Changing the login shell for %s\n"), user);
+               new_fields ();
        }
-       if (!pw_unlock ()) {
-               SYSLOG ((LOG_ERR, "can't unlock /etc/passwd"));
-               closelog ();
-               fprintf (stderr, _("Cannot unlock the password file.\n"));
-               exit (1);
+
+       /*
+        * Check all of the fields for valid information. The shell
+        * field may not contain any illegal characters. Non-privileged
+        * users are restricted to using the shells in /etc/shells.
+        * The shell must be executable by the user.
+        */
+       if (valid_field (loginsh, ":,=\n") != 0) {
+               fprintf (stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
+               fail_exit (1);
+       }
+       if (   !amroot
+           && (   is_restricted_shell (loginsh)
+               || (access (loginsh, X_OK) != 0))) {
+               fprintf (stderr, _("%s: %s is an invalid shell\n"), Prog, loginsh);
+               fail_exit (1);
        }
-       SYSLOG ((LOG_INFO, "changed user `%s' shell to `%s'", user,
-                loginsh));
+
+       /* Even for root, warn if an invalid shell is specified. */
+       if (access (loginsh, F_OK) != 0) {
+               fprintf (stderr, _("%s: Warning: %s does not exist\n"), Prog, loginsh);
+       } else if (access (loginsh, X_OK) != 0) {
+               fprintf (stderr, _("%s: Warning: %s is not executable\n"), Prog, loginsh);
+       }
+
+       update_shell (user, loginsh);
+
+       SYSLOG ((LOG_INFO, "changed user '%s' shell to '%s'", user, loginsh));
+
+       nscd_flush_cache ("passwd");
+
        closelog ();
-       exit (0);
+       exit (E_SUCCESS);
 }
+