]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/chsh.c
6e9325deb6d447b93e138805f49e5aaf6560a350
[thirdparty/util-linux.git] / login-utils / chsh.c
1 /*
2 * chsh.c -- change your login shell
3 * (c) 1994 by salvatore valente <svalente@athena.mit.edu>
4 *
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.
8 *
9 * $Author: aebr $
10 * $Revision: 1.19 $
11 * $Date: 1998/06/11 22:30:14 $
12 *
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>
15 *
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.
19 *
20 * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
21 * - added Native Language Support
22 */
23
24 #include <ctype.h>
25 #include <errno.h>
26 #include <getopt.h>
27 #include <pwd.h>
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <unistd.h>
34
35 #include "c.h"
36 #include "env.h"
37 #include "closestream.h"
38 #include "islocal.h"
39 #include "nls.h"
40 #include "pamfail.h"
41 #include "pathnames.h"
42 #include "setpwnam.h"
43 #include "xalloc.h"
44
45 #ifdef HAVE_LIBSELINUX
46 # include <selinux/selinux.h>
47 # include <selinux/av_permissions.h>
48 # include "selinux_utils.h"
49 #endif
50
51 struct sinfo {
52 char *username;
53 char *shell;
54 };
55
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);
60
61 static void __attribute__((__noreturn__)) usage (FILE *fp)
62 {
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);
73 }
74
75 int main(int argc, char **argv)
76 {
77 char *shell, *oldshell;
78 uid_t uid;
79 struct sinfo info;
80 struct passwd *pw;
81
82 sanitize_env();
83 setlocale(LC_ALL, "");
84 bindtextdomain(PACKAGE, LOCALEDIR);
85 textdomain(PACKAGE);
86 atexit(close_stdout);
87
88 uid = getuid();
89 memset(&info, 0, sizeof(info));
90
91 parse_argv(argc, argv, &info);
92 pw = NULL;
93 if (!info.username) {
94 pw = getpwuid(uid);
95 if (!pw)
96 errx(EXIT_FAILURE, _("you (user %d) don't exist."),
97 uid);
98 } else {
99 pw = getpwnam(info.username);
100 if (!pw)
101 errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
102 info.username);
103 }
104
105 if (!(is_local(pw->pw_name)))
106 errx(EXIT_FAILURE, _("can only change local entries."));
107
108 #ifdef HAVE_LIBSELINUX
109 if (is_selinux_enabled() > 0) {
110 if (uid == 0) {
111 if (checkAccess(pw->pw_name, PASSWD__CHSH) != 0) {
112 security_context_t user_context;
113 if (getprevcon(&user_context) < 0)
114 user_context =
115 (security_context_t) NULL;
116
117 errx(EXIT_FAILURE,
118 _("%s is not authorized to change the shell of %s"),
119 user_context ? : _("Unknown user context"),
120 pw->pw_name);
121 }
122 }
123 if (setupDefaultContext(_PATH_PASSWD) != 0)
124 errx(EXIT_FAILURE,
125 _("can't set default context for %s"), _PATH_PASSWD);
126 }
127 #endif
128
129 oldshell = pw->pw_shell;
130 if (oldshell == NULL || *oldshell == '\0')
131 oldshell = _PATH_BSHELL; /* default */
132
133 /* reality check */
134 if (uid != 0 && uid != pw->pw_uid) {
135 errno = EACCES;
136 err(EXIT_FAILURE,
137 _("running UID doesn't match UID of user we're "
138 "altering, shell change denied"));
139 }
140 if (uid != 0 && !get_shell_list(oldshell)) {
141 errno = EACCES;
142 err(EXIT_FAILURE, _("your shell is not in %s, "
143 "shell change denied"), _PATH_SHELLS);
144 }
145
146 shell = info.shell;
147
148 printf(_("Changing shell for %s.\n"), pw->pw_name);
149
150 #ifdef REQUIRE_PASSWORD
151 if (uid != 0) {
152 pam_handle_t *pamh = NULL;
153 struct pam_conv conv = { misc_conv, NULL };
154 int retcode;
155
156 retcode = pam_start("chsh", pw->pw_name, &conv, &pamh);
157 if (pam_fail_check(pamh, retcode))
158 return EXIT_FAILURE;
159
160 retcode = pam_authenticate(pamh, 0);
161 if (pam_fail_check(pamh, retcode))
162 return EXIT_FAILURE;
163
164 retcode = pam_acct_mgmt(pamh, 0);
165 if (retcode == PAM_NEW_AUTHTOK_REQD)
166 retcode =
167 pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
168 if (pam_fail_check(pamh, retcode))
169 return EXIT_FAILURE;
170
171 retcode = pam_setcred(pamh, 0);
172 if (pam_fail_check(pamh, retcode))
173 return EXIT_FAILURE;
174
175 pam_end(pamh, 0);
176 /* no need to establish a session; this isn't a
177 * session-oriented activity... */
178 }
179 #endif /* REQUIRE_PASSWORD */
180
181 if (!shell) {
182 shell = prompt(_("New shell"), oldshell);
183 if (!shell)
184 return EXIT_SUCCESS;
185 }
186
187 if (check_shell(shell) < 0)
188 return EXIT_FAILURE;
189
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."));
196
197 printf(_("Shell changed.\n"));
198 return EXIT_SUCCESS;
199 }
200
201 /*
202 * parse_argv () --
203 * parse the command line arguments, and fill in "pinfo" with any
204 * information from the command line.
205 */
206 static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
207 {
208 int index, c;
209
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'},
216 };
217
218 optind = c = 0;
219 while (c != EOF) {
220 c = getopt_long(argc, argv, "s:luv", long_options, &index);
221 switch (c) {
222 case -1:
223 break;
224 case 'v':
225 printf(UTIL_LINUX_VERSION);
226 exit(EXIT_SUCCESS);
227 case 'u':
228 usage(stdout);
229 case 'l':
230 get_shell_list(NULL);
231 exit(EXIT_SUCCESS);
232 case 's':
233 if (!optarg)
234 usage(stderr);
235 pinfo->shell = optarg;
236 break;
237 default:
238 usage(stderr);
239 }
240 }
241 /* done parsing arguments. check for a username. */
242 if (optind < argc) {
243 if (optind + 1 < argc)
244 usage(stderr);
245 pinfo->username = argv[optind];
246 }
247 }
248
249 /*
250 * prompt () --
251 * ask the user for a given field and return it.
252 */
253 static char *prompt(char *question, char *def_val)
254 {
255 int len;
256 char *ans, *cp;
257 char buf[BUFSIZ];
258
259 if (!def_val)
260 def_val = "";
261 printf("%s [%s]: ", question, def_val);
262 *buf = 0;
263 if (fgets(buf, sizeof(buf), stdin) == NULL)
264 errx(EXIT_FAILURE, _("Aborted."));
265 /* remove the newline at the end of buf. */
266 ans = buf;
267 while (isspace(*ans))
268 ans++;
269 len = strlen(ans);
270 while (len > 0 && isspace(ans[len - 1]))
271 len--;
272 if (len <= 0)
273 return NULL;
274 ans[len] = 0;
275 cp = (char *)xmalloc(len + 1);
276 strcpy(cp, ans);
277 return cp;
278 }
279
280 /*
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.
284 */
285 static int check_shell(char *shell)
286 {
287 unsigned int i, c;
288
289 if (!shell)
290 return -1;
291
292 if (*shell != '/') {
293 warnx(_("shell must be a full path name"));
294 return -1;
295 }
296 if (access(shell, F_OK) < 0) {
297 warnx(_("\"%s\" does not exist"), shell);
298 return -1;
299 }
300 if (access(shell, X_OK) < 0) {
301 printf(_("\"%s\" is not executable"), shell);
302 return -1;
303 }
304 /* keep /etc/passwd clean. */
305 for (i = 0; i < strlen(shell); i++) {
306 c = shell[i];
307 if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
308 warnx(_("'%c' is not allowed"), c);
309 return -1;
310 }
311 if (iscntrl(c)) {
312 warnx(_("control characters are not allowed"));
313 return -1;
314 }
315 }
316 #ifdef ONLY_LISTED_SHELLS
317 if (!get_shell_list(shell)) {
318 if (!getuid())
319 warnx(_
320 ("Warning: \"%s\" is not listed in %s."),
321 shell, _PATH_SHELLS);
322 else
323 errx(EXIT_FAILURE,
324 _("\"%s\" is not listed in %s.\n"
325 "Use %s -l to see list."), shell, _PATH_SHELLS,
326 program_invocation_short_name);
327 }
328 #else
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);
333 }
334 #endif
335 return 0;
336 }
337
338 /*
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.
342 */
343 static int get_shell_list(char *shell_name)
344 {
345 FILE *fp;
346 int found;
347 int len;
348 char buf[PATH_MAX];
349
350 found = false;
351 fp = fopen(_PATH_SHELLS, "r");
352 if (!fp) {
353 if (!shell_name)
354 warnx(_("No known shells."));
355 return true;
356 }
357 while (fgets(buf, sizeof(buf), fp) != NULL) {
358 /* ignore comments */
359 if (*buf == '#')
360 continue;
361 len = strlen(buf);
362 /* strip the ending newline */
363 if (buf[len - 1] == '\n')
364 buf[len - 1] = 0;
365 /* ignore lines that are too damn long */
366 else
367 continue;
368 /* check or output the shell */
369 if (shell_name) {
370 if (!strcmp(shell_name, buf)) {
371 found = true;
372 break;
373 }
374 } else
375 printf("%s\n", buf);
376 }
377 fclose(fp);
378 return found;
379 }