]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/chsh.c
Merge branch 'PR/libmount-exec-errors' of github.com:karelzak/util-linux-work
[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"
83db544d 41#include "pwdutils.h"
0a065b7a 42#include "setpwnam.h"
e323df25 43#include "strutils.h"
d39d4bf6 44#include "xalloc.h"
fd6b7a7f 45
144ae70e 46#include "ch-common.h"
825feef8 47#include "shells.h"
144ae70e 48
48d7b13a 49#ifdef HAVE_LIBSELINUX
0a065b7a 50# include <selinux/selinux.h>
68f4aa2a 51# include "selinux-utils.h"
d03dd608
KZ
52#endif
53
6adb1ef2
CM
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
6e1eda6f 66static void __attribute__((__noreturn__)) usage(void)
d39d4bf6 67{
6e1eda6f 68 FILE *fp = stdout;
0a065b7a 69 fputs(USAGE_HEADER, fp);
09af3db4 70 fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
451dbcfa
BS
71
72 fputs(USAGE_SEPARATOR, fp);
73 fputs(_("Change your login shell.\n"), fp);
74
0a065b7a
SK
75 fputs(USAGE_OPTIONS, fp);
76 fputs(_(" -s, --shell <shell> specify login shell\n"), fp);
77 fputs(_(" -l, --list-shells print list of shells and exit\n"), fp);
13e58aa7 78
0a065b7a 79 fputs(USAGE_SEPARATOR, fp);
bad4c729 80 fprintf(fp, USAGE_HELP_OPTIONS(22));
13e58aa7 81
bad4c729 82 fprintf(fp, USAGE_MAN_TAIL("chsh(1)"));
6e1eda6f 83 exit(EXIT_SUCCESS);
d39d4bf6 84}
6dbe3af9 85
6dbe3af9
KZ
86/*
87 * parse_argv () --
88 * parse the command line arguments, and fill in "pinfo" with any
89 * information from the command line.
90 */
5f032ae4 91static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
0a065b7a 92{
4a98629b 93 static const struct option long_options[] = {
87918040
SK
94 {"shell", required_argument, NULL, 's'},
95 {"list-shells", no_argument, NULL, 'l'},
96 {"help", no_argument, NULL, 'h'},
13e58aa7 97 {"version", no_argument, NULL, 'V'},
87918040 98 {NULL, 0, NULL, 0},
0a065b7a 99 };
68b24d53 100 int c;
0a065b7a 101
13e58aa7 102 while ((c = getopt_long(argc, argv, "s:lhuvV", long_options, NULL)) != -1) {
0a065b7a 103 switch (c) {
13e58aa7
KZ
104 case 'v': /* deprecated */
105 case 'V':
2c308875 106 print_version(EXIT_SUCCESS);
631a1954
AH
107 case 'u': /* deprecated */
108 case 'h':
6e1eda6f 109 usage();
0a065b7a 110 case 'l':
b8ecc2d3 111 print_shells(stdout, "%s\n");
0a065b7a
SK
112 exit(EXIT_SUCCESS);
113 case 's':
0a065b7a
SK
114 pinfo->shell = optarg;
115 break;
116 default:
677ec86c 117 errtryhelp(EXIT_FAILURE);
0a065b7a
SK
118 }
119 }
120 /* done parsing arguments. check for a username. */
121 if (optind < argc) {
6e1eda6f
RM
122 if (optind + 1 < argc) {
123 errx(EXIT_FAILURE, _("cannot handle multiple usernames"));
124 }
0a065b7a 125 pinfo->username = argv[optind];
6dbe3af9 126 }
6dbe3af9
KZ
127}
128
6dbe3af9 129/*
e323df25
SK
130 * ask_new_shell () --
131 * ask the user for a shell and return it.
6dbe3af9 132 */
e323df25 133static char *ask_new_shell(char *question, char *oldshell)
0a065b7a
SK
134{
135 int len;
e323df25
SK
136 char *ans = NULL;
137 size_t dummy = 0;
faa5a3a8 138
e323df25
SK
139 if (!oldshell)
140 oldshell = "";
49848aa5 141 printf("%s [%s]:", question, oldshell);
faa5a3a8 142
49848aa5 143 putchar(' ');
0a08200b 144 fflush(stdout);
faa5a3a8 145
e41ae450 146 if (getline(&ans, &dummy, stdin) < 0)
0a065b7a 147 return NULL;
faa5a3a8 148
e323df25
SK
149 /* remove the newline at the end of ans. */
150 ltrim_whitespace((unsigned char *) ans);
151 len = rtrim_whitespace((unsigned char *) ans);
152 if (len == 0)
153 return NULL;
154 return ans;
6dbe3af9
KZ
155}
156
157/*
158 * check_shell () -- if the shell is completely invalid, print
1df0219f 159 * an error and exit.
6dbe3af9 160 */
5f032ae4 161static void check_shell(const char *shell)
0a065b7a 162{
1df0219f
SK
163 if (*shell != '/')
164 errx(EXIT_FAILURE, _("shell must be a full path name"));
165 if (access(shell, F_OK) < 0)
166 errx(EXIT_FAILURE, _("\"%s\" does not exist"), shell);
167 if (access(shell, X_OK) < 0)
168 errx(EXIT_FAILURE, _("\"%s\" is not executable"), shell);
169 if (illegal_passwd_chars(shell))
170 errx(EXIT_FAILURE, _("%s: has illegal characters"), shell);
5f032ae4 171 if (!is_known_shell(shell)) {
1df0219f 172#ifdef ONLY_LISTED_SHELLS
0a065b7a 173 if (!getuid())
1df0219f
SK
174 warnx(_("Warning: \"%s\" is not listed in %s."), shell,
175 _PATH_SHELLS);
0a065b7a
SK
176 else
177 errx(EXIT_FAILURE,
c900336d
SK
178 _("\"%s\" is not listed in %s.\n"
179 "Use %s -l to see list."), shell, _PATH_SHELLS,
0a065b7a 180 program_invocation_short_name);
726f69e2 181#else
c900336d
SK
182 warnx(_("\"%s\" is not listed in %s.\n"
183 "Use %s -l to see list."), shell, _PATH_SHELLS,
1df0219f 184 program_invocation_short_name);
726f69e2 185#endif
1df0219f 186 }
6dbe3af9
KZ
187}
188
5eef6129 189int main(int argc, char **argv)
0a065b7a 190{
83db544d 191 char *oldshell, *pwbuf;
f6497923 192 int nullshell = 0;
561c4858 193 const uid_t uid = getuid();
87918040 194 struct sinfo info = { NULL };
5eef6129 195 struct passwd *pw;
0a065b7a 196
5eef6129
SK
197 sanitize_env();
198 setlocale(LC_ALL, "");
199 bindtextdomain(PACKAGE, LOCALEDIR);
200 textdomain(PACKAGE);
2c308875 201 close_stdout_atexit();
5eef6129 202
5f032ae4 203 parse_argv(argc, argv, &info);
5eef6129 204 if (!info.username) {
83db544d 205 pw = xgetpwuid(uid, &pwbuf);
5eef6129
SK
206 if (!pw)
207 errx(EXIT_FAILURE, _("you (user %d) don't exist."),
208 uid);
209 } else {
83db544d 210 pw = xgetpwnam(info.username, &pwbuf);
5eef6129
SK
211 if (!pw)
212 errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
213 info.username);
0a065b7a 214 }
5eef6129
SK
215
216#ifndef HAVE_LIBUSER
217 if (!(is_local(pw->pw_name)))
218 errx(EXIT_FAILURE, _("can only change local entries"));
219#endif
220
221#ifdef HAVE_LIBSELINUX
222 if (is_selinux_enabled() > 0) {
e1de70b3 223 char *user_cxt = NULL;
dd5ef107 224
e1de70b3
KZ
225 if (uid == 0 && !ul_selinux_has_access("passwd", "chsh", &user_cxt))
226 errx(EXIT_FAILURE,
227 _("%s is not authorized to change the shell of %s"),
228 user_cxt ? : _("Unknown user context"),
229 pw->pw_name);
5eef6129 230
b5debf71 231 if (ul_setfscreatecon_from_file(_PATH_PASSWD) != 0)
5eef6129
SK
232 errx(EXIT_FAILURE,
233 _("can't set default context for %s"), _PATH_PASSWD);
6dbe3af9 234 }
5eef6129
SK
235#endif
236
237 oldshell = pw->pw_shell;
f6497923 238 if (oldshell == NULL || *oldshell == '\0') {
5eef6129 239 oldshell = _PATH_BSHELL; /* default */
f6497923
SK
240 nullshell = 1;
241 }
5eef6129
SK
242
243 /* reality check */
244#ifdef HAVE_LIBUSER
245 /* If we're setuid and not really root, disallow the password change. */
246 if (geteuid() != getuid() && uid != pw->pw_uid) {
247#else
248 if (uid != 0 && uid != pw->pw_uid) {
249#endif
250 errno = EACCES;
251 err(EXIT_FAILURE,
252 _("running UID doesn't match UID of user we're "
253 "altering, shell change denied"));
254 }
5f032ae4 255 if (uid != 0 && !is_known_shell(oldshell)) {
5eef6129
SK
256 errno = EACCES;
257 err(EXIT_FAILURE, _("your shell is not in %s, "
258 "shell change denied"), _PATH_SHELLS);
259 }
260
5eef6129
SK
261 printf(_("Changing shell for %s.\n"), pw->pw_name);
262
263#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
561c4858 264 if (!auth_pam("chsh", uid, pw->pw_name)) {
5eef6129
SK
265 return EXIT_FAILURE;
266 }
267#endif
561c4858 268 if (!info.shell) {
e323df25 269 info.shell = ask_new_shell(_("New shell"), oldshell);
561c4858 270 if (!info.shell)
5eef6129
SK
271 return EXIT_SUCCESS;
272 }
273
5f032ae4 274 check_shell(info.shell);
5eef6129 275
f6497923 276 if (!nullshell && strcmp(oldshell, info.shell) == 0)
5eef6129
SK
277 errx(EXIT_SUCCESS, _("Shell not changed."));
278
279#ifdef HAVE_LIBUSER
280 if (set_value_libuser("chsh", pw->pw_name, uid,
561c4858 281 LU_LOGINSHELL, info.shell) < 0)
5eef6129
SK
282 errx(EXIT_FAILURE, _("Shell *NOT* changed. Try again later."));
283#else
561c4858 284 pw->pw_shell = info.shell;
bde91c85 285 if (setpwnam(pw, ".chsh") < 0)
5eef6129
SK
286 err(EXIT_FAILURE, _("setpwnam failed\n"
287 "Shell *NOT* changed. Try again later."));
288#endif
289
290 printf(_("Shell changed.\n"));
291 return EXIT_SUCCESS;
6dbe3af9 292}