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
33 RCSID(PKG_VER "$Id: chsh.c,v 1.16 2000/08/26 18:27:18 marekm Exp $")
35 #include <sys/types.h>
39 #include "prototypes.h"
56 #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 */
68 * External identifiers
74 extern int pw_dbm_mode;
78 * #defines for messages. This facilitates foreign language conversion
79 * since all messages are defined right here.
82 #define WRONGPWD2 "incorrect password for `%s'"
83 #define NOPERM2 "can't change shell for `%s'\n"
84 #define PWDBUSY2 "can't lock /etc/passwd\n"
85 #define OPNERROR2 "can't open /etc/passwd\n"
86 #define UPDERROR2 "error updating passwd entry\n"
87 #define DBMERROR2 "error updating DBM passwd entry.\n"
88 #define NOTROOT2 "can't setuid(0).\n"
89 #define CLSERROR2 "can't rewrite /etc/passwd.\n"
90 #define UNLKERROR2 "can't unlock /etc/passwd.\n"
91 #define CHGSHELL "changed user `%s' shell to `%s'\n"
93 /* local function prototypes */
94 static void usage(void);
95 static void new_fields(void);
96 static int restricted_shell(const char *);
99 * usage - print command line syntax and exit
105 fprintf(stderr, _("Usage: %s [ -s shell ] [ name ]\n"), Prog);
110 * new_fields - change the user's login shell information interactively
112 * prompt the user for the login shell and change it according to the
113 * response, or leave it alone if nothing was entered.
119 printf(_("Enter the new value, or press return for the default\n"));
120 change_field(loginsh, sizeof loginsh, _("Login Shell"));
124 * restricted_shell - return true if the named shell begins with 'r' or 'R'
126 * If the first letter of the filename is 'r' or 'R', the shell is
127 * considered to be restricted.
131 restricted_shell(const char *sh)
134 char *cp = Basename((char *) sh);
135 return *cp == 'r' || *cp == 'R';
138 * Shells not listed in /etc/shells are considered to be
139 * restricted. Changed this to avoid confusion with "rc"
140 * (the plan9 shell - not restricted despite the name
141 * starting with 'r'). --marekm
143 return !check_shell(sh);
149 * chsh - this command controls changes to the user's shell
151 * The only supported option is -s which permits the
152 * the login shell to be set from the command line.
156 main(int argc, char **argv)
158 char *user; /* User name */
159 int flag; /* Current command line flag */
160 int sflg = 0; /* -s - set shell from command line */
161 const struct passwd *pw; /* Password entry from /etc/passwd */
162 struct passwd pwent; /* New password entry */
166 setlocale(LC_ALL, "");
167 bindtextdomain(PACKAGE, LOCALEDIR);
171 * This command behaves different for root and non-root
175 amroot = getuid () == 0;
177 pw_dbm_mode = O_RDWR;
181 * Get the program name. The program name is used as a
182 * prefix to most error messages. It is also used as input
183 * to the openlog() function for error logging.
186 Prog = Basename(argv[0]);
188 openlog("chsh", LOG_PID, LOG_AUTH);
191 * There is only one option, but use getopt() anyway to
192 * keep things consistent.
195 while ((flag = getopt (argc, argv, "s:")) != EOF) {
199 STRFCPY(loginsh, optarg);
207 * There should be only one remaining argument at most
208 * and it should be the user's name.
211 if (argc > optind + 1)
215 * Get the name of the user to check. It is either
216 * the command line name, or the name getlogin()
225 _("%s: Unknown user %s\n"),
233 _("%s: Cannot determine your user name.\n"),
237 user = xstrdup(pw->pw_name);
242 * Now we make sure this is a LOCAL password entry for
251 _("%s: cannot change user `%s' on NIS client.\n"),
254 if (! yp_get_default_domain (&nis_domain) &&
255 ! yp_master (nis_domain, "passwd.byname",
258 _("%s: `%s' is the NIS master for this client.\n"),
266 * Non-privileged users are only allowed to change the
267 * shell if the UID of the user matches the current
271 if (! amroot && pw->pw_uid != getuid()) {
272 SYSLOG((LOG_WARN, NOPERM2, user));
274 fprintf(stderr, _("You may not change the shell for %s.\n"),
280 * Non-privileged users are only allowed to change the
281 * shell if it is not a restricted one.
284 if (! amroot && restricted_shell(pw->pw_shell)) {
285 SYSLOG((LOG_WARN, NOPERM2, user));
287 fprintf(stderr, _("You may not change the shell for %s.\n"),
293 * Non-privileged users are optionally authenticated
294 * (must enter the password of the user whose information
295 * is being changed) before any changes can be made.
296 * Idea from util-linux chfn/chsh. --marekm
299 if (!amroot && getdef_bool("CHFN_AUTH"))
300 passwd_check(pw->pw_name, pw->pw_passwd, "chsh");
303 * Now get the login shell. Either get it from the password
304 * file, or use the value from the command line.
308 STRFCPY(loginsh, pw->pw_shell);
311 * If the login shell was not set on the command line,
312 * let the user interactively change it.
316 printf(_("Changing the login shell for %s\n"), user);
321 * Check all of the fields for valid information. The shell
322 * field may not contain any illegal characters. Non-privileged
323 * users are restricted to using the shells in /etc/shells.
324 * The shell must be executable by the user.
327 if (valid_field (loginsh, ":,=")) {
328 fprintf(stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
332 if (!amroot && (!check_shell(loginsh) || access(loginsh, X_OK) != 0)) {
333 fprintf(stderr, _("%s is an invalid shell.\n"), loginsh);
339 * Before going any further, raise the ulimit to prevent
340 * colliding into a lowered ulimit, and set the real UID
341 * to root to protect against unexpected signals. Any
342 * keyboard signals are set to be ignored.
346 SYSLOG((LOG_ERR, NOTROOT2));
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.
359 SYSLOG((LOG_WARN, PWDBUSY2));
362 _("Cannot lock the password file; try again later.\n"));
365 if (! pw_open (O_RDWR)) {
366 SYSLOG((LOG_ERR, OPNERROR2));
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,
396 * update that entry as well.
399 if (!pw_update(&pwent)) {
400 SYSLOG((LOG_ERR, UPDERROR2));
402 fprintf(stderr, _("Error updating the password entry.\n"));
407 if (pw_dbm_present() && ! pw_dbm_update (&pwent)) {
408 SYSLOG((LOG_ERR, DBMERROR2));
410 fprintf (stderr, _("Error updating the DBM password entry.\n"));
418 * Changes have all been made, so commit them and unlock the
423 SYSLOG((LOG_ERR, CLSERROR2));
425 fprintf(stderr, _("Cannot commit password file changes.\n"));
430 SYSLOG((LOG_ERR, UNLKERROR2));
432 fprintf(stderr, _("Cannot unlock the password file.\n"));
435 SYSLOG((LOG_INFO, CHGSHELL, user, loginsh));