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.35 2005/10/04 21:02:22 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 static struct pam_conv conv = {
118 * chsh - this command controls changes to the user's shell
120 * The only supported option is -s which permits the the login shell to
121 * be set from the command line.
123 int main (int argc, char **argv)
125 char *user; /* User name */
126 int flag; /* Current command line flag */
127 int sflg = 0; /* -s - set shell from command line */
128 const struct passwd *pw; /* Password entry from /etc/passwd */
129 struct passwd pwent; /* New password entry */
132 pam_handle_t *pamh = NULL;
133 struct passwd *pampw;
139 setlocale (LC_ALL, "");
140 bindtextdomain (PACKAGE, LOCALEDIR);
141 textdomain (PACKAGE);
144 * This command behaves different for root and non-root users.
146 amroot = getuid () == 0;
149 * Get the program name. The program name is used as a prefix to
150 * most error messages.
152 Prog = Basename (argv[0]);
157 * There is only one option, but use getopt() anyway to
158 * keep things consistent.
161 while ((flag = getopt (argc, argv, "s:")) != EOF) {
165 STRFCPY (loginsh, optarg);
173 * There should be only one remaining argument at most and it should
174 * be the user's name.
176 if (argc > optind + 1)
180 * Get the name of the user to check. It is either the command line
181 * name, or the name getlogin() returns.
185 pw = getpwnam (user);
188 _("%s: unknown user %s\n"), Prog, user);
192 pw = get_my_pwent ();
196 ("%s: Cannot determine your user name.\n"),
200 user = xstrdup (pw->pw_name);
205 * Now we make sure this is a LOCAL password entry for this user ...
212 _("%s: cannot change user `%s' on NIS client.\n"),
215 if (!yp_get_default_domain (&nis_domain) &&
216 !yp_master (nis_domain, "passwd.byname", &nis_master)) {
219 ("%s: `%s' is the NIS master for this client.\n"),
227 * Non-privileged users are only allowed to change the shell if the
228 * UID of the user matches the current real UID.
230 if (!amroot && pw->pw_uid != getuid ()) {
231 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
234 _("You may not change the shell for %s.\n"), user);
239 * Non-privileged users are only allowed to change the shell if it
240 * is not a restricted one.
242 if (!amroot && restricted_shell (pw->pw_shell)) {
243 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
246 _("You may not change the shell for %s.\n"), user);
251 * If the UID of the user does not match the current real UID,
252 * check if the change is allowed by SELinux policy.
254 if ((pw->pw_uid != getuid ())
255 && (selinux_check_passwd_access (PASSWD__CHSH) != 0)) {
256 SYSLOG ((LOG_WARN, "can't change shell for `%s'", user));
259 _("You may not change the shell for %s.\n"), user);
266 * Non-privileged users are optionally authenticated (must enter
267 * the password of the user whose information is being changed)
268 * before any changes can be made. Idea from util-linux
269 * chfn/chsh. --marekm
271 if (!amroot && getdef_bool ("CHSH_AUTH"))
272 passwd_check (pw->pw_name, pw->pw_passwd, "chsh");
275 retval = PAM_SUCCESS;
277 pampw = getpwuid (getuid ());
279 retval = PAM_USER_UNKNOWN;
282 if (retval == PAM_SUCCESS) {
283 retval = pam_start ("chsh", pampw->pw_name, &conv, &pamh);
286 if (retval == PAM_SUCCESS) {
287 retval = pam_authenticate (pamh, 0);
288 if (retval != PAM_SUCCESS) {
289 pam_end (pamh, retval);
293 if (retval == PAM_SUCCESS) {
294 retval = pam_acct_mgmt (pamh, 0);
295 if (retval != PAM_SUCCESS) {
296 pam_end (pamh, retval);
300 if (retval != PAM_SUCCESS) {
301 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
307 * Now get the login shell. Either get it from the password
308 * file, or use the value from the command line.
311 STRFCPY (loginsh, pw->pw_shell);
314 * If the login shell was not set on the command line, let the user
315 * interactively change it.
318 printf (_("Changing the login shell for %s\n"), user);
323 * Check all of the fields for valid information. The shell
324 * field may not contain any illegal characters. Non-privileged
325 * users are restricted to using the shells in /etc/shells.
326 * The shell must be executable by the user.
328 if (valid_field (loginsh, ":,=")) {
329 fprintf (stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
333 if (!amroot && (!check_shell (loginsh) || access (loginsh, X_OK) != 0)) {
334 fprintf (stderr, _("%s is an invalid shell.\n"), loginsh);
340 * Before going any further, raise the ulimit to prevent
341 * colliding into a lowered ulimit, and set the real UID
342 * to root to protect against unexpected signals. Any
343 * keyboard signals are set to be ignored.
346 SYSLOG ((LOG_ERR, "can't setuid(0)"));
348 fprintf (stderr, _("Cannot change ID to root.\n"));
354 * The passwd entry is now ready to be committed back to
355 * the password file. Get a lock on the file and open it.
358 SYSLOG ((LOG_WARN, "can't lock /etc/passwd"));
362 ("Cannot lock the password file; try again later.\n"));
365 if (!pw_open (O_RDWR)) {
366 SYSLOG ((LOG_ERR, "can't open /etc/passwd"));
368 fprintf (stderr, _("Cannot open the password file.\n"));
374 * Get the entry to update using pw_locate() - we want the real
375 * one from /etc/passwd, not the one from getpwnam() which could
376 * contain the shadow password if (despite the warnings) someone
377 * enables AUTOSHADOW (or SHADOW_COMPAT in libc). --marekm
379 pw = pw_locate (user);
383 _("%s: %s not found in /etc/passwd\n"), Prog, user);
388 * Make a copy of the entry, then change the shell field. The other
389 * fields remain unchanged.
392 pwent.pw_shell = loginsh;
395 * Update the passwd file entry. If there is a DBM file, update
396 * that entry as well.
398 if (!pw_update (&pwent)) {
399 SYSLOG ((LOG_ERR, "error updating passwd entry"));
401 fprintf (stderr, _("Error updating the password entry.\n"));
407 * Changes have all been made, so commit them and unlock the file.
410 SYSLOG ((LOG_ERR, "can't rewrite /etc/passwd"));
412 fprintf (stderr, _("Cannot commit password file changes.\n"));
417 SYSLOG ((LOG_ERR, "can't unlock /etc/passwd"));
419 fprintf (stderr, _("Cannot unlock the password file.\n"));
422 SYSLOG ((LOG_INFO, "changed user `%s' shell to `%s'", user, loginsh));
424 nscd_flush_cache ("passwd");
427 if (retval == PAM_SUCCESS)
428 pam_end (pamh, PAM_SUCCESS);