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"
55 # include <libuser/user.h>
57 #elif CHFN_CHSH_PASSWORD
61 #ifdef HAVE_LIBREADLINE
62 # define _FUNCTION_DEF
63 # include <readline/readline.h>
71 static void __attribute__((__noreturn__
)) usage(void)
74 fputs(USAGE_HEADER
, fp
);
75 fprintf(fp
, _(" %s [options] [<username>]\n"), program_invocation_short_name
);
77 fputs(USAGE_SEPARATOR
, fp
);
78 fputs(_("Change your login shell.\n"), fp
);
80 fputs(USAGE_OPTIONS
, fp
);
81 fputs(_(" -s, --shell <shell> specify login shell\n"), fp
);
82 fputs(_(" -l, --list-shells print list of shells and exit\n"), fp
);
83 fputs(USAGE_SEPARATOR
, fp
);
84 printf( " -u, --help %s\n", USAGE_OPTSTR_HELP
);
85 printf( " -v, --version %s\n", USAGE_OPTSTR_VERSION
);
86 printf(USAGE_MAN_TAIL("chsh(1)"));
91 * is_known_shell() -- if the given shell appears in /etc/shells,
92 * return true. if not, return false.
94 static int is_known_shell(const char *shell_name
)
102 while ((s
= getusershell())) {
103 if (strcmp(shell_name
, s
) == 0) {
113 * print_shells () -- /etc/shells is outputted to stdout.
115 static void print_shells(void)
119 while ((s
= getusershell()))
124 #ifdef HAVE_LIBREADLINE
125 static char *shell_name_generator(const char *text
, int state
)
135 while ((s
= getusershell())) {
136 if (strncmp(s
, text
, len
) == 0)
142 static char **shell_name_completion(const char *text
,
143 int start
__attribute__((__unused__
)),
144 int end
__attribute__((__unused__
)))
146 rl_attempted_completion_over
= 1;
147 return rl_completion_matches(text
, shell_name_generator
);
153 * parse the command line arguments, and fill in "pinfo" with any
154 * information from the command line.
156 static void parse_argv(int argc
, char **argv
, struct sinfo
*pinfo
)
158 static const struct option long_options
[] = {
159 {"shell", required_argument
, NULL
, 's'},
160 {"list-shells", no_argument
, NULL
, 'l'},
161 {"help", no_argument
, NULL
, 'h'},
162 {"version", no_argument
, NULL
, 'v'},
167 while ((c
= getopt_long(argc
, argv
, "s:lhuv", long_options
, NULL
)) != -1) {
170 print_version(EXIT_SUCCESS
);
171 case 'u': /* deprecated */
178 pinfo
->shell
= optarg
;
181 errtryhelp(EXIT_FAILURE
);
184 /* done parsing arguments. check for a username. */
186 if (optind
+ 1 < argc
) {
187 errx(EXIT_FAILURE
, _("cannot handle multiple usernames"));
189 pinfo
->username
= argv
[optind
];
194 * ask_new_shell () --
195 * ask the user for a shell and return it.
197 static char *ask_new_shell(char *question
, char *oldshell
)
201 #ifdef HAVE_LIBREADLINE
202 rl_attempted_completion_function
= shell_name_completion
;
208 printf("%s [%s]\n", question
, oldshell
);
209 #ifdef HAVE_LIBREADLINE
210 if ((ans
= readline("> ")) == NULL
)
212 if (getline(&ans
, &dummy
, stdin
) < 0)
215 /* remove the newline at the end of ans. */
216 ltrim_whitespace((unsigned char *) ans
);
217 len
= rtrim_whitespace((unsigned char *) ans
);
224 * check_shell () -- if the shell is completely invalid, print
227 static void check_shell(const char *shell
)
230 errx(EXIT_FAILURE
, _("shell must be a full path name"));
231 if (access(shell
, F_OK
) < 0)
232 errx(EXIT_FAILURE
, _("\"%s\" does not exist"), shell
);
233 if (access(shell
, X_OK
) < 0)
234 errx(EXIT_FAILURE
, _("\"%s\" is not executable"), shell
);
235 if (illegal_passwd_chars(shell
))
236 errx(EXIT_FAILURE
, _("%s: has illegal characters"), shell
);
237 if (!is_known_shell(shell
)) {
238 #ifdef ONLY_LISTED_SHELLS
240 warnx(_("Warning: \"%s\" is not listed in %s."), shell
,
244 _("\"%s\" is not listed in %s.\n"
245 "Use %s -l to see list."), shell
, _PATH_SHELLS
,
246 program_invocation_short_name
);
248 warnx(_("\"%s\" is not listed in %s.\n"
249 "Use %s -l to see list."), shell
, _PATH_SHELLS
,
250 program_invocation_short_name
);
255 int main(int argc
, char **argv
)
257 char *oldshell
, *pwbuf
;
259 const uid_t uid
= getuid();
260 struct sinfo info
= { NULL
};
264 setlocale(LC_ALL
, "");
265 bindtextdomain(PACKAGE
, LOCALEDIR
);
267 close_stdout_atexit();
269 parse_argv(argc
, argv
, &info
);
270 if (!info
.username
) {
271 pw
= xgetpwuid(uid
, &pwbuf
);
273 errx(EXIT_FAILURE
, _("you (user %d) don't exist."),
276 pw
= xgetpwnam(info
.username
, &pwbuf
);
278 errx(EXIT_FAILURE
, _("user \"%s\" does not exist."),
283 if (!(is_local(pw
->pw_name
)))
284 errx(EXIT_FAILURE
, _("can only change local entries"));
287 #ifdef HAVE_LIBSELINUX
288 if (is_selinux_enabled() > 0) {
290 access_vector_t av
= get_access_vector("passwd", "chsh");
292 if (selinux_check_passwd_access(av
) != 0) {
293 security_context_t user_context
;
294 if (getprevcon(&user_context
) < 0)
296 (security_context_t
) NULL
;
299 _("%s is not authorized to change the shell of %s"),
300 user_context
? : _("Unknown user context"),
304 if (setupDefaultContext(_PATH_PASSWD
) != 0)
306 _("can't set default context for %s"), _PATH_PASSWD
);
310 oldshell
= pw
->pw_shell
;
311 if (oldshell
== NULL
|| *oldshell
== '\0') {
312 oldshell
= _PATH_BSHELL
; /* default */
318 /* If we're setuid and not really root, disallow the password change. */
319 if (geteuid() != getuid() && uid
!= pw
->pw_uid
) {
321 if (uid
!= 0 && uid
!= pw
->pw_uid
) {
325 _("running UID doesn't match UID of user we're "
326 "altering, shell change denied"));
328 if (uid
!= 0 && !is_known_shell(oldshell
)) {
330 err(EXIT_FAILURE
, _("your shell is not in %s, "
331 "shell change denied"), _PATH_SHELLS
);
334 printf(_("Changing shell for %s.\n"), pw
->pw_name
);
336 #if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
337 if (!auth_pam("chsh", uid
, pw
->pw_name
)) {
342 info
.shell
= ask_new_shell(_("New shell"), oldshell
);
347 check_shell(info
.shell
);
349 if (!nullshell
&& strcmp(oldshell
, info
.shell
) == 0)
350 errx(EXIT_SUCCESS
, _("Shell not changed."));
353 if (set_value_libuser("chsh", pw
->pw_name
, uid
,
354 LU_LOGINSHELL
, info
.shell
) < 0)
355 errx(EXIT_FAILURE
, _("Shell *NOT* changed. Try again later."));
357 pw
->pw_shell
= info
.shell
;
358 if (setpwnam(pw
, ".chsh") < 0)
359 err(EXIT_FAILURE
, _("setpwnam failed\n"
360 "Shell *NOT* changed. Try again later."));
363 printf(_("Shell changed.\n"));