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