]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/chsh.c
build-sys: release++ (v2.38.1)
[thirdparty/util-linux.git] / login-utils / chsh.c
1 /*
2 * chsh.c -- change your login shell
3 * (c) 1994 by salvatore valente <svalente@athena.mit.edu>
4 * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
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 *
10 * $Author: aebr $
11 * $Revision: 1.19 $
12 * $Date: 1998/06/11 22:30:14 $
13 *
14 * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security
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 *
21 * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
22 * - added Native Language Support
23 */
24
25 #include <ctype.h>
26 #include <errno.h>
27 #include <getopt.h>
28 #include <pwd.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <unistd.h>
34
35 #include "c.h"
36 #include "env.h"
37 #include "closestream.h"
38 #include "islocal.h"
39 #include "nls.h"
40 #include "pathnames.h"
41 #include "pwdutils.h"
42 #include "setpwnam.h"
43 #include "strutils.h"
44 #include "xalloc.h"
45
46 #include "ch-common.h"
47
48 #ifdef HAVE_LIBSELINUX
49 # include <selinux/selinux.h>
50 # include "selinux-utils.h"
51 #endif
52
53 #ifdef HAVE_LIBUSER
54 # include <libuser/user.h>
55 # include "libuser.h"
56 #elif CHFN_CHSH_PASSWORD
57 # include "auth.h"
58 #endif
59
60 struct sinfo {
61 char *username;
62 char *shell;
63 };
64
65 static void __attribute__((__noreturn__)) usage(void)
66 {
67 FILE *fp = stdout;
68 fputs(USAGE_HEADER, fp);
69 fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
70
71 fputs(USAGE_SEPARATOR, fp);
72 fputs(_("Change your login shell.\n"), fp);
73
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 printf( " -u, --help %s\n", USAGE_OPTSTR_HELP);
79 printf( " -v, --version %s\n", USAGE_OPTSTR_VERSION);
80 printf(USAGE_MAN_TAIL("chsh(1)"));
81 exit(EXIT_SUCCESS);
82 }
83
84 /*
85 * is_known_shell() -- if the given shell appears in /etc/shells,
86 * return true. if not, return false.
87 */
88 static int is_known_shell(const char *shell_name)
89 {
90 char *s, ret = 0;
91
92 if (!shell_name)
93 return 0;
94
95 setusershell();
96 while ((s = getusershell())) {
97 if (strcmp(shell_name, s) == 0) {
98 ret = 1;
99 break;
100 }
101 }
102 endusershell();
103 return ret;
104 }
105
106 /*
107 * print_shells () -- /etc/shells is outputted to stdout.
108 */
109 static void print_shells(void)
110 {
111 char *s;
112
113 while ((s = getusershell()))
114 printf("%s\n", s);
115 endusershell();
116 }
117
118 /*
119 * parse_argv () --
120 * parse the command line arguments, and fill in "pinfo" with any
121 * information from the command line.
122 */
123 static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
124 {
125 static const struct option long_options[] = {
126 {"shell", required_argument, NULL, 's'},
127 {"list-shells", no_argument, NULL, 'l'},
128 {"help", no_argument, NULL, 'h'},
129 {"version", no_argument, NULL, 'v'},
130 {NULL, 0, NULL, 0},
131 };
132 int c;
133
134 while ((c = getopt_long(argc, argv, "s:lhuv", long_options, NULL)) != -1) {
135 switch (c) {
136 case 'v':
137 print_version(EXIT_SUCCESS);
138 case 'u': /* deprecated */
139 case 'h':
140 usage();
141 case 'l':
142 print_shells();
143 exit(EXIT_SUCCESS);
144 case 's':
145 pinfo->shell = optarg;
146 break;
147 default:
148 errtryhelp(EXIT_FAILURE);
149 }
150 }
151 /* done parsing arguments. check for a username. */
152 if (optind < argc) {
153 if (optind + 1 < argc) {
154 errx(EXIT_FAILURE, _("cannot handle multiple usernames"));
155 }
156 pinfo->username = argv[optind];
157 }
158 }
159
160 /*
161 * ask_new_shell () --
162 * ask the user for a shell and return it.
163 */
164 static char *ask_new_shell(char *question, char *oldshell)
165 {
166 int len;
167 char *ans = NULL;
168 size_t dummy = 0;
169
170 if (!oldshell)
171 oldshell = "";
172 printf("%s [%s]:", question, oldshell);
173
174 putchar(' ');
175 fflush(stdout);
176
177 if (getline(&ans, &dummy, stdin) < 0)
178 return NULL;
179
180 /* remove the newline at the end of ans. */
181 ltrim_whitespace((unsigned char *) ans);
182 len = rtrim_whitespace((unsigned char *) ans);
183 if (len == 0)
184 return NULL;
185 return ans;
186 }
187
188 /*
189 * check_shell () -- if the shell is completely invalid, print
190 * an error and exit.
191 */
192 static void check_shell(const char *shell)
193 {
194 if (*shell != '/')
195 errx(EXIT_FAILURE, _("shell must be a full path name"));
196 if (access(shell, F_OK) < 0)
197 errx(EXIT_FAILURE, _("\"%s\" does not exist"), shell);
198 if (access(shell, X_OK) < 0)
199 errx(EXIT_FAILURE, _("\"%s\" is not executable"), shell);
200 if (illegal_passwd_chars(shell))
201 errx(EXIT_FAILURE, _("%s: has illegal characters"), shell);
202 if (!is_known_shell(shell)) {
203 #ifdef ONLY_LISTED_SHELLS
204 if (!getuid())
205 warnx(_("Warning: \"%s\" is not listed in %s."), shell,
206 _PATH_SHELLS);
207 else
208 errx(EXIT_FAILURE,
209 _("\"%s\" is not listed in %s.\n"
210 "Use %s -l to see list."), shell, _PATH_SHELLS,
211 program_invocation_short_name);
212 #else
213 warnx(_("\"%s\" is not listed in %s.\n"
214 "Use %s -l to see list."), shell, _PATH_SHELLS,
215 program_invocation_short_name);
216 #endif
217 }
218 }
219
220 int main(int argc, char **argv)
221 {
222 char *oldshell, *pwbuf;
223 int nullshell = 0;
224 const uid_t uid = getuid();
225 struct sinfo info = { NULL };
226 struct passwd *pw;
227
228 sanitize_env();
229 setlocale(LC_ALL, "");
230 bindtextdomain(PACKAGE, LOCALEDIR);
231 textdomain(PACKAGE);
232 close_stdout_atexit();
233
234 parse_argv(argc, argv, &info);
235 if (!info.username) {
236 pw = xgetpwuid(uid, &pwbuf);
237 if (!pw)
238 errx(EXIT_FAILURE, _("you (user %d) don't exist."),
239 uid);
240 } else {
241 pw = xgetpwnam(info.username, &pwbuf);
242 if (!pw)
243 errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
244 info.username);
245 }
246
247 #ifndef HAVE_LIBUSER
248 if (!(is_local(pw->pw_name)))
249 errx(EXIT_FAILURE, _("can only change local entries"));
250 #endif
251
252 #ifdef HAVE_LIBSELINUX
253 if (is_selinux_enabled() > 0) {
254 char *user_cxt = NULL;
255
256 if (uid == 0 && !ul_selinux_has_access("passwd", "chsh", &user_cxt))
257 errx(EXIT_FAILURE,
258 _("%s is not authorized to change the shell of %s"),
259 user_cxt ? : _("Unknown user context"),
260 pw->pw_name);
261
262 if (ul_setfscreatecon_from_file(_PATH_PASSWD) != 0)
263 errx(EXIT_FAILURE,
264 _("can't set default context for %s"), _PATH_PASSWD);
265 }
266 #endif
267
268 oldshell = pw->pw_shell;
269 if (oldshell == NULL || *oldshell == '\0') {
270 oldshell = _PATH_BSHELL; /* default */
271 nullshell = 1;
272 }
273
274 /* reality check */
275 #ifdef HAVE_LIBUSER
276 /* If we're setuid and not really root, disallow the password change. */
277 if (geteuid() != getuid() && uid != pw->pw_uid) {
278 #else
279 if (uid != 0 && uid != pw->pw_uid) {
280 #endif
281 errno = EACCES;
282 err(EXIT_FAILURE,
283 _("running UID doesn't match UID of user we're "
284 "altering, shell change denied"));
285 }
286 if (uid != 0 && !is_known_shell(oldshell)) {
287 errno = EACCES;
288 err(EXIT_FAILURE, _("your shell is not in %s, "
289 "shell change denied"), _PATH_SHELLS);
290 }
291
292 printf(_("Changing shell for %s.\n"), pw->pw_name);
293
294 #if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
295 if (!auth_pam("chsh", uid, pw->pw_name)) {
296 return EXIT_FAILURE;
297 }
298 #endif
299 if (!info.shell) {
300 info.shell = ask_new_shell(_("New shell"), oldshell);
301 if (!info.shell)
302 return EXIT_SUCCESS;
303 }
304
305 check_shell(info.shell);
306
307 if (!nullshell && strcmp(oldshell, info.shell) == 0)
308 errx(EXIT_SUCCESS, _("Shell not changed."));
309
310 #ifdef HAVE_LIBUSER
311 if (set_value_libuser("chsh", pw->pw_name, uid,
312 LU_LOGINSHELL, info.shell) < 0)
313 errx(EXIT_FAILURE, _("Shell *NOT* changed. Try again later."));
314 #else
315 pw->pw_shell = info.shell;
316 if (setpwnam(pw, ".chsh") < 0)
317 err(EXIT_FAILURE, _("setpwnam failed\n"
318 "Shell *NOT* changed. Try again later."));
319 #endif
320
321 printf(_("Shell changed.\n"));
322 return EXIT_SUCCESS;
323 }