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