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
32 #ident "$Id: chsh.c,v 1.37 2006/01/02 23:31:59 kloczek Exp $"
38 #include <sys/types.h>
40 #include <selinux/selinux.h>
41 #include <selinux/av_permissions.h>
47 #include "exitcodes.h"
50 #include "prototypes.h"
57 #define SHELLS_FILE "/etc/shells"
62 static char *Prog; /* Program name */
63 static int amroot; /* Real UID is root */
64 static char loginsh[BUFSIZ]; /* Name of new login shell */
66 /* external identifiers */
68 /* local function prototypes */
69 static void usage (void);
70 static void new_fields (void);
71 static int restricted_shell (const char *);
74 * usage - print command line syntax and exit
76 static void usage (void)
78 fprintf (stderr, _("Usage: %s [-s shell] [name]\n"), Prog);
83 * new_fields - change the user's login shell information interactively
85 * prompt the user for the login shell and change it according to the
86 * response, or leave it alone if nothing was entered.
88 static void new_fields (void)
90 printf (_("Enter the new value, or press ENTER for the default\n"));
91 change_field (loginsh, sizeof loginsh, _("Login Shell"));
95 * restricted_shell - return true if the named shell begins with 'r' or 'R'
97 * If the first letter of the filename is 'r' or 'R', the shell is
98 * considered to be restricted.
100 static int restricted_shell (const char *sh)
103 * Shells not listed in /etc/shells are considered to be restricted.
104 * Changed this to avoid confusion with "rc" (the plan9 shell - not
105 * restricted despite the name starting with 'r'). --marekm
107 return !check_shell (sh);
111 * check_shell - see if the user's login shell is listed in /etc/shells
113 * The /etc/shells file is read for valid names of login shells. If the
114 * /etc/shells file does not exist the user cannot set any shell unless
117 * If getusershell() is available (Linux, *BSD, possibly others), use it
118 * instead of re-implementing it.
120 int check_shell (const char *sh)
125 #ifndef HAVE_GETUSERSHELL
130 #ifdef HAVE_GETUSERSHELL
132 while ((cp = getusershell ())) {
136 if (strcmp (cp, sh) == 0) {
143 if ((fp = fopen (SHELLS_FILE, "r")) == (FILE *) 0)
146 while (fgets (buf, sizeof (buf), fp)) {
147 if ((cp = strrchr (buf, '\n')))
153 if (strcmp (buf, sh) == 0) {
164 * chsh - this command controls changes to the user's shell
166 * The only supported option is -s which permits the the login shell to
167 * be set from the command line.
169 int main (int argc, char **argv)
171 char *user; /* User name */
172 int flag; /* Current command line flag */
173 int sflg = 0; /* -s - set shell from command line */
174 const struct passwd *pw; /* Password entry from /etc/passwd */
175 struct passwd pwent; /* New password entry */
178 pam_handle_t *pamh = NULL;
179 struct passwd *pampw;
185 setlocale (LC_ALL, "");
186 bindtextdomain (PACKAGE, LOCALEDIR);
187 textdomain (PACKAGE);
190 * This command behaves different for root and non-root users.
192 amroot = getuid () == 0;
195 * Get the program name. The program name is used as a prefix to
196 * most error messages.
198 Prog = Basename (argv[0]);
203 * There is only one option, but use getopt() anyway to
204 * keep things consistent.
207 while ((flag = getopt (argc, argv, "s:")) != EOF) {
211 STRFCPY (loginsh, optarg);
219 * There should be only one remaining argument at most and it should
220 * be the user's name.
222 if (argc > optind + 1)
226 * Get the name of the user to check. It is either the command line
227 * name, or the name getlogin() returns.
231 pw = getpwnam (user);
234 _("%s: unknown user %s\n"), Prog, user);
238 pw = get_my_pwent ();
242 ("%s: Cannot determine your user name.\n"),
246 user = xstrdup (pw->pw_name);
251 * Now we make sure this is a LOCAL password entry for this user ...
258 _("%s: cannot change user `%s' on NIS client.\n"),
261 if (!yp_get_default_domain (&nis_domain) &&
262 !yp_master (nis_domain, "passwd.byname", &nis_master)) {
265 ("%s: `%s' is the NIS master for this client.\n"),
273 * Non-privileged users are only allowed to change the shell if the
274 * UID of the user matches the current real UID.
276 if (!amroot && pw->pw_uid != getuid ()) {
277 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
280 _("You may not change the shell for %s.\n"), user);
285 * Non-privileged users are only allowed to change the shell if it
286 * is not a restricted one.
288 if (!amroot && restricted_shell (pw->pw_shell)) {
289 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
292 _("You may not change the shell for %s.\n"), user);
297 * If the UID of the user does not match the current real UID,
298 * check if the change is allowed by SELinux policy.
300 if ((pw->pw_uid != getuid ())
301 && (selinux_check_passwd_access (PASSWD__CHSH) != 0)) {
302 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
305 _("You may not change the shell for %s.\n"), user);
312 * Non-privileged users are optionally authenticated (must enter
313 * the password of the user whose information is being changed)
314 * before any changes can be made. Idea from util-linux
315 * chfn/chsh. --marekm
317 if (!amroot && getdef_bool ("CHSH_AUTH"))
318 passwd_check (pw->pw_name, pw->pw_passwd, "chsh");
321 retval = PAM_SUCCESS;
323 pampw = getpwuid (getuid ());
325 retval = PAM_USER_UNKNOWN;
328 if (retval == PAM_SUCCESS) {
329 retval = pam_start ("chsh", pampw->pw_name, &conv, &pamh);
332 if (retval == PAM_SUCCESS) {
333 retval = pam_authenticate (pamh, 0);
334 if (retval != PAM_SUCCESS) {
335 pam_end (pamh, retval);
339 if (retval == PAM_SUCCESS) {
340 retval = pam_acct_mgmt (pamh, 0);
341 if (retval != PAM_SUCCESS) {
342 pam_end (pamh, retval);
346 if (retval != PAM_SUCCESS) {
347 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
353 * Now get the login shell. Either get it from the password
354 * file, or use the value from the command line.
357 STRFCPY (loginsh, pw->pw_shell);
360 * If the login shell was not set on the command line, let the user
361 * interactively change it.
364 printf (_("Changing the login shell for %s\n"), user);
369 * Check all of the fields for valid information. The shell
370 * field may not contain any illegal characters. Non-privileged
371 * users are restricted to using the shells in /etc/shells.
372 * The shell must be executable by the user.
374 if (valid_field (loginsh, ":,=")) {
375 fprintf (stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
379 if (!amroot && (!check_shell (loginsh) || access (loginsh, X_OK) != 0)) {
380 fprintf (stderr, _("%s is an invalid shell.\n"), loginsh);
386 * Before going any further, raise the ulimit to prevent
387 * colliding into a lowered ulimit, and set the real UID
388 * to root to protect against unexpected signals. Any
389 * keyboard signals are set to be ignored.
392 SYSLOG ((LOG_ERR, "can't setuid(0)"));
394 fprintf (stderr, _("Cannot change ID to root.\n"));
400 * The passwd entry is now ready to be committed back to
401 * the password file. Get a lock on the file and open it.
404 SYSLOG ((LOG_WARN, "can't lock /etc/passwd"));
408 ("Cannot lock the password file; try again later.\n"));
411 if (!pw_open (O_RDWR)) {
412 SYSLOG ((LOG_ERR, "can't open /etc/passwd"));
414 fprintf (stderr, _("Cannot open the password file.\n"));
420 * Get the entry to update using pw_locate() - we want the real
421 * one from /etc/passwd, not the one from getpwnam() which could
422 * contain the shadow password if (despite the warnings) someone
423 * enables AUTOSHADOW (or SHADOW_COMPAT in libc). --marekm
425 pw = pw_locate (user);
429 _("%s: %s not found in /etc/passwd\n"), Prog, user);
434 * Make a copy of the entry, then change the shell field. The other
435 * fields remain unchanged.
438 pwent.pw_shell = loginsh;
441 * Update the passwd file entry. If there is a DBM file, update
442 * that entry as well.
444 if (!pw_update (&pwent)) {
445 SYSLOG ((LOG_ERR, "error updating passwd entry"));
447 fprintf (stderr, _("Error updating the password entry.\n"));
453 * Changes have all been made, so commit them and unlock the file.
456 SYSLOG ((LOG_ERR, "can't rewrite /etc/passwd"));
458 fprintf (stderr, _("Cannot commit password file changes.\n"));
463 SYSLOG ((LOG_ERR, "can't unlock /etc/passwd"));
465 fprintf (stderr, _("Cannot unlock the password file.\n"));
468 SYSLOG ((LOG_INFO, "changed user `%s' shell to `%s'", user, loginsh));
470 nscd_flush_cache ("passwd");
473 if (retval == PAM_SUCCESS)
474 pam_end (pamh, PAM_SUCCESS);