]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/chsh.c
cfdisk: improve Dump dialog
[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>
8187b555 29#include <stdbool.h>
0a065b7a
SK
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <sys/types.h>
34#include <unistd.h>
d39d4bf6
KZ
35
36#include "c.h"
0a065b7a 37#include "env.h"
439cdf1e 38#include "closestream.h"
66ee8158 39#include "islocal.h"
7eda085c 40#include "nls.h"
7ab59baf 41#include "pathnames.h"
0a065b7a 42#include "setpwnam.h"
d39d4bf6 43#include "xalloc.h"
fd6b7a7f 44
48d7b13a 45#ifdef HAVE_LIBSELINUX
0a065b7a
SK
46# include <selinux/selinux.h>
47# include <selinux/av_permissions.h>
48# include "selinux_utils.h"
d03dd608
KZ
49#endif
50
6adb1ef2
CM
51
52#ifdef HAVE_LIBUSER
53# include <libuser/user.h>
54# include "libuser.h"
d86918b6 55#elif CHFN_CHSH_PASSWORD
6adb1ef2
CM
56# include "auth.h"
57#endif
58
6dbe3af9 59struct sinfo {
0a065b7a
SK
60 char *username;
61 char *shell;
6dbe3af9
KZ
62};
63
0a065b7a
SK
64static void parse_argv(int argc, char **argv, struct sinfo *pinfo);
65static char *prompt(char *question, char *def_val);
66static int check_shell(char *shell);
67static int get_shell_list(char *shell);
66ee8158 68
abafd686 69static void __attribute__((__noreturn__)) usage (FILE *fp)
d39d4bf6 70{
0a065b7a 71 fputs(USAGE_HEADER, fp);
09af3db4 72 fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
0a065b7a
SK
73 fputs(USAGE_OPTIONS, fp);
74 fputs(_(" -s, --shell <shell> specify login shell\n"), fp);
75 fputs(_(" -l, --list-shells print list of shells and exit\n"), fp);
76 fputs(USAGE_SEPARATOR, fp);
77 fputs(_(" -u, --help display this help and exit\n"), fp);
78 fputs(_(" -v, --version output version information and exit\n"), fp);
79 fprintf(fp, USAGE_MAN_TAIL("chsh(1)"));
80 exit(fp == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
d39d4bf6 81}
6dbe3af9 82
0a065b7a
SK
83int main(int argc, char **argv)
84{
85 char *shell, *oldshell;
86 uid_t uid;
87 struct sinfo info;
88 struct passwd *pw;
89
90 sanitize_env();
91 setlocale(LC_ALL, "");
92 bindtextdomain(PACKAGE, LOCALEDIR);
93 textdomain(PACKAGE);
439cdf1e 94 atexit(close_stdout);
0a065b7a
SK
95
96 uid = getuid();
97 memset(&info, 0, sizeof(info));
98
99 parse_argv(argc, argv, &info);
100 pw = NULL;
101 if (!info.username) {
102 pw = getpwuid(uid);
103 if (!pw)
104 errx(EXIT_FAILURE, _("you (user %d) don't exist."),
105 uid);
106 } else {
107 pw = getpwnam(info.username);
108 if (!pw)
109 errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
110 info.username);
111 }
112
d86918b6 113#ifndef HAVE_LIBUSER
0a065b7a 114 if (!(is_local(pw->pw_name)))
09af3db4 115 errx(EXIT_FAILURE, _("can only change local entries"));
d86918b6 116#endif
fd6b7a7f 117
48d7b13a 118#ifdef HAVE_LIBSELINUX
0a065b7a
SK
119 if (is_selinux_enabled() > 0) {
120 if (uid == 0) {
121 if (checkAccess(pw->pw_name, PASSWD__CHSH) != 0) {
122 security_context_t user_context;
123 if (getprevcon(&user_context) < 0)
124 user_context =
125 (security_context_t) NULL;
126
127 errx(EXIT_FAILURE,
128 _("%s is not authorized to change the shell of %s"),
129 user_context ? : _("Unknown user context"),
130 pw->pw_name);
131 }
132 }
c900336d 133 if (setupDefaultContext(_PATH_PASSWD) != 0)
0a065b7a 134 errx(EXIT_FAILURE,
c900336d 135 _("can't set default context for %s"), _PATH_PASSWD);
d03dd608 136 }
d03dd608
KZ
137#endif
138
0a065b7a
SK
139 oldshell = pw->pw_shell;
140 if (oldshell == NULL || *oldshell == '\0')
141 oldshell = _PATH_BSHELL; /* default */
726f69e2 142
0a065b7a 143 /* reality check */
6adb1ef2
CM
144#ifdef HAVE_LIBUSER
145 /* If we're setuid and not really root, disallow the password change. */
146 if (geteuid() != getuid() && uid != pw->pw_uid) {
147#else
0a065b7a 148 if (uid != 0 && uid != pw->pw_uid) {
6adb1ef2 149#endif
0a065b7a
SK
150 errno = EACCES;
151 err(EXIT_FAILURE,
152 _("running UID doesn't match UID of user we're "
153 "altering, shell change denied"));
154 }
155 if (uid != 0 && !get_shell_list(oldshell)) {
156 errno = EACCES;
c900336d
SK
157 err(EXIT_FAILURE, _("your shell is not in %s, "
158 "shell change denied"), _PATH_SHELLS);
0a065b7a 159 }
7ab59baf 160
0a065b7a 161 shell = info.shell;
726f69e2 162
0a065b7a 163 printf(_("Changing shell for %s.\n"), pw->pw_name);
726f69e2 164
d86918b6 165#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
d91ad6ab
CM
166 if(!auth_pam("chsh", uid, pw->pw_name)) {
167 return EXIT_FAILURE;
0a065b7a 168 }
6adb1ef2 169#endif
0a065b7a
SK
170 if (!shell) {
171 shell = prompt(_("New shell"), oldshell);
172 if (!shell)
173 return EXIT_SUCCESS;
174 }
175
176 if (check_shell(shell) < 0)
177 return EXIT_FAILURE;
178
c6f23b3b 179 if (strcmp(oldshell, shell) == 0)
0a065b7a 180 errx(EXIT_SUCCESS, _("Shell not changed."));
6adb1ef2
CM
181
182#ifdef HAVE_LIBUSER
d86918b6
KZ
183 if (set_value_libuser("chsh", pw->pw_name, uid,
184 LU_LOGINSHELL, shell) < 0)
185 errx(EXIT_FAILURE, _("Shell *NOT* changed. Try again later."));
6adb1ef2 186#else
0a065b7a 187 pw->pw_shell = shell;
36f9133d
KZ
188 if (setpwnam(pw) < 0)
189 err(EXIT_FAILURE, _("setpwnam failed\n"
190 "Shell *NOT* changed. Try again later."));
6adb1ef2 191#endif
36f9133d 192
0a065b7a
SK
193 printf(_("Shell changed.\n"));
194 return EXIT_SUCCESS;
6dbe3af9
KZ
195}
196
197/*
198 * parse_argv () --
199 * parse the command line arguments, and fill in "pinfo" with any
200 * information from the command line.
201 */
0a065b7a
SK
202static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
203{
204 int index, c;
205
206 static struct option long_options[] = {
207 {"shell", required_argument, 0, 's'},
208 {"list-shells", no_argument, 0, 'l'},
209 {"help", no_argument, 0, 'u'},
210 {"version", no_argument, 0, 'v'},
211 {NULL, no_argument, 0, '0'},
212 };
213
214 optind = c = 0;
215 while (c != EOF) {
216 c = getopt_long(argc, argv, "s:luv", long_options, &index);
217 switch (c) {
218 case -1:
219 break;
220 case 'v':
221 printf(UTIL_LINUX_VERSION);
222 exit(EXIT_SUCCESS);
223 case 'u':
224 usage(stdout);
225 case 'l':
226 get_shell_list(NULL);
227 exit(EXIT_SUCCESS);
228 case 's':
229 if (!optarg)
230 usage(stderr);
231 pinfo->shell = optarg;
232 break;
233 default:
234 usage(stderr);
235 }
236 }
237 /* done parsing arguments. check for a username. */
238 if (optind < argc) {
239 if (optind + 1 < argc)
240 usage(stderr);
241 pinfo->username = argv[optind];
6dbe3af9 242 }
6dbe3af9
KZ
243}
244
6dbe3af9
KZ
245/*
246 * prompt () --
247 * ask the user for a given field and return it.
248 */
0a065b7a
SK
249static char *prompt(char *question, char *def_val)
250{
251 int len;
252 char *ans, *cp;
253 char buf[BUFSIZ];
254
255 if (!def_val)
256 def_val = "";
257 printf("%s [%s]: ", question, def_val);
258 *buf = 0;
259 if (fgets(buf, sizeof(buf), stdin) == NULL)
260 errx(EXIT_FAILURE, _("Aborted."));
261 /* remove the newline at the end of buf. */
262 ans = buf;
263 while (isspace(*ans))
264 ans++;
265 len = strlen(ans);
266 while (len > 0 && isspace(ans[len - 1]))
267 len--;
268 if (len <= 0)
269 return NULL;
270 ans[len] = 0;
271 cp = (char *)xmalloc(len + 1);
272 strcpy(cp, ans);
273 return cp;
6dbe3af9
KZ
274}
275
276/*
277 * check_shell () -- if the shell is completely invalid, print
278 * an error and return (-1).
279 * if the shell is a bad idea, print a warning.
280 */
0a065b7a
SK
281static int check_shell(char *shell)
282{
283 unsigned int i, c;
284
285 if (!shell)
286 return -1;
287
288 if (*shell != '/') {
289 warnx(_("shell must be a full path name"));
290 return -1;
291 }
292 if (access(shell, F_OK) < 0) {
293 warnx(_("\"%s\" does not exist"), shell);
294 return -1;
6dbe3af9 295 }
0a065b7a
SK
296 if (access(shell, X_OK) < 0) {
297 printf(_("\"%s\" is not executable"), shell);
298 return -1;
299 }
300 /* keep /etc/passwd clean. */
301 for (i = 0; i < strlen(shell); i++) {
302 c = shell[i];
303 if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
304 warnx(_("'%c' is not allowed"), c);
305 return -1;
306 }
307 if (iscntrl(c)) {
308 warnx(_("control characters are not allowed"));
309 return -1;
310 }
6dbe3af9 311 }
48d7b13a 312#ifdef ONLY_LISTED_SHELLS
0a065b7a
SK
313 if (!get_shell_list(shell)) {
314 if (!getuid())
315 warnx(_
c900336d
SK
316 ("Warning: \"%s\" is not listed in %s."),
317 shell, _PATH_SHELLS);
0a065b7a
SK
318 else
319 errx(EXIT_FAILURE,
c900336d
SK
320 _("\"%s\" is not listed in %s.\n"
321 "Use %s -l to see list."), shell, _PATH_SHELLS,
0a065b7a
SK
322 program_invocation_short_name);
323 }
726f69e2 324#else
0a065b7a 325 if (!get_shell_list(shell)) {
c900336d
SK
326 warnx(_("\"%s\" is not listed in %s.\n"
327 "Use %s -l to see list."), shell, _PATH_SHELLS,
0a065b7a
SK
328 program_invocation_short_name);
329 }
726f69e2 330#endif
0a065b7a 331 return 0;
6dbe3af9
KZ
332}
333
334/*
335 * get_shell_list () -- if the given shell appears in /etc/shells,
336 * return true. if not, return false.
337 * if the given shell is NULL, /etc/shells is outputted to stdout.
338 */
0a065b7a
SK
339static int get_shell_list(char *shell_name)
340{
341 FILE *fp;
342 int found;
343 int len;
344 char buf[PATH_MAX];
345
346 found = false;
c900336d 347 fp = fopen(_PATH_SHELLS, "r");
0a065b7a
SK
348 if (!fp) {
349 if (!shell_name)
350 warnx(_("No known shells."));
351 return true;
352 }
353 while (fgets(buf, sizeof(buf), fp) != NULL) {
354 /* ignore comments */
355 if (*buf == '#')
356 continue;
357 len = strlen(buf);
358 /* strip the ending newline */
359 if (buf[len - 1] == '\n')
360 buf[len - 1] = 0;
361 /* ignore lines that are too damn long */
362 else
363 continue;
364 /* check or output the shell */
365 if (shell_name) {
366 if (!strcmp(shell_name, buf)) {
367 found = true;
368 break;
369 }
370 } else
371 printf("%s\n", buf);
6dbe3af9 372 }
0a065b7a
SK
373 fclose(fp);
374 return found;
6dbe3af9 375}