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