]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/chsh.c
Merge branch 'usage-part2' of https://github.com/rudimeier/util-linux
[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"
0a065b7a 41#include "setpwnam.h"
e323df25 42#include "strutils.h"
d39d4bf6 43#include "xalloc.h"
fd6b7a7f 44
144ae70e
SK
45#include "ch-common.h"
46
48d7b13a 47#ifdef HAVE_LIBSELINUX
0a065b7a 48# include <selinux/selinux.h>
0a065b7a 49# include "selinux_utils.h"
d03dd608
KZ
50#endif
51
6adb1ef2
CM
52
53#ifdef HAVE_LIBUSER
54# include <libuser/user.h>
55# include "libuser.h"
d86918b6 56#elif CHFN_CHSH_PASSWORD
6adb1ef2
CM
57# include "auth.h"
58#endif
59
e41ae450
SK
60#ifdef HAVE_LIBREADLINE
61# define _FUNCTION_DEF
62# include <readline/readline.h>
63#endif
64
6dbe3af9 65struct sinfo {
0a065b7a
SK
66 char *username;
67 char *shell;
6dbe3af9
KZ
68};
69
d1818b9e 70/* global due readline completion */
5f032ae4 71static char **global_shells;
66ee8158 72
6e1eda6f 73static void __attribute__((__noreturn__)) usage(void)
d39d4bf6 74{
6e1eda6f 75 FILE *fp = stdout;
0a065b7a 76 fputs(USAGE_HEADER, fp);
09af3db4 77 fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
451dbcfa
BS
78
79 fputs(USAGE_SEPARATOR, fp);
80 fputs(_("Change your login shell.\n"), fp);
81
0a065b7a
SK
82 fputs(USAGE_OPTIONS, fp);
83 fputs(_(" -s, --shell <shell> specify login shell\n"), fp);
84 fputs(_(" -l, --list-shells print list of shells and exit\n"), fp);
85 fputs(USAGE_SEPARATOR, fp);
86 fputs(_(" -u, --help display this help and exit\n"), fp);
87 fputs(_(" -v, --version output version information and exit\n"), fp);
88 fprintf(fp, USAGE_MAN_TAIL("chsh(1)"));
6e1eda6f 89 exit(EXIT_SUCCESS);
d39d4bf6 90}
6dbe3af9 91
5eef6129 92/*
d1818b9e 93 * free_shells () -- free shells allocations.
5eef6129 94 */
d1818b9e
SK
95static void free_shells(void)
96{
5f032ae4
KZ
97 if (global_shells) {
98 char **s;
d1818b9e 99
5f032ae4
KZ
100 for (s = global_shells; *s; s++)
101 free(*s);
102 free(global_shells);
103 }
d1818b9e
SK
104}
105
106/*
5f032ae4 107 * init_shells () -- reads /etc/shells into global_shells
d1818b9e 108 */
5f032ae4 109static int init_shells(void)
0a065b7a 110{
5eef6129 111 FILE *fp;
1cb122d5 112 char *buf = NULL;
d1818b9e 113 size_t sz = 0, shellsz = 8, n = 0;
84705c8b 114 ssize_t len;
0a065b7a 115
5f032ae4
KZ
116 if (global_shells)
117 return 0; /* already initialized */
118
5eef6129 119 fp = fopen(_PATH_SHELLS, "r");
d1818b9e
SK
120 if (!fp)
121 return 1;
5f032ae4
KZ
122
123 global_shells = xmalloc(sizeof(char *) * shellsz);
124
84705c8b
KZ
125 while ((len = getline(&buf, &sz, fp)) != -1) {
126 /* ignore comments and blank lines */
127 if (*buf == '#' || len < 2)
1cb122d5 128 continue;
5eef6129
SK
129 /* strip the ending newline */
130 if (buf[len - 1] == '\n')
131 buf[len - 1] = 0;
5f032ae4 132 global_shells[n++] = buf;
d1818b9e
SK
133 if (shellsz < n) {
134 shellsz *= 2;
5f032ae4 135 global_shells = xrealloc(global_shells, sizeof(char *) * shellsz);
d1818b9e
SK
136 }
137 buf = NULL;
138 }
5f032ae4 139
d1818b9e 140 free(buf);
5f032ae4
KZ
141 global_shells[n] = NULL;
142
d1818b9e
SK
143 fclose(fp);
144 atexit(free_shells);
5f032ae4 145
d1818b9e
SK
146 return 0;
147}
148
149/*
54373fb9 150 * is_known_shell() -- if the given shell appears in /etc/shells,
d1818b9e 151 * return true. if not, return false.
d1818b9e 152 */
5f032ae4 153static int is_known_shell(const char *shell_name)
d1818b9e
SK
154{
155 char **s;
d1818b9e 156
5f032ae4 157 if (!global_shells || !shell_name)
54373fb9
KZ
158 return 0;
159
5f032ae4 160 for (s = global_shells; *s; s++) {
54373fb9
KZ
161 if (strcmp(shell_name, *s) == 0)
162 return 1;
0a065b7a 163 }
54373fb9
KZ
164 return 0;
165}
166
167/*
168 * print_shells () -- /etc/shells is outputted to stdout.
169 */
5f032ae4 170static void print_shells(void)
54373fb9
KZ
171{
172 char **s;
173
5f032ae4 174 if (!global_shells)
54373fb9
KZ
175 return;
176
5f032ae4 177 for (s = global_shells; *s; s++)
54373fb9 178 printf("%s\n", *s);
6dbe3af9
KZ
179}
180
d1818b9e
SK
181#ifdef HAVE_LIBREADLINE
182static char *shell_name_generator(const char *text, int state)
183{
184 static size_t len, idx;
185 char *s;
186
5f032ae4
KZ
187 if (!global_shells)
188 return NULL;
189
d1818b9e
SK
190 if (!state) {
191 idx = 0;
192 len = strlen(text);
193 }
194 while ((s = global_shells[idx++])) {
195 if (strncmp(s, text, len) == 0)
196 return xstrdup(s);
197 }
198 return NULL;
199}
200
201static char **shell_name_completion(const char *text,
202 int start __attribute__((__unused__)),
203 int end __attribute__((__unused__)))
204{
205 rl_attempted_completion_over = 1;
206 return rl_completion_matches(text, shell_name_generator);
207}
208#endif
209
6dbe3af9
KZ
210/*
211 * parse_argv () --
212 * parse the command line arguments, and fill in "pinfo" with any
213 * information from the command line.
214 */
5f032ae4 215static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
0a065b7a 216{
4a98629b 217 static const struct option long_options[] = {
87918040
SK
218 {"shell", required_argument, NULL, 's'},
219 {"list-shells", no_argument, NULL, 'l'},
220 {"help", no_argument, NULL, 'h'},
221 {"version", no_argument, NULL, 'v'},
222 {NULL, 0, NULL, 0},
0a065b7a 223 };
68b24d53 224 int c;
0a065b7a 225
631a1954 226 while ((c = getopt_long(argc, argv, "s:lhuv", long_options, NULL)) != -1) {
0a065b7a 227 switch (c) {
0a065b7a
SK
228 case 'v':
229 printf(UTIL_LINUX_VERSION);
230 exit(EXIT_SUCCESS);
631a1954
AH
231 case 'u': /* deprecated */
232 case 'h':
6e1eda6f 233 usage();
0a065b7a 234 case 'l':
5f032ae4
KZ
235 init_shells();
236 print_shells();
0a065b7a
SK
237 exit(EXIT_SUCCESS);
238 case 's':
0a065b7a
SK
239 pinfo->shell = optarg;
240 break;
241 default:
677ec86c 242 errtryhelp(EXIT_FAILURE);
0a065b7a
SK
243 }
244 }
245 /* done parsing arguments. check for a username. */
246 if (optind < argc) {
6e1eda6f
RM
247 if (optind + 1 < argc) {
248 errx(EXIT_FAILURE, _("cannot handle multiple usernames"));
249 }
0a065b7a 250 pinfo->username = argv[optind];
6dbe3af9 251 }
6dbe3af9
KZ
252}
253
6dbe3af9 254/*
e323df25
SK
255 * ask_new_shell () --
256 * ask the user for a shell and return it.
6dbe3af9 257 */
e323df25 258static char *ask_new_shell(char *question, char *oldshell)
0a065b7a
SK
259{
260 int len;
e323df25 261 char *ans = NULL;
d1818b9e
SK
262#ifdef HAVE_LIBREADLINE
263 rl_attempted_completion_function = shell_name_completion;
264#else
e323df25 265 size_t dummy = 0;
e41ae450 266#endif
e323df25
SK
267 if (!oldshell)
268 oldshell = "";
d1818b9e 269 printf("%s [%s]\n", question, oldshell);
e41ae450 270#ifdef HAVE_LIBREADLINE
d1818b9e 271 if ((ans = readline("> ")) == NULL)
e41ae450
SK
272#else
273 if (getline(&ans, &dummy, stdin) < 0)
274#endif
0a065b7a 275 return NULL;
e323df25
SK
276 /* remove the newline at the end of ans. */
277 ltrim_whitespace((unsigned char *) ans);
278 len = rtrim_whitespace((unsigned char *) ans);
279 if (len == 0)
280 return NULL;
281 return ans;
6dbe3af9
KZ
282}
283
284/*
285 * check_shell () -- if the shell is completely invalid, print
1df0219f 286 * an error and exit.
6dbe3af9 287 */
5f032ae4 288static void check_shell(const char *shell)
0a065b7a 289{
1df0219f
SK
290 if (*shell != '/')
291 errx(EXIT_FAILURE, _("shell must be a full path name"));
292 if (access(shell, F_OK) < 0)
293 errx(EXIT_FAILURE, _("\"%s\" does not exist"), shell);
294 if (access(shell, X_OK) < 0)
295 errx(EXIT_FAILURE, _("\"%s\" is not executable"), shell);
296 if (illegal_passwd_chars(shell))
297 errx(EXIT_FAILURE, _("%s: has illegal characters"), shell);
5f032ae4 298 if (!is_known_shell(shell)) {
1df0219f 299#ifdef ONLY_LISTED_SHELLS
0a065b7a 300 if (!getuid())
1df0219f
SK
301 warnx(_("Warning: \"%s\" is not listed in %s."), shell,
302 _PATH_SHELLS);
0a065b7a
SK
303 else
304 errx(EXIT_FAILURE,
c900336d
SK
305 _("\"%s\" is not listed in %s.\n"
306 "Use %s -l to see list."), shell, _PATH_SHELLS,
0a065b7a 307 program_invocation_short_name);
726f69e2 308#else
c900336d
SK
309 warnx(_("\"%s\" is not listed in %s.\n"
310 "Use %s -l to see list."), shell, _PATH_SHELLS,
1df0219f 311 program_invocation_short_name);
726f69e2 312#endif
1df0219f 313 }
6dbe3af9
KZ
314}
315
5eef6129 316int main(int argc, char **argv)
0a065b7a 317{
561c4858 318 char *oldshell;
f6497923 319 int nullshell = 0;
561c4858 320 const uid_t uid = getuid();
87918040 321 struct sinfo info = { NULL };
5eef6129 322 struct passwd *pw;
0a065b7a 323
5eef6129
SK
324 sanitize_env();
325 setlocale(LC_ALL, "");
326 bindtextdomain(PACKAGE, LOCALEDIR);
327 textdomain(PACKAGE);
328 atexit(close_stdout);
329
5f032ae4 330 parse_argv(argc, argv, &info);
5eef6129
SK
331 if (!info.username) {
332 pw = getpwuid(uid);
333 if (!pw)
334 errx(EXIT_FAILURE, _("you (user %d) don't exist."),
335 uid);
336 } else {
337 pw = getpwnam(info.username);
338 if (!pw)
339 errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
340 info.username);
0a065b7a 341 }
5eef6129
SK
342
343#ifndef HAVE_LIBUSER
344 if (!(is_local(pw->pw_name)))
345 errx(EXIT_FAILURE, _("can only change local entries"));
346#endif
347
348#ifdef HAVE_LIBSELINUX
349 if (is_selinux_enabled() > 0) {
350 if (uid == 0) {
dd5ef107
KZ
351 access_vector_t av = get_access_vector("passwd", "chsh");
352
353 if (selinux_check_passwd_access(av) != 0) {
5eef6129
SK
354 security_context_t user_context;
355 if (getprevcon(&user_context) < 0)
356 user_context =
357 (security_context_t) NULL;
358
359 errx(EXIT_FAILURE,
360 _("%s is not authorized to change the shell of %s"),
361 user_context ? : _("Unknown user context"),
362 pw->pw_name);
0a065b7a 363 }
5eef6129
SK
364 }
365 if (setupDefaultContext(_PATH_PASSWD) != 0)
366 errx(EXIT_FAILURE,
367 _("can't set default context for %s"), _PATH_PASSWD);
6dbe3af9 368 }
5eef6129
SK
369#endif
370
371 oldshell = pw->pw_shell;
f6497923 372 if (oldshell == NULL || *oldshell == '\0') {
5eef6129 373 oldshell = _PATH_BSHELL; /* default */
f6497923
SK
374 nullshell = 1;
375 }
5eef6129
SK
376
377 /* reality check */
378#ifdef HAVE_LIBUSER
379 /* If we're setuid and not really root, disallow the password change. */
380 if (geteuid() != getuid() && uid != pw->pw_uid) {
381#else
382 if (uid != 0 && uid != pw->pw_uid) {
383#endif
384 errno = EACCES;
385 err(EXIT_FAILURE,
386 _("running UID doesn't match UID of user we're "
387 "altering, shell change denied"));
388 }
5f032ae4
KZ
389 init_shells();
390 if (uid != 0 && !is_known_shell(oldshell)) {
5eef6129
SK
391 errno = EACCES;
392 err(EXIT_FAILURE, _("your shell is not in %s, "
393 "shell change denied"), _PATH_SHELLS);
394 }
395
5eef6129
SK
396 printf(_("Changing shell for %s.\n"), pw->pw_name);
397
398#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
561c4858 399 if (!auth_pam("chsh", uid, pw->pw_name)) {
5eef6129
SK
400 return EXIT_FAILURE;
401 }
402#endif
561c4858 403 if (!info.shell) {
e323df25 404 info.shell = ask_new_shell(_("New shell"), oldshell);
561c4858 405 if (!info.shell)
5eef6129
SK
406 return EXIT_SUCCESS;
407 }
408
5f032ae4 409 check_shell(info.shell);
5eef6129 410
f6497923 411 if (!nullshell && strcmp(oldshell, info.shell) == 0)
5eef6129
SK
412 errx(EXIT_SUCCESS, _("Shell not changed."));
413
414#ifdef HAVE_LIBUSER
415 if (set_value_libuser("chsh", pw->pw_name, uid,
561c4858 416 LU_LOGINSHELL, info.shell) < 0)
5eef6129
SK
417 errx(EXIT_FAILURE, _("Shell *NOT* changed. Try again later."));
418#else
561c4858 419 pw->pw_shell = info.shell;
bde91c85 420 if (setpwnam(pw, ".chsh") < 0)
5eef6129
SK
421 err(EXIT_FAILURE, _("setpwnam failed\n"
422 "Shell *NOT* changed. Try again later."));
423#endif
424
425 printf(_("Shell changed.\n"));
426 return EXIT_SUCCESS;
6dbe3af9 427}