2 * chsh.c -- change your login shell
3 * (c) 1994 by salvatore valente <svalente@athena.mit.edu>
4 * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
6 * this program is free software. you can redistribute it and
7 * modify it under the terms of the gnu general public license.
8 * there is no warranty.
12 * $Date: 1998/06/11 22:30:14 $
14 * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security
15 * patches from Zefram <A.Main@dcs.warwick.ac.uk>
17 * Updated Mon Jul 1 18:46:22 1996 by janl@math.uio.no with security
18 * suggestion from Zefram. Disallowing users with shells not in /etc/shells
19 * from changing their shell.
21 * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL>
22 * - added Native Language Support
32 #include <sys/types.h>
37 #include "closestream.h"
40 #include "pathnames.h"
46 #include "ch-common.h"
48 #ifdef HAVE_LIBSELINUX
49 # include <selinux/selinux.h>
50 # include "selinux-utils.h"
54 # include <libuser/user.h>
56 #elif CHFN_CHSH_PASSWORD
65 static void __attribute__((__noreturn__
)) usage(void)
68 fputs(USAGE_HEADER
, fp
);
69 fprintf(fp
, _(" %s [options] [<username>]\n"), program_invocation_short_name
);
71 fputs(USAGE_SEPARATOR
, fp
);
72 fputs(_("Change your login shell.\n"), fp
);
74 fputs(USAGE_OPTIONS
, fp
);
75 fputs(_(" -s, --shell <shell> specify login shell\n"), fp
);
76 fputs(_(" -l, --list-shells print list of shells and exit\n"), fp
);
77 fputs(USAGE_SEPARATOR
, fp
);
78 printf( " -u, --help %s\n", USAGE_OPTSTR_HELP
);
79 printf( " -v, --version %s\n", USAGE_OPTSTR_VERSION
);
80 printf(USAGE_MAN_TAIL("chsh(1)"));
85 * is_known_shell() -- if the given shell appears in /etc/shells,
86 * return true. if not, return false.
88 static int is_known_shell(const char *shell_name
)
96 while ((s
= getusershell())) {
97 if (strcmp(shell_name
, s
) == 0) {
107 * print_shells () -- /etc/shells is outputted to stdout.
109 static void print_shells(void)
113 while ((s
= getusershell()))
120 * parse the command line arguments, and fill in "pinfo" with any
121 * information from the command line.
123 static void parse_argv(int argc
, char **argv
, struct sinfo
*pinfo
)
125 static const struct option long_options
[] = {
126 {"shell", required_argument
, NULL
, 's'},
127 {"list-shells", no_argument
, NULL
, 'l'},
128 {"help", no_argument
, NULL
, 'h'},
129 {"version", no_argument
, NULL
, 'v'},
134 while ((c
= getopt_long(argc
, argv
, "s:lhuv", long_options
, NULL
)) != -1) {
137 print_version(EXIT_SUCCESS
);
138 case 'u': /* deprecated */
145 pinfo
->shell
= optarg
;
148 errtryhelp(EXIT_FAILURE
);
151 /* done parsing arguments. check for a username. */
153 if (optind
+ 1 < argc
) {
154 errx(EXIT_FAILURE
, _("cannot handle multiple usernames"));
156 pinfo
->username
= argv
[optind
];
161 * ask_new_shell () --
162 * ask the user for a shell and return it.
164 static char *ask_new_shell(char *question
, char *oldshell
)
172 printf("%s [%s]:", question
, oldshell
);
177 if (getline(&ans
, &dummy
, stdin
) < 0)
180 /* remove the newline at the end of ans. */
181 ltrim_whitespace((unsigned char *) ans
);
182 len
= rtrim_whitespace((unsigned char *) ans
);
189 * check_shell () -- if the shell is completely invalid, print
192 static void check_shell(const char *shell
)
195 errx(EXIT_FAILURE
, _("shell must be a full path name"));
196 if (access(shell
, F_OK
) < 0)
197 errx(EXIT_FAILURE
, _("\"%s\" does not exist"), shell
);
198 if (access(shell
, X_OK
) < 0)
199 errx(EXIT_FAILURE
, _("\"%s\" is not executable"), shell
);
200 if (illegal_passwd_chars(shell
))
201 errx(EXIT_FAILURE
, _("%s: has illegal characters"), shell
);
202 if (!is_known_shell(shell
)) {
203 #ifdef ONLY_LISTED_SHELLS
205 warnx(_("Warning: \"%s\" is not listed in %s."), shell
,
209 _("\"%s\" is not listed in %s.\n"
210 "Use %s -l to see list."), shell
, _PATH_SHELLS
,
211 program_invocation_short_name
);
213 warnx(_("\"%s\" is not listed in %s.\n"
214 "Use %s -l to see list."), shell
, _PATH_SHELLS
,
215 program_invocation_short_name
);
220 int main(int argc
, char **argv
)
222 char *oldshell
, *pwbuf
;
224 const uid_t uid
= getuid();
225 struct sinfo info
= { NULL
};
229 setlocale(LC_ALL
, "");
230 bindtextdomain(PACKAGE
, LOCALEDIR
);
232 close_stdout_atexit();
234 parse_argv(argc
, argv
, &info
);
235 if (!info
.username
) {
236 pw
= xgetpwuid(uid
, &pwbuf
);
238 errx(EXIT_FAILURE
, _("you (user %d) don't exist."),
241 pw
= xgetpwnam(info
.username
, &pwbuf
);
243 errx(EXIT_FAILURE
, _("user \"%s\" does not exist."),
248 if (!(is_local(pw
->pw_name
)))
249 errx(EXIT_FAILURE
, _("can only change local entries"));
252 #ifdef HAVE_LIBSELINUX
253 if (is_selinux_enabled() > 0) {
254 char *user_cxt
= NULL
;
256 if (uid
== 0 && !ul_selinux_has_access("passwd", "chsh", &user_cxt
))
258 _("%s is not authorized to change the shell of %s"),
259 user_cxt
? : _("Unknown user context"),
262 if (ul_setfscreatecon_from_file(_PATH_PASSWD
) != 0)
264 _("can't set default context for %s"), _PATH_PASSWD
);
268 oldshell
= pw
->pw_shell
;
269 if (oldshell
== NULL
|| *oldshell
== '\0') {
270 oldshell
= _PATH_BSHELL
; /* default */
276 /* If we're setuid and not really root, disallow the password change. */
277 if (geteuid() != getuid() && uid
!= pw
->pw_uid
) {
279 if (uid
!= 0 && uid
!= pw
->pw_uid
) {
283 _("running UID doesn't match UID of user we're "
284 "altering, shell change denied"));
286 if (uid
!= 0 && !is_known_shell(oldshell
)) {
288 err(EXIT_FAILURE
, _("your shell is not in %s, "
289 "shell change denied"), _PATH_SHELLS
);
292 printf(_("Changing shell for %s.\n"), pw
->pw_name
);
294 #if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
295 if (!auth_pam("chsh", uid
, pw
->pw_name
)) {
300 info
.shell
= ask_new_shell(_("New shell"), oldshell
);
305 check_shell(info
.shell
);
307 if (!nullshell
&& strcmp(oldshell
, info
.shell
) == 0)
308 errx(EXIT_SUCCESS
, _("Shell not changed."));
311 if (set_value_libuser("chsh", pw
->pw_name
, uid
,
312 LU_LOGINSHELL
, info
.shell
) < 0)
313 errx(EXIT_FAILURE
, _("Shell *NOT* changed. Try again later."));
315 pw
->pw_shell
= info
.shell
;
316 if (setpwnam(pw
, ".chsh") < 0)
317 err(EXIT_FAILURE
, _("setpwnam failed\n"
318 "Shell *NOT* changed. Try again later."));
321 printf(_("Shell changed.\n"));