]> git.ipfire.org Git - thirdparty/util-linux.git/blame_incremental - login-utils/chsh.c
taskset: Accept 0 pid for current process
[thirdparty/util-linux.git] / login-utils / chsh.c
... / ...
CommitLineData
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#include "shells.h"
48
49#ifdef HAVE_LIBSELINUX
50# include <selinux/selinux.h>
51# include "selinux-utils.h"
52#endif
53
54#ifdef HAVE_LIBUSER
55# include <libuser/user.h>
56# include "libuser.h"
57#elif CHFN_CHSH_PASSWORD
58# include "auth.h"
59#endif
60
61struct sinfo {
62 char *username;
63 char *shell;
64};
65
66static void __attribute__((__noreturn__)) usage(void)
67{
68 FILE *fp = stdout;
69 fputs(USAGE_HEADER, fp);
70 fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
71
72 fputs(USAGE_SEPARATOR, fp);
73 fputs(_("Change your login shell.\n"), fp);
74
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);
78
79 fputs(USAGE_SEPARATOR, fp);
80 fprintf(fp, USAGE_HELP_OPTIONS(22));
81
82 fprintf(fp, USAGE_MAN_TAIL("chsh(1)"));
83 exit(EXIT_SUCCESS);
84}
85
86/*
87 * parse_argv () --
88 * parse the command line arguments, and fill in "pinfo" with any
89 * information from the command line.
90 */
91static void parse_argv(int argc, char **argv, struct sinfo *pinfo)
92{
93 static const struct option long_options[] = {
94 {"shell", required_argument, NULL, 's'},
95 {"list-shells", no_argument, NULL, 'l'},
96 {"help", no_argument, NULL, 'h'},
97 {"version", no_argument, NULL, 'V'},
98 {NULL, 0, NULL, 0},
99 };
100 int c;
101
102 while ((c = getopt_long(argc, argv, "s:lhuvV", long_options, NULL)) != -1) {
103 switch (c) {
104 case 'v': /* deprecated */
105 case 'V':
106 print_version(EXIT_SUCCESS);
107 case 'u': /* deprecated */
108 case 'h':
109 usage();
110 case 'l':
111 print_shells(stdout, "%s\n");
112 exit(EXIT_SUCCESS);
113 case 's':
114 pinfo->shell = optarg;
115 break;
116 default:
117 errtryhelp(EXIT_FAILURE);
118 }
119 }
120 /* done parsing arguments. check for a username. */
121 if (optind < argc) {
122 if (optind + 1 < argc) {
123 errx(EXIT_FAILURE, _("cannot handle multiple usernames"));
124 }
125 pinfo->username = argv[optind];
126 }
127}
128
129/*
130 * ask_new_shell () --
131 * ask the user for a shell and return it.
132 */
133static char *ask_new_shell(char *question, char *oldshell)
134{
135 int len;
136 char *ans = NULL;
137 size_t dummy = 0;
138
139 if (!oldshell)
140 oldshell = "";
141 printf("%s [%s]:", question, oldshell);
142
143 putchar(' ');
144 fflush(stdout);
145
146 if (getline(&ans, &dummy, stdin) < 0)
147 return NULL;
148
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;
155}
156
157/*
158 * check_shell () -- if the shell is completely invalid, print
159 * an error and exit.
160 */
161static void check_shell(const char *shell)
162{
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);
171 if (!is_known_shell(shell)) {
172#ifdef ONLY_LISTED_SHELLS
173 if (!getuid())
174 warnx(_("Warning: \"%s\" is not listed in %s."), shell,
175 _PATH_SHELLS);
176 else
177 errx(EXIT_FAILURE,
178 _("\"%s\" is not listed in %s.\n"
179 "Use %s -l to see list."), shell, _PATH_SHELLS,
180 program_invocation_short_name);
181#else
182 warnx(_("\"%s\" is not listed in %s.\n"
183 "Use %s -l to see list."), shell, _PATH_SHELLS,
184 program_invocation_short_name);
185#endif
186 }
187}
188
189int main(int argc, char **argv)
190{
191 char *oldshell, *pwbuf;
192 int nullshell = 0;
193 const uid_t uid = getuid();
194 struct sinfo info = { NULL };
195 struct passwd *pw;
196
197 sanitize_env();
198 setlocale(LC_ALL, "");
199 bindtextdomain(PACKAGE, LOCALEDIR);
200 textdomain(PACKAGE);
201 close_stdout_atexit();
202
203 parse_argv(argc, argv, &info);
204 if (!info.username) {
205 pw = xgetpwuid(uid, &pwbuf);
206 if (!pw)
207 errx(EXIT_FAILURE, _("you (user %d) don't exist."),
208 uid);
209 } else {
210 pw = xgetpwnam(info.username, &pwbuf);
211 if (!pw)
212 errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
213 info.username);
214 }
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) {
223 char *user_cxt = NULL;
224
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);
230
231 if (ul_setfscreatecon_from_file(_PATH_PASSWD) != 0)
232 errx(EXIT_FAILURE,
233 _("can't set default context for %s"), _PATH_PASSWD);
234 }
235#endif
236
237 oldshell = pw->pw_shell;
238 if (oldshell == NULL || *oldshell == '\0') {
239 oldshell = _PATH_BSHELL; /* default */
240 nullshell = 1;
241 }
242
243 /* reality check */
244#ifdef HAVE_LIBUSER
245 /* If we're setuid and not really root, disallow the password change. */
246 if (is_privileged_execution() && 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 }
255 if (uid != 0 && !is_known_shell(oldshell)) {
256 errno = EACCES;
257 err(EXIT_FAILURE, _("your shell is not in %s, "
258 "shell change denied"), _PATH_SHELLS);
259 }
260
261 printf(_("Changing shell for %s.\n"), pw->pw_name);
262
263#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
264 if (!auth_pam("chsh", uid, pw->pw_name)) {
265 return EXIT_FAILURE;
266 }
267#endif
268 if (!info.shell) {
269 info.shell = ask_new_shell(_("New shell"), oldshell);
270 if (!info.shell)
271 return EXIT_SUCCESS;
272 }
273
274 check_shell(info.shell);
275
276 if (!nullshell && strcmp(oldshell, info.shell) == 0)
277 errx(EXIT_SUCCESS, _("Shell not changed."));
278
279#ifdef HAVE_LIBUSER
280 if (set_value_libuser("chsh", pw->pw_name, uid,
281 LU_LOGINSHELL, info.shell) < 0)
282 errx(EXIT_FAILURE, _("Shell *NOT* changed. Try again later."));
283#else
284 pw->pw_shell = info.shell;
285 if (setpwnam(pw, ".chsh") < 0)
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;
292}