2 * chsh.c -- change your login shell
3 * (c) 1994 by salvatore valente <svalente@athena.mit.edu>
5 * this program is free software. you can redistribute it and
6 * modify it under the terms of the gnu general public license.
7 * there is no warranty.
11 * $Date: 1998/06/11 22:30:14 $
13 * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security
14 * patches from Zefram <A.Main@dcs.warwick.ac.uk>
16 * Updated Mon Jul 1 18:46:22 1996 by janl@math.uio.no with security
17 * suggestion from Zefram. Disallowing users with shells not in /etc/shells
18 * from changing their shell.
20 * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
21 * - added Native Language Support
27 #define _POSIX_SOURCE 1
30 #include <sys/types.h>
44 #include "pathnames.h"
46 #if defined(REQUIRE_PASSWORD) && defined(HAVE_SECURITY_PAM_MISC_H)
47 #include <security/pam_appl.h>
48 #include <security/pam_misc.h>
50 #define PAM_FAIL_CHECK(_ph, _rc) \
52 if ((_rc) != PAM_SUCCESS) { \
53 fprintf(stderr, "\n%s\n", pam_strerror((_ph), (_rc))); \
54 pam_end((_ph), (_rc)); \
61 #ifdef HAVE_LIBSELINUX
62 #include <selinux/selinux.h>
63 #include <selinux/av_permissions.h>
64 #include "selinux_utils.h"
67 typedef unsigned char boolean;
71 /* Only root is allowed to assign a luser a non-listed shell, by default */
72 #define ONLY_LISTED_SHELLS 1
77 static char buf[FILENAME_MAX];
84 static void parse_argv (int argc, char *argv[], struct sinfo *pinfo);
85 static void usage (FILE *fp);
86 static char *prompt (char *question, char *def_val);
87 static int check_shell (char *shell);
88 static boolean get_shell_list (char *shell);
89 static void *xmalloc (int bytes);
91 #define memzero(ptr, size) memset((char *) ptr, 0, size)
94 main (int argc, char *argv[]) {
95 char *cp, *shell, *oldshell;
101 setlocale(LC_ALL, "");
102 bindtextdomain(PACKAGE, LOCALEDIR);
105 /* whoami is the program name for error messages */
107 if (! whoami) whoami = "chsh";
108 for (cp = whoami; *cp; cp++)
109 if (*cp == '/') whoami = cp + 1;
112 memzero (&info, sizeof (info));
114 parse_argv (argc, argv, &info);
116 if (! info.username) {
119 fprintf (stderr, _("%s: you (user %d) don't exist.\n"), whoami, uid);
123 pw = getpwnam (info.username);
126 fprintf (stderr, _("%s: user \"%s\" does not exist.\n"), whoami, cp);
130 if (!(is_local(pw->pw_name))) {
131 fprintf (stderr, _("%s: can only change local entries; use yp%s instead.\n"),
136 #ifdef HAVE_LIBSELINUX
137 if (is_selinux_enabled() > 0) {
139 if (checkAccess(pw->pw_name,PASSWD__CHSH)!=0) {
140 security_context_t user_context;
141 if (getprevcon(&user_context) < 0)
142 user_context=(security_context_t) strdup(_("Unknown user context"));
143 fprintf(stderr, _("%s: %s is not authorized to change the shell of %s\n"),
144 whoami, user_context, pw->pw_name);
145 freecon(user_context);
149 if (setupDefaultContext("/etc/passwd") != 0) {
150 fprintf(stderr,_("%s: Can't set default context for /etc/passwd"),
157 oldshell = pw->pw_shell;
158 if (oldshell == NULL || *oldshell == '\0')
159 oldshell = _PATH_BSHELL; /* default */
162 if (uid != 0 && uid != pw->pw_uid) {
164 fprintf(stderr,_("%s: Running UID doesn't match UID of user we're "
165 "altering, shell change denied\n"), whoami);
168 if (uid != 0 && !get_shell_list(oldshell)) {
170 fprintf(stderr,_("%s: Your shell is not in /etc/shells, shell change"
171 " denied\n"),whoami);
177 printf( _("Changing shell for %s.\n"), pw->pw_name );
179 #ifdef REQUIRE_PASSWORD
180 #ifdef HAVE_SECURITY_PAM_MISC_H
182 pam_handle_t *pamh = NULL;
183 struct pam_conv conv = { misc_conv, NULL };
186 retcode = pam_start("chsh", pw->pw_name, &conv, &pamh);
187 if(retcode != PAM_SUCCESS) {
188 fprintf(stderr, _("%s: PAM failure, aborting: %s\n"),
189 whoami, pam_strerror(pamh, retcode));
193 retcode = pam_authenticate(pamh, 0);
194 PAM_FAIL_CHECK(pamh, retcode);
196 retcode = pam_acct_mgmt(pamh, 0);
197 if (retcode == PAM_NEW_AUTHTOK_REQD)
198 retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
199 PAM_FAIL_CHECK(pamh, retcode);
201 retcode = pam_setcred(pamh, 0);
202 PAM_FAIL_CHECK(pamh, retcode);
205 /* no need to establish a session; this isn't a session-oriented
208 #else /* HAVE_SECURITY_PAM_MISC_H */
209 /* require password, unless root */
210 if(uid != 0 && pw->pw_passwd && pw->pw_passwd[0]) {
211 char *pwdstr = getpass(_("Password: "));
212 if(strncmp(pw->pw_passwd,
213 crypt(pwdstr, pw->pw_passwd), 13)) {
214 puts(_("Incorrect password."));
218 #endif /* HAVE_SECURITY_PAM_MISC_H */
219 #endif /* REQUIRE_PASSWORD */
222 shell = prompt (_("New shell"), oldshell);
223 if (! shell) return 0;
226 if (check_shell (shell) < 0) return (-1);
228 if (! strcmp (pw->pw_shell, shell)) {
229 printf (_("Shell not changed.\n"));
232 pw->pw_shell = shell;
233 if (setpwnam (pw) < 0) {
235 printf( _("Shell *NOT* changed. Try again later.\n") );
238 printf (_("Shell changed.\n"));
244 * parse the command line arguments, and fill in "pinfo" with any
245 * information from the command line.
248 parse_argv (int argc, char *argv[], struct sinfo *pinfo) {
251 static struct option long_options[] = {
252 { "shell", required_argument, 0, 's' },
253 { "list-shells", no_argument, 0, 'l' },
254 { "help", no_argument, 0, 'u' },
255 { "version", no_argument, 0, 'v' },
256 { NULL, no_argument, 0, '0' },
261 c = getopt_long (argc, argv, "s:luv", long_options, &index);
266 printf ("%s\n", PACKAGE_STRING);
272 get_shell_list (NULL);
279 pinfo->shell = optarg;
286 /* done parsing arguments. check for a username. */
288 if (optind + 1 < argc) {
292 pinfo->username = argv[optind];
298 * print out a usage message.
303 _("Usage: %s [ -s shell ] [ --list-shells ] "
304 "[ --help ] [ --version ]\n"
305 " [ username ]\n"), whoami);
310 * ask the user for a given field and return it.
313 prompt (char *question, char *def_val) {
317 if (! def_val) def_val = "";
318 printf("%s [%s]: ", question, def_val);
320 if (fgets (buf, sizeof (buf), stdin) == NULL) {
321 printf (_("\nAborted.\n"));
324 /* remove the newline at the end of buf. */
326 while (isspace (*ans)) ans++;
328 while (len > 0 && isspace (ans[len-1])) len--;
329 if (len <= 0) return NULL;
331 cp = (char *) xmalloc (len + 1);
337 * check_shell () -- if the shell is completely invalid, print
338 * an error and return (-1).
339 * if the shell is a bad idea, print a warning.
342 check_shell (char *shell) {
349 printf (_("%s: shell must be a full path name.\n"), whoami);
352 if (access (shell, F_OK) < 0) {
353 printf (_("%s: \"%s\" does not exist.\n"), whoami, shell);
356 if (access (shell, X_OK) < 0) {
357 printf (_("%s: \"%s\" is not executable.\n"), whoami, shell);
360 /* keep /etc/passwd clean. */
361 for (i = 0; i < strlen (shell); i++) {
363 if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
364 printf (_("%s: '%c' is not allowed.\n"), whoami, c);
368 printf (_("%s: Control characters are not allowed.\n"), whoami);
372 #ifdef ONLY_LISTED_SHELLS
373 if (! get_shell_list (shell)) {
375 printf (_("Warning: \"%s\" is not listed in /etc/shells.\n"), shell);
377 printf (_("%s: \"%s\" is not listed in /etc/shells.\n"),
379 printf( _("%s: Use -l option to see list.\n"), whoami );
384 if (! get_shell_list (shell)) {
385 printf (_("Warning: \"%s\" is not listed in /etc/shells.\n"), shell);
386 printf( _("Use %s -l to see list.\n"), whoami );
393 * get_shell_list () -- if the given shell appears in /etc/shells,
394 * return true. if not, return false.
395 * if the given shell is NULL, /etc/shells is outputted to stdout.
398 get_shell_list (char *shell_name) {
404 fp = fopen ("/etc/shells", "r");
406 if (! shell_name) printf (_("No known shells.\n"));
409 while (fgets (buf, sizeof (buf), fp) != NULL) {
410 /* ignore comments */
411 if (*buf == '#') continue;
413 /* strip the ending newline */
414 if (buf[len - 1] == '\n') buf[len - 1] = 0;
415 /* ignore lines that are too damn long */
417 /* check or output the shell */
419 if (! strcmp (shell_name, buf)) {
424 else printf ("%s\n", buf);
431 * xmalloc () -- malloc that never fails.
434 xmalloc (int bytes) {
438 if (! vp && bytes > 0) {
439 perror (_("malloc failed"));