2 * Copyright 1989 - 1994, Julianne Frances Haugh
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 #include <sys/types.h>
41 #include <selinux/selinux.h>
42 #include <selinux/av_permissions.h>
48 #include "exitcodes.h"
51 #include "prototypes.h"
58 #define SHELLS_FILE "/etc/shells"
63 static char *Prog; /* Program name */
64 static int amroot; /* Real UID is root */
65 static char loginsh[BUFSIZ]; /* Name of new login shell */
67 /* external identifiers */
69 /* local function prototypes */
70 static void usage (void);
71 static void new_fields (void);
72 static int check_shell (const char *);
73 static int restricted_shell (const char *);
76 * usage - print command line syntax and exit
78 static void usage (void)
80 fprintf (stderr, _("Usage: chsh [options] [LOGIN]\n"
83 " -h, --help display this help message and exit\n"
84 " -s, --shell SHELL new login shell for the user account\n"
90 * new_fields - change the user's login shell information interactively
92 * prompt the user for the login shell and change it according to the
93 * response, or leave it alone if nothing was entered.
95 static void new_fields (void)
97 printf (_("Enter the new value, or press ENTER for the default\n"));
98 change_field (loginsh, sizeof loginsh, _("Login Shell"));
102 * restricted_shell - return true if the named shell begins with 'r' or 'R'
104 * If the first letter of the filename is 'r' or 'R', the shell is
105 * considered to be restricted.
107 static int restricted_shell (const char *sh)
110 * Shells not listed in /etc/shells are considered to be restricted.
111 * Changed this to avoid confusion with "rc" (the plan9 shell - not
112 * restricted despite the name starting with 'r'). --marekm
114 return !check_shell (sh);
118 * check_shell - see if the user's login shell is listed in /etc/shells
120 * The /etc/shells file is read for valid names of login shells. If the
121 * /etc/shells file does not exist the user cannot set any shell unless
124 * If getusershell() is available (Linux, *BSD, possibly others), use it
125 * instead of re-implementing it.
127 static int check_shell (const char *sh)
132 #ifndef HAVE_GETUSERSHELL
137 #ifdef HAVE_GETUSERSHELL
139 while ((cp = getusershell ())) {
143 if (strcmp (cp, sh) == 0) {
150 if ((fp = fopen (SHELLS_FILE, "r")) == (FILE *) 0)
153 while (fgets (buf, sizeof (buf), fp)) {
154 if ((cp = strrchr (buf, '\n')))
160 if (strcmp (buf, sh) == 0) {
171 * chsh - this command controls changes to the user's shell
173 * The only supported option is -s which permits the the login shell to
174 * be set from the command line.
176 int main (int argc, char **argv)
178 char *user; /* User name */
179 int sflg = 0; /* -s - set shell from command line */
180 const struct passwd *pw; /* Password entry from /etc/passwd */
181 struct passwd pwent; /* New password entry */
184 pam_handle_t *pamh = NULL;
190 setlocale (LC_ALL, "");
191 bindtextdomain (PACKAGE, LOCALEDIR);
192 textdomain (PACKAGE);
195 * This command behaves different for root and non-root users.
197 amroot = getuid () == 0;
200 * Get the program name. The program name is used as a prefix to
201 * most error messages.
203 Prog = Basename (argv[0]);
208 * There is only one option, but use getopt() anyway to
209 * keep things consistent.
212 int option_index = 0;
214 static struct option long_options[] = {
215 {"help", no_argument, NULL, 'h'},
216 {"shell", required_argument, NULL, 's'},
217 {NULL, 0, NULL, '\0'}
221 getopt_long (argc, argv, "hs:", long_options,
222 &option_index)) != -1) {
229 STRFCPY (loginsh, optarg);
238 * There should be only one remaining argument at most and it should
239 * be the user's name.
241 if (argc > optind + 1)
245 * Get the name of the user to check. It is either the command line
246 * name, or the name getlogin() returns.
250 pw = xgetpwnam (user);
253 _("%s: unknown user %s\n"), Prog, user);
257 pw = get_my_pwent ();
261 ("%s: Cannot determine your user name.\n"),
265 user = xstrdup (pw->pw_name);
270 * Now we make sure this is a LOCAL password entry for this user ...
277 _("%s: cannot change user '%s' on NIS client.\n"),
280 if (!yp_get_default_domain (&nis_domain) &&
281 !yp_master (nis_domain, "passwd.byname", &nis_master)) {
284 ("%s: '%s' is the NIS master for this client.\n"),
292 * Non-privileged users are only allowed to change the shell if the
293 * UID of the user matches the current real UID.
295 if (!amroot && pw->pw_uid != getuid ()) {
296 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
299 _("You may not change the shell for %s.\n"), user);
304 * Non-privileged users are only allowed to change the shell if it
305 * is not a restricted one.
307 if (!amroot && restricted_shell (pw->pw_shell)) {
308 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
311 _("You may not change the shell for %s.\n"), user);
316 * If the UID of the user does not match the current real UID,
317 * check if the change is allowed by SELinux policy.
319 if ((pw->pw_uid != getuid ())
320 && (is_selinux_enabled () > 0)
321 && (selinux_check_passwd_access (PASSWD__CHSH) != 0)) {
322 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
325 _("You may not change the shell for %s.\n"), user);
332 * Non-privileged users are optionally authenticated (must enter
333 * the password of the user whose information is being changed)
334 * before any changes can be made. Idea from util-linux
335 * chfn/chsh. --marekm
337 if (!amroot && getdef_bool ("CHSH_AUTH"))
338 passwd_check (pw->pw_name, pw->pw_passwd, "chsh");
341 retval = PAM_SUCCESS;
344 struct passwd *pampw;
345 pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
347 retval = PAM_USER_UNKNOWN;
350 if (retval == PAM_SUCCESS) {
351 retval = pam_start ("chsh", pampw->pw_name,
356 if (retval == PAM_SUCCESS) {
357 retval = pam_authenticate (pamh, 0);
358 if (retval != PAM_SUCCESS) {
359 pam_end (pamh, retval);
363 if (retval == PAM_SUCCESS) {
364 retval = pam_acct_mgmt (pamh, 0);
365 if (retval != PAM_SUCCESS) {
366 pam_end (pamh, retval);
370 if (retval != PAM_SUCCESS) {
371 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
377 * Now get the login shell. Either get it from the password
378 * file, or use the value from the command line.
381 STRFCPY (loginsh, pw->pw_shell);
384 * If the login shell was not set on the command line, let the user
385 * interactively change it.
388 printf (_("Changing the login shell for %s\n"), user);
393 * Check all of the fields for valid information. The shell
394 * field may not contain any illegal characters. Non-privileged
395 * users are restricted to using the shells in /etc/shells.
396 * The shell must be executable by the user.
398 if (valid_field (loginsh, ":,=")) {
399 fprintf (stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
403 if (!amroot && (!check_shell (loginsh) || access (loginsh, X_OK) != 0)) {
404 fprintf (stderr, _("%s is an invalid shell.\n"), loginsh);
410 * Before going any further, raise the ulimit to prevent
411 * colliding into a lowered ulimit, and set the real UID
412 * to root to protect against unexpected signals. Any
413 * keyboard signals are set to be ignored.
416 SYSLOG ((LOG_ERR, "can't setuid(0)"));
418 fprintf (stderr, _("Cannot change ID to root.\n"));
424 * The passwd entry is now ready to be committed back to
425 * the password file. Get a lock on the file and open it.
428 SYSLOG ((LOG_WARN, "can't lock /etc/passwd"));
432 ("Cannot lock the password file; try again later.\n"));
435 if (!pw_open (O_RDWR)) {
436 SYSLOG ((LOG_ERR, "can't open /etc/passwd"));
438 fprintf (stderr, _("Cannot open the password file.\n"));
444 * Get the entry to update using pw_locate() - we want the real
445 * one from /etc/passwd, not the one from getpwnam() which could
446 * contain the shadow password if (despite the warnings) someone
447 * enables AUTOSHADOW (or SHADOW_COMPAT in libc). --marekm
449 pw = pw_locate (user);
453 _("%s: %s not found in /etc/passwd\n"), Prog, user);
458 * Make a copy of the entry, then change the shell field. The other
459 * fields remain unchanged.
462 pwent.pw_shell = loginsh;
465 * Update the passwd file entry. If there is a DBM file, update
466 * that entry as well.
468 if (!pw_update (&pwent)) {
469 SYSLOG ((LOG_ERR, "error updating passwd entry"));
471 fprintf (stderr, _("Error updating the password entry.\n"));
477 * Changes have all been made, so commit them and unlock the file.
480 SYSLOG ((LOG_ERR, "can't rewrite /etc/passwd"));
482 fprintf (stderr, _("Cannot commit password file changes.\n"));
487 SYSLOG ((LOG_ERR, "can't unlock /etc/passwd"));
489 fprintf (stderr, _("Cannot unlock the password file.\n"));
492 SYSLOG ((LOG_INFO, "changed user `%s' shell to `%s'", user, loginsh));
494 nscd_flush_cache ("passwd");
497 if (retval == PAM_SUCCESS)
498 pam_end (pamh, PAM_SUCCESS);