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