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"
49 #ifdef HAVE_LIBSELINUX
50 # include <selinux/selinux.h>
51 # include "selinux-utils.h"
55 # include <libuser/user.h>
57 #elif CHFN_CHSH_PASSWORD
66 static void __attribute__((__noreturn__
)) usage(void)
69 fputs(USAGE_HEADER
, fp
);
70 fprintf(fp
, _(" %s [options] [<username>]\n"), program_invocation_short_name
);
72 fputs(USAGE_SEPARATOR
, fp
);
73 fputs(_("Change your login shell.\n"), fp
);
75 fputs(USAGE_OPTIONS
, fp
);
76 fputs(_(" -s, --shell <shell> specify login shell\n"), fp
);
77 fputs(_(" -l, --list-shells print list of shells and exit\n"), fp
);
79 fputs(USAGE_SEPARATOR
, fp
);
80 fprintf(fp
, USAGE_HELP_OPTIONS(22));
82 fprintf(fp
, USAGE_MAN_TAIL("chsh(1)"));
88 * parse the command line arguments, and fill in "pinfo" with any
89 * information from the command line.
91 static void parse_argv(int argc
, char **argv
, struct sinfo
*pinfo
)
93 static const struct option long_options
[] = {
94 {"shell", required_argument
, NULL
, 's'},
95 {"list-shells", no_argument
, NULL
, 'l'},
96 {"help", no_argument
, NULL
, 'h'},
97 {"version", no_argument
, NULL
, 'V'},
102 while ((c
= getopt_long(argc
, argv
, "s:lhuvV", long_options
, NULL
)) != -1) {
104 case 'v': /* deprecated */
106 print_version(EXIT_SUCCESS
);
107 case 'u': /* deprecated */
111 print_shells(stdout
, "%s\n");
114 pinfo
->shell
= optarg
;
117 errtryhelp(EXIT_FAILURE
);
120 /* done parsing arguments. check for a username. */
122 if (optind
+ 1 < argc
) {
123 errx(EXIT_FAILURE
, _("cannot handle multiple usernames"));
125 pinfo
->username
= argv
[optind
];
130 * ask_new_shell () --
131 * ask the user for a shell and return it.
133 static char *ask_new_shell(char *question
, char *oldshell
)
141 printf("%s [%s]:", question
, oldshell
);
146 if (getline(&ans
, &dummy
, stdin
) < 0)
149 /* remove the newline at the end of ans. */
150 ltrim_whitespace((unsigned char *) ans
);
151 len
= rtrim_whitespace((unsigned char *) ans
);
158 * check_shell () -- if the shell is completely invalid, print
161 static void check_shell(const char *shell
)
164 errx(EXIT_FAILURE
, _("shell must be a full path name"));
165 if (access(shell
, F_OK
) < 0)
166 errx(EXIT_FAILURE
, _("\"%s\" does not exist"), shell
);
167 if (access(shell
, X_OK
) < 0)
168 errx(EXIT_FAILURE
, _("\"%s\" is not executable"), shell
);
169 if (illegal_passwd_chars(shell
))
170 errx(EXIT_FAILURE
, _("%s: has illegal characters"), shell
);
171 if (!is_known_shell(shell
)) {
172 #ifdef ONLY_LISTED_SHELLS
174 warnx(_("Warning: \"%s\" is not listed in %s."), shell
,
178 _("\"%s\" is not listed in %s.\n"
179 "Use %s -l to see list."), shell
, _PATH_SHELLS
,
180 program_invocation_short_name
);
182 warnx(_("\"%s\" is not listed in %s.\n"
183 "Use %s -l to see list."), shell
, _PATH_SHELLS
,
184 program_invocation_short_name
);
189 int main(int argc
, char **argv
)
191 char *oldshell
, *pwbuf
;
193 const uid_t uid
= getuid();
194 struct sinfo info
= { NULL
};
198 setlocale(LC_ALL
, "");
199 bindtextdomain(PACKAGE
, LOCALEDIR
);
201 close_stdout_atexit();
203 parse_argv(argc
, argv
, &info
);
204 if (!info
.username
) {
205 pw
= xgetpwuid(uid
, &pwbuf
);
207 errx(EXIT_FAILURE
, _("you (user %d) don't exist."),
210 pw
= xgetpwnam(info
.username
, &pwbuf
);
212 errx(EXIT_FAILURE
, _("user \"%s\" does not exist."),
217 if (!(is_local(pw
->pw_name
)))
218 errx(EXIT_FAILURE
, _("can only change local entries"));
221 #ifdef HAVE_LIBSELINUX
222 if (is_selinux_enabled() > 0) {
223 char *user_cxt
= NULL
;
225 if (uid
== 0 && !ul_selinux_has_access("passwd", "chsh", &user_cxt
))
227 _("%s is not authorized to change the shell of %s"),
228 user_cxt
? : _("Unknown user context"),
231 if (ul_setfscreatecon_from_file(_PATH_PASSWD
) != 0)
233 _("can't set default context for %s"), _PATH_PASSWD
);
237 oldshell
= pw
->pw_shell
;
238 if (oldshell
== NULL
|| *oldshell
== '\0') {
239 oldshell
= _PATH_BSHELL
; /* default */
245 /* If we're setuid and not really root, disallow the password change. */
246 if (is_privileged_execution() && uid
!= pw
->pw_uid
) {
248 if (uid
!= 0 && uid
!= pw
->pw_uid
) {
252 _("running UID doesn't match UID of user we're "
253 "altering, shell change denied"));
255 if (uid
!= 0 && !is_known_shell(oldshell
)) {
257 err(EXIT_FAILURE
, _("your shell is not in %s, "
258 "shell change denied"), _PATH_SHELLS
);
261 printf(_("Changing shell for %s.\n"), pw
->pw_name
);
263 #if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
264 if (!auth_pam("chsh", uid
, pw
->pw_name
)) {
269 info
.shell
= ask_new_shell(_("New shell"), oldshell
);
274 check_shell(info
.shell
);
276 if (!nullshell
&& strcmp(oldshell
, info
.shell
) == 0)
277 errx(EXIT_SUCCESS
, _("Shell not changed."));
280 if (set_value_libuser("chsh", pw
->pw_name
, uid
,
281 LU_LOGINSHELL
, info
.shell
) < 0)
282 errx(EXIT_FAILURE
, _("Shell *NOT* changed. Try again later."));
284 pw
->pw_shell
= info
.shell
;
285 if (setpwnam(pw
, ".chsh") < 0)
286 err(EXIT_FAILURE
, _("setpwnam failed\n"
287 "Shell *NOT* changed. Try again later."));
290 printf(_("Shell changed.\n"));