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
32 #include <sys/types.h>
37 #include "closestream.h"
41 #include "pathnames.h"
45 #ifdef HAVE_LIBSELINUX
46 # include <selinux/selinux.h>
47 # include <selinux/av_permissions.h>
48 # include "selinux_utils.h"
56 static void parse_argv(int argc
, char **argv
, struct sinfo
*pinfo
);
57 static char *prompt(char *question
, char *def_val
);
58 static int check_shell(char *shell
);
59 static int get_shell_list(char *shell
);
61 static void __attribute__((__noreturn__
)) usage (FILE *fp
)
63 fputs(USAGE_HEADER
, fp
);
64 fprintf(fp
, _(" %s [options] [username]\n"), program_invocation_short_name
);
65 fputs(USAGE_OPTIONS
, fp
);
66 fputs(_(" -s, --shell <shell> specify login shell\n"), fp
);
67 fputs(_(" -l, --list-shells print list of shells and exit\n"), fp
);
68 fputs(USAGE_SEPARATOR
, fp
);
69 fputs(_(" -u, --help display this help and exit\n"), fp
);
70 fputs(_(" -v, --version output version information and exit\n"), fp
);
71 fprintf(fp
, USAGE_MAN_TAIL("chsh(1)"));
72 exit(fp
== stderr
? EXIT_FAILURE
: EXIT_SUCCESS
);
75 int main(int argc
, char **argv
)
77 char *shell
, *oldshell
;
83 setlocale(LC_ALL
, "");
84 bindtextdomain(PACKAGE
, LOCALEDIR
);
89 memset(&info
, 0, sizeof(info
));
91 parse_argv(argc
, argv
, &info
);
96 errx(EXIT_FAILURE
, _("you (user %d) don't exist."),
99 pw
= getpwnam(info
.username
);
101 errx(EXIT_FAILURE
, _("user \"%s\" does not exist."),
105 if (!(is_local(pw
->pw_name
)))
106 errx(EXIT_FAILURE
, _("can only change local entries."));
108 #ifdef HAVE_LIBSELINUX
109 if (is_selinux_enabled() > 0) {
111 if (checkAccess(pw
->pw_name
, PASSWD__CHSH
) != 0) {
112 security_context_t user_context
;
113 if (getprevcon(&user_context
) < 0)
115 (security_context_t
) NULL
;
118 _("%s is not authorized to change the shell of %s"),
119 user_context
? : _("Unknown user context"),
123 if (setupDefaultContext(_PATH_PASSWD
) != 0)
125 _("can't set default context for %s"), _PATH_PASSWD
);
129 oldshell
= pw
->pw_shell
;
130 if (oldshell
== NULL
|| *oldshell
== '\0')
131 oldshell
= _PATH_BSHELL
; /* default */
134 if (uid
!= 0 && uid
!= pw
->pw_uid
) {
137 _("running UID doesn't match UID of user we're "
138 "altering, shell change denied"));
140 if (uid
!= 0 && !get_shell_list(oldshell
)) {
142 err(EXIT_FAILURE
, _("your shell is not in %s, "
143 "shell change denied"), _PATH_SHELLS
);
148 printf(_("Changing shell for %s.\n"), pw
->pw_name
);
150 #ifdef REQUIRE_PASSWORD
152 pam_handle_t
*pamh
= NULL
;
153 struct pam_conv conv
= { misc_conv
, NULL
};
156 retcode
= pam_start("chsh", pw
->pw_name
, &conv
, &pamh
);
157 if (pam_fail_check(pamh
, retcode
))
160 retcode
= pam_authenticate(pamh
, 0);
161 if (pam_fail_check(pamh
, retcode
))
164 retcode
= pam_acct_mgmt(pamh
, 0);
165 if (retcode
== PAM_NEW_AUTHTOK_REQD
)
167 pam_chauthtok(pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
168 if (pam_fail_check(pamh
, retcode
))
171 retcode
= pam_setcred(pamh
, 0);
172 if (pam_fail_check(pamh
, retcode
))
176 /* no need to establish a session; this isn't a
177 * session-oriented activity... */
179 #endif /* REQUIRE_PASSWORD */
182 shell
= prompt(_("New shell"), oldshell
);
187 if (check_shell(shell
) < 0)
190 if (strcmp(oldshell
, shell
) == 0)
191 errx(EXIT_SUCCESS
, _("Shell not changed."));
192 pw
->pw_shell
= shell
;
193 if (setpwnam(pw
) < 0)
194 err(EXIT_FAILURE
, _("setpwnam failed\n"
195 "Shell *NOT* changed. Try again later."));
197 printf(_("Shell changed.\n"));
203 * parse the command line arguments, and fill in "pinfo" with any
204 * information from the command line.
206 static void parse_argv(int argc
, char **argv
, struct sinfo
*pinfo
)
210 static struct option long_options
[] = {
211 {"shell", required_argument
, 0, 's'},
212 {"list-shells", no_argument
, 0, 'l'},
213 {"help", no_argument
, 0, 'u'},
214 {"version", no_argument
, 0, 'v'},
215 {NULL
, no_argument
, 0, '0'},
220 c
= getopt_long(argc
, argv
, "s:luv", long_options
, &index
);
225 printf(UTIL_LINUX_VERSION
);
230 get_shell_list(NULL
);
235 pinfo
->shell
= optarg
;
241 /* done parsing arguments. check for a username. */
243 if (optind
+ 1 < argc
)
245 pinfo
->username
= argv
[optind
];
251 * ask the user for a given field and return it.
253 static char *prompt(char *question
, char *def_val
)
261 printf("%s [%s]: ", question
, def_val
);
263 if (fgets(buf
, sizeof(buf
), stdin
) == NULL
)
264 errx(EXIT_FAILURE
, _("Aborted."));
265 /* remove the newline at the end of buf. */
267 while (isspace(*ans
))
270 while (len
> 0 && isspace(ans
[len
- 1]))
275 cp
= (char *)xmalloc(len
+ 1);
281 * check_shell () -- if the shell is completely invalid, print
282 * an error and return (-1).
283 * if the shell is a bad idea, print a warning.
285 static int check_shell(char *shell
)
293 warnx(_("shell must be a full path name"));
296 if (access(shell
, F_OK
) < 0) {
297 warnx(_("\"%s\" does not exist"), shell
);
300 if (access(shell
, X_OK
) < 0) {
301 printf(_("\"%s\" is not executable"), shell
);
304 /* keep /etc/passwd clean. */
305 for (i
= 0; i
< strlen(shell
); i
++) {
307 if (c
== ',' || c
== ':' || c
== '=' || c
== '"' || c
== '\n') {
308 warnx(_("'%c' is not allowed"), c
);
312 warnx(_("control characters are not allowed"));
316 #ifdef ONLY_LISTED_SHELLS
317 if (!get_shell_list(shell
)) {
320 ("Warning: \"%s\" is not listed in %s."),
321 shell
, _PATH_SHELLS
);
324 _("\"%s\" is not listed in %s.\n"
325 "Use %s -l to see list."), shell
, _PATH_SHELLS
,
326 program_invocation_short_name
);
329 if (!get_shell_list(shell
)) {
330 warnx(_("\"%s\" is not listed in %s.\n"
331 "Use %s -l to see list."), shell
, _PATH_SHELLS
,
332 program_invocation_short_name
);
339 * get_shell_list () -- if the given shell appears in /etc/shells,
340 * return true. if not, return false.
341 * if the given shell is NULL, /etc/shells is outputted to stdout.
343 static int get_shell_list(char *shell_name
)
351 fp
= fopen(_PATH_SHELLS
, "r");
354 warnx(_("No known shells."));
357 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
358 /* ignore comments */
362 /* strip the ending newline */
363 if (buf
[len
- 1] == '\n')
365 /* ignore lines that are too damn long */
368 /* check or output the shell */
370 if (!strcmp(shell_name
, buf
)) {