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