]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/chsh.c
libblkid: check status for the current CDROM slot
[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 * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
5 *
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.
9 *
10 * $Author: aebr $
11 * $Revision: 1.19 $
12 * $Date: 1998/06/11 22:30:14 $
13 *
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>
16 *
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.
20 *
21 * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
22 * - added Native Language Support
23 */
24
25 #include <ctype.h>
26 #include <errno.h>
27 #include <getopt.h>
28 #include <pwd.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 "pathnames.h"
41 #include "pwdutils.h"
42 #include "setpwnam.h"
43 #include "strutils.h"
44 #include "xalloc.h"
45
46 #include "ch-common.h"
47
48 #ifdef HAVE_LIBSELINUX
49 # include <selinux/selinux.h>
50 # include "selinux_utils.h"
51 #endif
52
53
54 #ifdef HAVE_LIBUSER
55 # include <libuser/user.h>
56 # include "libuser.h"
57 #elif CHFN_CHSH_PASSWORD
58 # include "auth.h"
59 #endif
60
61 #ifdef HAVE_LIBREADLINE
62 # define _FUNCTION_DEF
63 # include <readline/readline.h>
64 #endif
65
66 struct sinfo {
67 char *username;
68 char *shell;
69 };
70
71 static void __attribute__((__noreturn__)) usage(void)
72 {
73 FILE *fp = stdout;
74 fputs(USAGE_HEADER, fp);
75 fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
76
77 fputs(USAGE_SEPARATOR, fp);
78 fputs(_("Change your login shell.\n"), fp);
79
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)"));
87 exit(EXIT_SUCCESS);
88 }
89
90 /*
91 * is_known_shell() -- if the given shell appears in /etc/shells,
92 * return true. if not, return false.
93 */
94 static int is_known_shell(const char *shell_name)
95 {
96 char *s, ret = 0;
97
98 if (!shell_name)
99 return 0;
100
101 setusershell();
102 while ((s = getusershell())) {
103 if (strcmp(shell_name, s) == 0) {
104 ret = 1;
105 break;
106 }
107 }
108 endusershell();
109 return ret;
110 }
111
112 /*
113 * print_shells () -- /etc/shells is outputted to stdout.
114 */
115 static void print_shells(void)
116 {
117 char *s;
118
119 while ((s = getusershell()))
120 printf("%s\n", s);
121 endusershell();
122 }
123
124 #ifdef HAVE_LIBREADLINE
125 static char *shell_name_generator(const char *text, int state)
126 {
127 static size_t len;
128 char *s;
129
130 if (!state) {
131 setusershell();
132 len = strlen(text);
133 }
134
135 while ((s = getusershell())) {
136 if (strncmp(s, text, len) == 0)
137 return xstrdup(s);
138 }
139 return NULL;
140 }
141
142 static char **shell_name_completion(const char *text,
143 int start __attribute__((__unused__)),
144 int end __attribute__((__unused__)))
145 {
146 rl_attempted_completion_over = 1;
147 return rl_completion_matches(text, shell_name_generator);
148 }
149 #endif
150
151 /*
152 * parse_argv () --
153 * parse the command line arguments, and fill in "pinfo" with any
154 * information from the command line.
155 */
156 static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
157 {
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'},
163 {NULL, 0, NULL, 0},
164 };
165 int c;
166
167 while ((c = getopt_long(argc, argv, "s:lhuv", long_options, NULL)) != -1) {
168 switch (c) {
169 case 'v':
170 print_version(EXIT_SUCCESS);
171 case 'u': /* deprecated */
172 case 'h':
173 usage();
174 case 'l':
175 print_shells();
176 exit(EXIT_SUCCESS);
177 case 's':
178 pinfo->shell = optarg;
179 break;
180 default:
181 errtryhelp(EXIT_FAILURE);
182 }
183 }
184 /* done parsing arguments. check for a username. */
185 if (optind < argc) {
186 if (optind + 1 < argc) {
187 errx(EXIT_FAILURE, _("cannot handle multiple usernames"));
188 }
189 pinfo->username = argv[optind];
190 }
191 }
192
193 /*
194 * ask_new_shell () --
195 * ask the user for a shell and return it.
196 */
197 static char *ask_new_shell(char *question, char *oldshell)
198 {
199 int len;
200 char *ans = NULL;
201 #ifdef HAVE_LIBREADLINE
202 rl_attempted_completion_function = shell_name_completion;
203 #else
204 size_t dummy = 0;
205 #endif
206 if (!oldshell)
207 oldshell = "";
208 printf("%s [%s]\n", question, oldshell);
209 #ifdef HAVE_LIBREADLINE
210 if ((ans = readline("> ")) == NULL)
211 #else
212 if (getline(&ans, &dummy, stdin) < 0)
213 #endif
214 return NULL;
215 /* remove the newline at the end of ans. */
216 ltrim_whitespace((unsigned char *) ans);
217 len = rtrim_whitespace((unsigned char *) ans);
218 if (len == 0)
219 return NULL;
220 return ans;
221 }
222
223 /*
224 * check_shell () -- if the shell is completely invalid, print
225 * an error and exit.
226 */
227 static void check_shell(const char *shell)
228 {
229 if (*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
239 if (!getuid())
240 warnx(_("Warning: \"%s\" is not listed in %s."), shell,
241 _PATH_SHELLS);
242 else
243 errx(EXIT_FAILURE,
244 _("\"%s\" is not listed in %s.\n"
245 "Use %s -l to see list."), shell, _PATH_SHELLS,
246 program_invocation_short_name);
247 #else
248 warnx(_("\"%s\" is not listed in %s.\n"
249 "Use %s -l to see list."), shell, _PATH_SHELLS,
250 program_invocation_short_name);
251 #endif
252 }
253 }
254
255 int main(int argc, char **argv)
256 {
257 char *oldshell, *pwbuf;
258 int nullshell = 0;
259 const uid_t uid = getuid();
260 struct sinfo info = { NULL };
261 struct passwd *pw;
262
263 sanitize_env();
264 setlocale(LC_ALL, "");
265 bindtextdomain(PACKAGE, LOCALEDIR);
266 textdomain(PACKAGE);
267 close_stdout_atexit();
268
269 parse_argv(argc, argv, &info);
270 if (!info.username) {
271 pw = xgetpwuid(uid, &pwbuf);
272 if (!pw)
273 errx(EXIT_FAILURE, _("you (user %d) don't exist."),
274 uid);
275 } else {
276 pw = xgetpwnam(info.username, &pwbuf);
277 if (!pw)
278 errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
279 info.username);
280 }
281
282 #ifndef HAVE_LIBUSER
283 if (!(is_local(pw->pw_name)))
284 errx(EXIT_FAILURE, _("can only change local entries"));
285 #endif
286
287 #ifdef HAVE_LIBSELINUX
288 if (is_selinux_enabled() > 0) {
289 if (uid == 0) {
290 access_vector_t av = get_access_vector("passwd", "chsh");
291
292 if (selinux_check_passwd_access(av) != 0) {
293 security_context_t user_context;
294 if (getprevcon(&user_context) < 0)
295 user_context =
296 (security_context_t) NULL;
297
298 errx(EXIT_FAILURE,
299 _("%s is not authorized to change the shell of %s"),
300 user_context ? : _("Unknown user context"),
301 pw->pw_name);
302 }
303 }
304 if (setupDefaultContext(_PATH_PASSWD) != 0)
305 errx(EXIT_FAILURE,
306 _("can't set default context for %s"), _PATH_PASSWD);
307 }
308 #endif
309
310 oldshell = pw->pw_shell;
311 if (oldshell == NULL || *oldshell == '\0') {
312 oldshell = _PATH_BSHELL; /* default */
313 nullshell = 1;
314 }
315
316 /* reality check */
317 #ifdef HAVE_LIBUSER
318 /* If we're setuid and not really root, disallow the password change. */
319 if (geteuid() != getuid() && uid != pw->pw_uid) {
320 #else
321 if (uid != 0 && uid != pw->pw_uid) {
322 #endif
323 errno = EACCES;
324 err(EXIT_FAILURE,
325 _("running UID doesn't match UID of user we're "
326 "altering, shell change denied"));
327 }
328 if (uid != 0 && !is_known_shell(oldshell)) {
329 errno = EACCES;
330 err(EXIT_FAILURE, _("your shell is not in %s, "
331 "shell change denied"), _PATH_SHELLS);
332 }
333
334 printf(_("Changing shell for %s.\n"), pw->pw_name);
335
336 #if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
337 if (!auth_pam("chsh", uid, pw->pw_name)) {
338 return EXIT_FAILURE;
339 }
340 #endif
341 if (!info.shell) {
342 info.shell = ask_new_shell(_("New shell"), oldshell);
343 if (!info.shell)
344 return EXIT_SUCCESS;
345 }
346
347 check_shell(info.shell);
348
349 if (!nullshell && strcmp(oldshell, info.shell) == 0)
350 errx(EXIT_SUCCESS, _("Shell not changed."));
351
352 #ifdef HAVE_LIBUSER
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."));
356 #else
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."));
361 #endif
362
363 printf(_("Shell changed.\n"));
364 return EXIT_SUCCESS;
365 }