2 * Copyright (c) 1989 - 1994, Julianne Frances Haugh
3 * Copyright (c) 1996 - 2000, Marek Michałkiewicz
4 * Copyright (c) 2001 - 2006, Tomasz Kłoczko
5 * Copyright (c) 2007 - 2008, Nicolas François
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. The name of the copyright holders or contributors may not be used to
17 * endorse or promote products derived from this software without
18 * specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 #include <sys/types.h>
43 #include <selinux/selinux.h>
44 #include <selinux/av_permissions.h>
49 #include "prototypes.h"
56 #include "exitcodes.h"
59 #define SHELLS_FILE "/etc/shells"
64 char *Prog; /* Program name */
65 static bool amroot; /* Real UID is root */
66 static char loginsh[BUFSIZ]; /* Name of new login shell */
67 /* command line options */
68 static bool sflg = false; /* -s - set shell from command line */
69 static bool pw_locked = false;
71 /* external identifiers */
73 /* local function prototypes */
74 static void fail_exit (int code);
75 static void usage (int status);
76 static void new_fields (void);
77 static bool shell_is_listed (const char *);
78 static bool is_restricted_shell (const char *);
79 static void process_flags (int argc, char **argv);
80 static void check_perms (const struct passwd *pw);
81 static void update_shell (const char *user, char *loginsh);
84 * fail_exit - do some cleanup and exit with the given error code
86 static void fail_exit (int code)
89 if (pw_unlock () == 0) {
90 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
91 SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
102 * usage - print command line syntax and exit
104 static void usage (int status)
106 fputs (_("Usage: chsh [options] [LOGIN]\n"
109 " -h, --help display this help message and exit\n"
110 " -s, --shell SHELL new login shell for the user account\n"
111 "\n"), (E_SUCCESS != status) ? stderr : stdout);
116 * new_fields - change the user's login shell information interactively
118 * prompt the user for the login shell and change it according to the
119 * response, or leave it alone if nothing was entered.
121 static void new_fields (void)
123 puts (_("Enter the new value, or press ENTER for the default"));
124 change_field (loginsh, sizeof loginsh, _("Login Shell"));
128 * is_restricted_shell - return true if the shell is restricted
131 static bool is_restricted_shell (const char *sh)
134 * Shells not listed in /etc/shells are considered to be restricted.
135 * Changed this to avoid confusion with "rc" (the plan9 shell - not
136 * restricted despite the name starting with 'r'). --marekm
138 return !shell_is_listed (sh);
142 * shell_is_listed - see if the user's login shell is listed in /etc/shells
144 * The /etc/shells file is read for valid names of login shells. If the
145 * /etc/shells file does not exist the user cannot set any shell unless
148 * If getusershell() is available (Linux, *BSD, possibly others), use it
149 * instead of re-implementing it.
151 static bool shell_is_listed (const char *sh)
156 #ifndef HAVE_GETUSERSHELL
161 #ifdef HAVE_GETUSERSHELL
163 while ((cp = getusershell ())) {
168 if (strcmp (cp, sh) == 0) {
175 fp = fopen (SHELLS_FILE, "r");
180 while (fgets (buf, sizeof (buf), fp) == buf) {
181 cp = strrchr (buf, '\n');
190 if (strcmp (buf, sh) == 0) {
201 * * process_flags - parse the command line options
203 * It will not return if an error is encountered.
205 static void process_flags (int argc, char **argv)
207 int option_index = 0;
209 static struct option long_options[] = {
210 {"help", no_argument, NULL, 'h'},
211 {"shell", required_argument, NULL, 's'},
212 {NULL, 0, NULL, '\0'}
216 getopt_long (argc, argv, "hs:", long_options,
217 &option_index)) != -1) {
224 STRFCPY (loginsh, optarg);
232 * There should be only one remaining argument at most and it should
233 * be the user's name.
235 if (argc > (optind + 1)) {
241 * check_perms - check if the caller is allowed to add a group
243 * Non-root users are only allowed to change their shell, if their current
244 * shell is not a restricted shell.
246 * Non-root users must be authenticated.
248 * It will not return if the user is not allowed.
250 static void check_perms (const struct passwd *pw)
253 pam_handle_t *pamh = NULL;
255 struct passwd *pampw;
259 * Non-privileged users are only allowed to change the shell if the
260 * UID of the user matches the current real UID.
262 if (!amroot && pw->pw_uid != getuid ()) {
263 SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
265 _("You may not change the shell for '%s'.\n"),
271 * Non-privileged users are only allowed to change the shell if it
272 * is not a restricted one.
274 if (!amroot && is_restricted_shell (pw->pw_shell)) {
275 SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
277 _("You may not change the shell for '%s'.\n"),
283 * If the UID of the user does not match the current real UID,
284 * check if the change is allowed by SELinux policy.
286 if ((pw->pw_uid != getuid ())
287 && (is_selinux_enabled () > 0)
288 && (selinux_check_passwd_access (PASSWD__CHSH) != 0)) {
289 SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
291 _("You may not change the shell for '%s'.\n"),
299 * Non-privileged users are optionally authenticated (must enter
300 * the password of the user whose information is being changed)
301 * before any changes can be made. Idea from util-linux
302 * chfn/chsh. --marekm
304 if (!amroot && getdef_bool ("CHSH_AUTH")) {
305 passwd_check (pw->pw_name, pw->pw_passwd, "chsh");
309 pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
312 _("%s: Cannot determine your user name.\n"),
317 retval = pam_start ("chsh", pampw->pw_name, &conv, &pamh);
319 if (PAM_SUCCESS == retval) {
320 retval = pam_authenticate (pamh, 0);
323 if (PAM_SUCCESS == retval) {
324 retval = pam_acct_mgmt (pamh, 0);
328 (void) pam_end (pamh, retval);
330 if (PAM_SUCCESS != retval) {
331 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
338 * update_shell - update the user's shell in the passwd database
340 * Commit the user's entry after changing her shell field.
342 * It will not return in case of error.
344 static void update_shell (const char *user, char *newshell)
346 const struct passwd *pw; /* Password entry from /etc/passwd */
347 struct passwd pwent; /* New password entry */
350 * Before going any further, raise the ulimit to prevent
351 * colliding into a lowered ulimit, and set the real UID
352 * to root to protect against unexpected signals. Any
353 * keyboard signals are set to be ignored.
355 if (setuid (0) != 0) {
356 SYSLOG ((LOG_ERR, "can't setuid(0)"));
357 fputs (_("Cannot change ID to root.\n"), stderr);
363 * The passwd entry is now ready to be committed back to
364 * the password file. Get a lock on the file and open it.
366 if (pw_lock () == 0) {
367 fprintf (stderr, _("%s: cannot lock %s; try again later.\n"),
372 if (pw_open (O_RDWR) == 0) {
373 fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
374 SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
379 * Get the entry to update using pw_locate() - we want the real
380 * one from /etc/passwd, not the one from getpwnam() which could
381 * contain the shadow password if (despite the warnings) someone
382 * enables AUTOSHADOW (or SHADOW_COMPAT in libc). --marekm
384 pw = pw_locate (user);
387 _("%s: user '%s' does not exist in %s\n"),
388 Prog, user, pw_dbname ());
393 * Make a copy of the entry, then change the shell field. The other
394 * fields remain unchanged.
397 pwent.pw_shell = newshell;
400 * Update the passwd file entry. If there is a DBM file, update
401 * that entry as well.
403 if (pw_update (&pwent) == 0) {
405 _("%s: failed to prepare the new %s entry '%s'\n"),
406 Prog, pw_dbname (), pwent.pw_name);
411 * Changes have all been made, so commit them and unlock the file.
413 if (pw_close () == 0) {
414 fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
415 SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
418 if (pw_unlock () == 0) {
419 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
420 SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
427 * chsh - this command controls changes to the user's shell
429 * The only supported option is -s which permits the the login shell to
430 * be set from the command line.
432 int main (int argc, char **argv)
434 char *user; /* User name */
435 const struct passwd *pw; /* Password entry from /etc/passwd */
439 (void) setlocale (LC_ALL, "");
440 (void) bindtextdomain (PACKAGE, LOCALEDIR);
441 (void) textdomain (PACKAGE);
444 * This command behaves different for root and non-root users.
446 amroot = (getuid () == 0);
449 * Get the program name. The program name is used as a prefix to
450 * most error messages.
452 Prog = Basename (argv[0]);
456 /* parse the command line options */
457 process_flags (argc, argv);
460 * Get the name of the user to check. It is either the command line
461 * name, or the name getlogin() returns.
465 pw = xgetpwnam (user);
468 _("%s: user '%s' does not exist\n"), Prog, user);
472 pw = get_my_pwent ();
475 _("%s: Cannot determine your user name.\n"),
477 SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
478 (unsigned long) getuid ()));
481 user = xstrdup (pw->pw_name);
486 * Now we make sure this is a LOCAL password entry for this user ...
493 _("%s: cannot change user '%s' on NIS client.\n"),
496 if (!yp_get_default_domain (&nis_domain) &&
497 !yp_master (nis_domain, "passwd.byname", &nis_master)) {
499 _("%s: '%s' is the NIS master for this client.\n"),
509 * Now get the login shell. Either get it from the password
510 * file, or use the value from the command line.
513 STRFCPY (loginsh, pw->pw_shell);
517 * If the login shell was not set on the command line, let the user
518 * interactively change it.
521 printf (_("Changing the login shell for %s\n"), user);
526 * Check all of the fields for valid information. The shell
527 * field may not contain any illegal characters. Non-privileged
528 * users are restricted to using the shells in /etc/shells.
529 * The shell must be executable by the user.
531 if (valid_field (loginsh, ":,=") != 0) {
532 fprintf (stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
536 && ( is_restricted_shell (loginsh)
537 || (access (loginsh, X_OK) != 0))) {
538 fprintf (stderr, _("%s: %s is an invalid shell.\n"), Prog, loginsh);
542 update_shell (user, loginsh);
544 SYSLOG ((LOG_INFO, "changed user '%s' shell to '%s'", user, loginsh));
546 nscd_flush_cache ("passwd");