]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/chsh.c
scriptreplay: cleanup usage()
[thirdparty/util-linux.git] / login-utils / chsh.c
CommitLineData
6dbe3af9
KZ
1/*
2 * chsh.c -- change your login shell
3 * (c) 1994 by salvatore valente <svalente@athena.mit.edu>
6adb1ef2 4 * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
6dbe3af9
KZ
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 *
fd6b7a7f 10 * $Author: aebr $
2b6fc908
KZ
11 * $Revision: 1.19 $
12 * $Date: 1998/06/11 22:30:14 $
726f69e2
KZ
13 *
14 * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security
fd6b7a7f
KZ
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 *
b50945d4 21 * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
7eda085c 22 * - added Native Language Support
6dbe3af9 23 */
0a065b7a 24
6dbe3af9 25#include <ctype.h>
0a065b7a 26#include <errno.h>
6dbe3af9 27#include <getopt.h>
0a065b7a 28#include <pwd.h>
0a065b7a
SK
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <sys/types.h>
33#include <unistd.h>
d39d4bf6
KZ
34
35#include "c.h"
0a065b7a 36#include "env.h"
439cdf1e 37#include "closestream.h"
66ee8158 38#include "islocal.h"
7eda085c 39#include "nls.h"
7ab59baf 40#include "pathnames.h"
83db544d 41#include "pwdutils.h"
0a065b7a 42#include "setpwnam.h"
e323df25 43#include "strutils.h"
d39d4bf6 44#include "xalloc.h"
fd6b7a7f 45
144ae70e
SK
46#include "ch-common.h"
47
48d7b13a 48#ifdef HAVE_LIBSELINUX
0a065b7a 49# include <selinux/selinux.h>
0a065b7a 50# include "selinux_utils.h"
d03dd608
KZ
51#endif
52
6adb1ef2
CM
53
54#ifdef HAVE_LIBUSER
55# include <libuser/user.h>
56# include "libuser.h"
d86918b6 57#elif CHFN_CHSH_PASSWORD
6adb1ef2
CM
58# include "auth.h"
59#endif
60
e41ae450
SK
61#ifdef HAVE_LIBREADLINE
62# define _FUNCTION_DEF
63# include <readline/readline.h>
64#endif
65
6dbe3af9 66struct sinfo {
0a065b7a
SK
67 char *username;
68 char *shell;
6dbe3af9
KZ
69};
70
6e1eda6f 71static void __attribute__((__noreturn__)) usage(void)
d39d4bf6 72{
6e1eda6f 73 FILE *fp = stdout;
0a065b7a 74 fputs(USAGE_HEADER, fp);
09af3db4 75 fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
451dbcfa
BS
76
77 fputs(USAGE_SEPARATOR, fp);
78 fputs(_("Change your login shell.\n"), fp);
79
0a065b7a
SK
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);
b3054454
RM
84 printf( " -u, --help %s\n", USAGE_OPTSTR_HELP);
85 printf( " -v, --version %s\n", USAGE_OPTSTR_VERSION);
f45f3ec3 86 printf(USAGE_MAN_TAIL("chsh(1)"));
6e1eda6f 87 exit(EXIT_SUCCESS);
d39d4bf6 88}
6dbe3af9 89
d1818b9e 90/*
54373fb9 91 * is_known_shell() -- if the given shell appears in /etc/shells,
d1818b9e 92 * return true. if not, return false.
d1818b9e 93 */
5f032ae4 94static int is_known_shell(const char *shell_name)
d1818b9e 95{
a1f5bb9d 96 char *s, ret = 0;
d1818b9e 97
a1f5bb9d 98 if (!shell_name)
54373fb9
KZ
99 return 0;
100
a1f5bb9d
SK
101 setusershell();
102 while ((s = getusershell())) {
103 if (strcmp(shell_name, s) == 0) {
104 ret = 1;
105 break;
106 }
0a065b7a 107 }
a1f5bb9d
SK
108 endusershell();
109 return ret;
54373fb9
KZ
110}
111
112/*
113 * print_shells () -- /etc/shells is outputted to stdout.
114 */
5f032ae4 115static void print_shells(void)
54373fb9 116{
a1f5bb9d 117 char *s;
54373fb9 118
a1f5bb9d
SK
119 while ((s = getusershell()))
120 printf("%s\n", s);
121 endusershell();
6dbe3af9
KZ
122}
123
d1818b9e
SK
124#ifdef HAVE_LIBREADLINE
125static char *shell_name_generator(const char *text, int state)
126{
a1f5bb9d 127 static size_t len;
d1818b9e
SK
128 char *s;
129
130 if (!state) {
a1f5bb9d 131 setusershell();
d1818b9e
SK
132 len = strlen(text);
133 }
a1f5bb9d
SK
134
135 while ((s = getusershell())) {
d1818b9e
SK
136 if (strncmp(s, text, len) == 0)
137 return xstrdup(s);
138 }
139 return NULL;
140}
141
142static 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
6dbe3af9
KZ
151/*
152 * parse_argv () --
153 * parse the command line arguments, and fill in "pinfo" with any
154 * information from the command line.
155 */
5f032ae4 156static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
0a065b7a 157{
4a98629b 158 static const struct option long_options[] = {
87918040
SK
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},
0a065b7a 164 };
68b24d53 165 int c;
0a065b7a 166
631a1954 167 while ((c = getopt_long(argc, argv, "s:lhuv", long_options, NULL)) != -1) {
0a065b7a 168 switch (c) {
0a065b7a 169 case 'v':
2c308875 170 print_version(EXIT_SUCCESS);
631a1954
AH
171 case 'u': /* deprecated */
172 case 'h':
6e1eda6f 173 usage();
0a065b7a 174 case 'l':
5f032ae4 175 print_shells();
0a065b7a
SK
176 exit(EXIT_SUCCESS);
177 case 's':
0a065b7a
SK
178 pinfo->shell = optarg;
179 break;
180 default:
677ec86c 181 errtryhelp(EXIT_FAILURE);
0a065b7a
SK
182 }
183 }
184 /* done parsing arguments. check for a username. */
185 if (optind < argc) {
6e1eda6f
RM
186 if (optind + 1 < argc) {
187 errx(EXIT_FAILURE, _("cannot handle multiple usernames"));
188 }
0a065b7a 189 pinfo->username = argv[optind];
6dbe3af9 190 }
6dbe3af9
KZ
191}
192
6dbe3af9 193/*
e323df25
SK
194 * ask_new_shell () --
195 * ask the user for a shell and return it.
6dbe3af9 196 */
e323df25 197static char *ask_new_shell(char *question, char *oldshell)
0a065b7a
SK
198{
199 int len;
e323df25 200 char *ans = NULL;
d1818b9e
SK
201#ifdef HAVE_LIBREADLINE
202 rl_attempted_completion_function = shell_name_completion;
203#else
e323df25 204 size_t dummy = 0;
e41ae450 205#endif
e323df25
SK
206 if (!oldshell)
207 oldshell = "";
d1818b9e 208 printf("%s [%s]\n", question, oldshell);
e41ae450 209#ifdef HAVE_LIBREADLINE
d1818b9e 210 if ((ans = readline("> ")) == NULL)
e41ae450
SK
211#else
212 if (getline(&ans, &dummy, stdin) < 0)
213#endif
0a065b7a 214 return NULL;
e323df25
SK
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;
6dbe3af9
KZ
221}
222
223/*
224 * check_shell () -- if the shell is completely invalid, print
1df0219f 225 * an error and exit.
6dbe3af9 226 */
5f032ae4 227static void check_shell(const char *shell)
0a065b7a 228{
1df0219f
SK
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);
5f032ae4 237 if (!is_known_shell(shell)) {
1df0219f 238#ifdef ONLY_LISTED_SHELLS
0a065b7a 239 if (!getuid())
1df0219f
SK
240 warnx(_("Warning: \"%s\" is not listed in %s."), shell,
241 _PATH_SHELLS);
0a065b7a
SK
242 else
243 errx(EXIT_FAILURE,
c900336d
SK
244 _("\"%s\" is not listed in %s.\n"
245 "Use %s -l to see list."), shell, _PATH_SHELLS,
0a065b7a 246 program_invocation_short_name);
726f69e2 247#else
c900336d
SK
248 warnx(_("\"%s\" is not listed in %s.\n"
249 "Use %s -l to see list."), shell, _PATH_SHELLS,
1df0219f 250 program_invocation_short_name);
726f69e2 251#endif
1df0219f 252 }
6dbe3af9
KZ
253}
254
5eef6129 255int main(int argc, char **argv)
0a065b7a 256{
83db544d 257 char *oldshell, *pwbuf;
f6497923 258 int nullshell = 0;
561c4858 259 const uid_t uid = getuid();
87918040 260 struct sinfo info = { NULL };
5eef6129 261 struct passwd *pw;
0a065b7a 262
5eef6129
SK
263 sanitize_env();
264 setlocale(LC_ALL, "");
265 bindtextdomain(PACKAGE, LOCALEDIR);
266 textdomain(PACKAGE);
2c308875 267 close_stdout_atexit();
5eef6129 268
5f032ae4 269 parse_argv(argc, argv, &info);
5eef6129 270 if (!info.username) {
83db544d 271 pw = xgetpwuid(uid, &pwbuf);
5eef6129
SK
272 if (!pw)
273 errx(EXIT_FAILURE, _("you (user %d) don't exist."),
274 uid);
275 } else {
83db544d 276 pw = xgetpwnam(info.username, &pwbuf);
5eef6129
SK
277 if (!pw)
278 errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
279 info.username);
0a065b7a 280 }
5eef6129
SK
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) {
dd5ef107
KZ
290 access_vector_t av = get_access_vector("passwd", "chsh");
291
292 if (selinux_check_passwd_access(av) != 0) {
5eef6129
SK
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);
0a065b7a 302 }
5eef6129
SK
303 }
304 if (setupDefaultContext(_PATH_PASSWD) != 0)
305 errx(EXIT_FAILURE,
306 _("can't set default context for %s"), _PATH_PASSWD);
6dbe3af9 307 }
5eef6129
SK
308#endif
309
310 oldshell = pw->pw_shell;
f6497923 311 if (oldshell == NULL || *oldshell == '\0') {
5eef6129 312 oldshell = _PATH_BSHELL; /* default */
f6497923
SK
313 nullshell = 1;
314 }
5eef6129
SK
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 }
5f032ae4 328 if (uid != 0 && !is_known_shell(oldshell)) {
5eef6129
SK
329 errno = EACCES;
330 err(EXIT_FAILURE, _("your shell is not in %s, "
331 "shell change denied"), _PATH_SHELLS);
332 }
333
5eef6129
SK
334 printf(_("Changing shell for %s.\n"), pw->pw_name);
335
336#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
561c4858 337 if (!auth_pam("chsh", uid, pw->pw_name)) {
5eef6129
SK
338 return EXIT_FAILURE;
339 }
340#endif
561c4858 341 if (!info.shell) {
e323df25 342 info.shell = ask_new_shell(_("New shell"), oldshell);
561c4858 343 if (!info.shell)
5eef6129
SK
344 return EXIT_SUCCESS;
345 }
346
5f032ae4 347 check_shell(info.shell);
5eef6129 348
f6497923 349 if (!nullshell && strcmp(oldshell, info.shell) == 0)
5eef6129
SK
350 errx(EXIT_SUCCESS, _("Shell not changed."));
351
352#ifdef HAVE_LIBUSER
353 if (set_value_libuser("chsh", pw->pw_name, uid,
561c4858 354 LU_LOGINSHELL, info.shell) < 0)
5eef6129
SK
355 errx(EXIT_FAILURE, _("Shell *NOT* changed. Try again later."));
356#else
561c4858 357 pw->pw_shell = info.shell;
bde91c85 358 if (setpwnam(pw, ".chsh") < 0)
5eef6129
SK
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;
6dbe3af9 365}