]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/chsh.c
login-utils: verify writing to streams was successful
[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 exit(EXIT_FAILURE);
159
160 retcode = pam_authenticate(pamh, 0);
161 if (pam_fail_check(pamh, retcode))
162 exit(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 exit(EXIT_FAILURE);
170
171 retcode = pam_setcred(pamh, 0);
172 if (pam_fail_check(pamh, retcode))
173 exit(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 warn(_("setpwnam failed\n"
195 "Shell *NOT* changed. Try again later."));
196 return EXIT_FAILURE;
197 }
198 printf(_("Shell changed.\n"));
199 return EXIT_SUCCESS;
200 }
201
202 /*
203 * parse_argv () --
204 * parse the command line arguments, and fill in "pinfo" with any
205 * information from the command line.
206 */
207 static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
208 {
209 int index, c;
210
211 static struct option long_options[] = {
212 {"shell", required_argument, 0, 's'},
213 {"list-shells", no_argument, 0, 'l'},
214 {"help", no_argument, 0, 'u'},
215 {"version", no_argument, 0, 'v'},
216 {NULL, no_argument, 0, '0'},
217 };
218
219 optind = c = 0;
220 while (c != EOF) {
221 c = getopt_long(argc, argv, "s:luv", long_options, &index);
222 switch (c) {
223 case -1:
224 break;
225 case 'v':
226 printf(UTIL_LINUX_VERSION);
227 exit(EXIT_SUCCESS);
228 case 'u':
229 usage(stdout);
230 case 'l':
231 get_shell_list(NULL);
232 exit(EXIT_SUCCESS);
233 case 's':
234 if (!optarg)
235 usage(stderr);
236 pinfo->shell = optarg;
237 break;
238 default:
239 usage(stderr);
240 }
241 }
242 /* done parsing arguments. check for a username. */
243 if (optind < argc) {
244 if (optind + 1 < argc)
245 usage(stderr);
246 pinfo->username = argv[optind];
247 }
248 }
249
250 /*
251 * prompt () --
252 * ask the user for a given field and return it.
253 */
254 static char *prompt(char *question, char *def_val)
255 {
256 int len;
257 char *ans, *cp;
258 char buf[BUFSIZ];
259
260 if (!def_val)
261 def_val = "";
262 printf("%s [%s]: ", question, def_val);
263 *buf = 0;
264 if (fgets(buf, sizeof(buf), stdin) == NULL)
265 errx(EXIT_FAILURE, _("Aborted."));
266 /* remove the newline at the end of buf. */
267 ans = buf;
268 while (isspace(*ans))
269 ans++;
270 len = strlen(ans);
271 while (len > 0 && isspace(ans[len - 1]))
272 len--;
273 if (len <= 0)
274 return NULL;
275 ans[len] = 0;
276 cp = (char *)xmalloc(len + 1);
277 strcpy(cp, ans);
278 return cp;
279 }
280
281 /*
282 * check_shell () -- if the shell is completely invalid, print
283 * an error and return (-1).
284 * if the shell is a bad idea, print a warning.
285 */
286 static int check_shell(char *shell)
287 {
288 unsigned int i, c;
289
290 if (!shell)
291 return -1;
292
293 if (*shell != '/') {
294 warnx(_("shell must be a full path name"));
295 return -1;
296 }
297 if (access(shell, F_OK) < 0) {
298 warnx(_("\"%s\" does not exist"), shell);
299 return -1;
300 }
301 if (access(shell, X_OK) < 0) {
302 printf(_("\"%s\" is not executable"), shell);
303 return -1;
304 }
305 /* keep /etc/passwd clean. */
306 for (i = 0; i < strlen(shell); i++) {
307 c = shell[i];
308 if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
309 warnx(_("'%c' is not allowed"), c);
310 return -1;
311 }
312 if (iscntrl(c)) {
313 warnx(_("control characters are not allowed"));
314 return -1;
315 }
316 }
317 #ifdef ONLY_LISTED_SHELLS
318 if (!get_shell_list(shell)) {
319 if (!getuid())
320 warnx(_
321 ("Warning: \"%s\" is not listed in %s."),
322 shell, _PATH_SHELLS);
323 else
324 errx(EXIT_FAILURE,
325 _("\"%s\" is not listed in %s.\n"
326 "Use %s -l to see list."), shell, _PATH_SHELLS,
327 program_invocation_short_name);
328 }
329 #else
330 if (!get_shell_list(shell)) {
331 warnx(_("\"%s\" is not listed in %s.\n"
332 "Use %s -l to see list."), shell, _PATH_SHELLS,
333 program_invocation_short_name);
334 }
335 #endif
336 return 0;
337 }
338
339 /*
340 * get_shell_list () -- if the given shell appears in /etc/shells,
341 * return true. if not, return false.
342 * if the given shell is NULL, /etc/shells is outputted to stdout.
343 */
344 static int get_shell_list(char *shell_name)
345 {
346 FILE *fp;
347 int found;
348 int len;
349 char buf[PATH_MAX];
350
351 found = false;
352 fp = fopen(_PATH_SHELLS, "r");
353 if (!fp) {
354 if (!shell_name)
355 warnx(_("No known shells."));
356 return true;
357 }
358 while (fgets(buf, sizeof(buf), fp) != NULL) {
359 /* ignore comments */
360 if (*buf == '#')
361 continue;
362 len = strlen(buf);
363 /* strip the ending newline */
364 if (buf[len - 1] == '\n')
365 buf[len - 1] = 0;
366 /* ignore lines that are too damn long */
367 else
368 continue;
369 /* check or output the shell */
370 if (shell_name) {
371 if (!strcmp(shell_name, buf)) {
372 found = true;
373 break;
374 }
375 } else
376 printf("%s\n", buf);
377 }
378 fclose(fp);
379 return found;
380 }