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