]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/chsh.c
Imported from util-linux-2.10s tarball.
[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 *
5 * this program is free software. you can redistribute it and
6 * modify it under the terms of the gnu general public license.
7 * there is no warranty.
8 *
9 * $Author: aebr $
10 * $Revision: 1.19 $
11 * $Date: 1998/06/11 22:30:14 $
12 *
13 * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security
14 * patches from Zefram <A.Main@dcs.warwick.ac.uk>
15 *
16 * Updated Mon Jul 1 18:46:22 1996 by janl@math.uio.no with security
17 * suggestion from Zefram. Disallowing users with shells not in /etc/shells
18 * from changing their shell.
19 *
20 * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@misiek.eu.org>
21 * - added Native Language Support
22 *
23 *
24 */
25
26 #if 0
27 #define _POSIX_SOURCE 1
28 #endif
29
30 #include <sys/types.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <pwd.h>
36 #include <errno.h>
37 #include <ctype.h>
38 #include <getopt.h>
39 #include "my_crypt.h"
40 #include "islocal.h"
41 #include "setpwnam.h"
42 #include "nls.h"
43 #include "env.h"
44
45 #if REQUIRE_PASSWORD && USE_PAM
46 #include <security/pam_appl.h>
47 #include <security/pam_misc.h>
48 #endif
49
50 typedef unsigned char boolean;
51 #define false 0
52 #define true 1
53
54 /* Only root is allowed to assign a luser a non-listed shell, by default */
55 #define ONLY_LISTED_SHELLS 1
56
57
58 static char *whoami;
59
60 static char buf[FILENAME_MAX];
61
62 struct sinfo {
63 char *username;
64 char *shell;
65 };
66
67 static void parse_argv (int argc, char *argv[], struct sinfo *pinfo);
68 static void usage (FILE *fp);
69 static char *prompt (char *question, char *def_val);
70 static int check_shell (char *shell);
71 static boolean get_shell_list (char *shell);
72 static void *xmalloc (int bytes);
73
74 #define memzero(ptr, size) memset((char *) ptr, 0, size)
75
76 int
77 main (int argc, char *argv[]) {
78 char *cp, *shell, *oldshell;
79 uid_t uid;
80 struct sinfo info;
81 struct passwd *pw;
82 #if REQUIRE_PASSWORD && USE_PAM
83 pam_handle_t *pamh = NULL;
84 int retcode;
85 struct pam_conv conv = { misc_conv, NULL };
86 #endif
87
88 sanitize_env();
89 setlocale(LC_ALL, "");
90 bindtextdomain(PACKAGE, LOCALEDIR);
91 textdomain(PACKAGE);
92
93 /* whoami is the program name for error messages */
94 whoami = argv[0];
95 if (! whoami) whoami = "chsh";
96 for (cp = whoami; *cp; cp++)
97 if (*cp == '/') whoami = cp + 1;
98
99 uid = getuid ();
100 memzero (&info, sizeof (info));
101
102 parse_argv (argc, argv, &info);
103 pw = NULL;
104 if (! info.username) {
105 pw = getpwuid (uid);
106 if (! pw) {
107 fprintf (stderr, _("%s: you (user %d) don't exist.\n"), whoami, uid);
108 return (-1); }
109 }
110 else {
111 pw = getpwnam (info.username);
112 if (! pw) {
113 cp = info.username;
114 fprintf (stderr, _("%s: user \"%s\" does not exist.\n"), whoami, cp);
115 return (-1); }
116 }
117
118 if (!(is_local(pw->pw_name))) {
119 fprintf (stderr, _("%s: can only change local entries; use yp%s instead.\n"),
120 whoami, whoami);
121 exit(1);
122 }
123
124 oldshell = pw->pw_shell;
125 if (!oldshell[0]) oldshell = "/bin/sh";
126
127 /* reality check */
128 if (uid != 0 && (uid != pw->pw_uid || !get_shell_list(oldshell))) {
129 errno = EACCES;
130 fprintf(stderr,_("%s: Your shell is not in /etc/shells, shell change"
131 " denied\n"),whoami);
132 return (-1);
133 }
134
135 shell = info.shell;
136
137 printf( _("Changing shell for %s.\n"), pw->pw_name );
138
139 #if REQUIRE_PASSWORD
140 # if USE_PAM
141 if(uid != 0) {
142 if (pam_start("chsh", pw->pw_name, &conv, &pamh)) {
143 puts(_("Password error."));
144 exit(1);
145 }
146 if (pam_authenticate(pamh, 0)) {
147 puts(_("Password error."));
148 exit(1);
149 }
150 retcode = pam_acct_mgmt(pamh, 0);
151 if (retcode == PAM_NEW_AUTHTOK_REQD) {
152 retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
153 } else if (retcode) {
154 puts(_("Password error."));
155 exit(1);
156 }
157 if (pam_setcred(pamh, 0)) {
158 puts(_("Password error."));
159 exit(1);
160 }
161 /* no need to establish a session; this isn't a session-oriented
162 * activity... */
163 }
164 # else /* USE_PAM */
165 /* require password, unless root */
166 if(uid != 0 && pw->pw_passwd && pw->pw_passwd[0]) {
167 char *pwdstr = getpass(_("Password: "));
168 if(strncmp(pw->pw_passwd,
169 crypt(pwdstr, pw->pw_passwd), 13)) {
170 puts(_("Incorrect password."));
171 exit(1);
172 }
173 }
174 # endif /* USE_PAM */
175 #endif /* REQUIRE_PASSWORD */
176
177 if (! shell) {
178 shell = prompt (_("New shell"), oldshell);
179 if (! shell) return 0;
180 }
181
182 if (check_shell (shell) < 0) return (-1);
183
184 if (! strcmp (pw->pw_shell, shell)) {
185 printf (_("Shell not changed.\n"));
186 return 0;
187 }
188 if (!strcmp(shell, "/bin/sh")) shell = "";
189 pw->pw_shell = shell;
190 if (setpwnam (pw) < 0) {
191 perror ("setpwnam");
192 printf( _("Shell *NOT* changed. Try again later.\n") );
193 return (-1);
194 }
195 printf (_("Shell changed.\n"));
196 return 0;
197 }
198
199 /*
200 * parse_argv () --
201 * parse the command line arguments, and fill in "pinfo" with any
202 * information from the command line.
203 */
204 static void parse_argv (argc, argv, pinfo)
205 int argc;
206 char *argv[];
207 struct sinfo *pinfo;
208 {
209 int index, c;
210
211 static struct option long_options[] = {
212 { "shell", required_argument, 0, 's' },
213 { "list-shells", no_argument, 0, 'l' },
214 { "help", no_argument, 0, 'u' },
215 { "version", no_argument, 0, 'v' },
216 { NULL, no_argument, 0, '0' },
217 };
218
219 optind = c = 0;
220 while (c != EOF) {
221 c = getopt_long (argc, argv, "s:luv", long_options, &index);
222 switch (c) {
223 case EOF:
224 break;
225 case 'v':
226 printf ("%s\n", util_linux_version);
227 exit (0);
228 case 'u':
229 usage (stdout);
230 exit (0);
231 case 'l':
232 get_shell_list (NULL);
233 exit (0);
234 case 's':
235 if (! optarg) {
236 usage (stderr);
237 exit (-1);
238 }
239 pinfo->shell = optarg;
240 break;
241 default:
242 usage (stderr);
243 exit (-1);
244 }
245 }
246 /* done parsing arguments. check for a username. */
247 if (optind < argc) {
248 if (optind + 1 < argc) {
249 usage (stderr);
250 exit (-1);
251 }
252 pinfo->username = argv[optind];
253 }
254 }
255
256 /*
257 * usage () --
258 * print out a usage message.
259 */
260 static void usage (fp)
261 FILE *fp;
262 {
263 fprintf (fp, _("Usage: %s [ -s shell ] "), whoami);
264 fprintf (fp, _("[ --list-shells ] [ --help ] [ --version ]\n"));
265 fprintf (fp, _(" [ username ]\n"));
266 }
267
268 /*
269 * prompt () --
270 * ask the user for a given field and return it.
271 */
272 static char *prompt (question, def_val)
273 char *question;
274 char *def_val;
275 {
276 int len;
277 char *ans, *cp;
278
279 if (! def_val) def_val = "";
280 printf("%s [%s]: ", question, def_val);
281 *buf = 0;
282 if (fgets (buf, sizeof (buf), stdin) == NULL) {
283 printf (_("\nAborted.\n"));
284 exit (-1);
285 }
286 /* remove the newline at the end of buf. */
287 ans = buf;
288 while (isspace (*ans)) ans++;
289 len = strlen (ans);
290 while (len > 0 && isspace (ans[len-1])) len--;
291 if (len <= 0) return NULL;
292 ans[len] = 0;
293 cp = (char *) xmalloc (len + 1);
294 strcpy (cp, ans);
295 return cp;
296 }
297
298 /*
299 * check_shell () -- if the shell is completely invalid, print
300 * an error and return (-1).
301 * if the shell is a bad idea, print a warning.
302 */
303 static int check_shell (shell)
304 char *shell;
305 {
306 int i, c;
307
308 if (*shell != '/') {
309 printf (_("%s: shell must be a full path name.\n"), whoami);
310 return (-1);
311 }
312 if (access (shell, F_OK) < 0) {
313 printf (_("%s: \"%s\" does not exist.\n"), whoami, shell);
314 return (-1);
315 }
316 if (access (shell, X_OK) < 0) {
317 printf (_("%s: \"%s\" is not executable.\n"), whoami, shell);
318 return (-1);
319 }
320 /* keep /etc/passwd clean. */
321 for (i = 0; i < strlen (shell); i++) {
322 c = shell[i];
323 if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
324 printf (_("%s: '%c' is not allowed.\n"), whoami, c);
325 return (-1);
326 }
327 if (iscntrl (c)) {
328 printf (_("%s: Control characters are not allowed.\n"), whoami);
329 return (-1);
330 }
331 }
332 #if ONLY_LISTED_SHELLS
333 if (! get_shell_list (shell)) {
334 if (!getuid())
335 printf (_("Warning: \"%s\" is not listed in /etc/shells\n"), shell);
336 else {
337 printf (_("%s: \"%s\" is not listed in /etc/shells.\n"),
338 whoami, shell);
339 printf( _("%s: use -l option to see list\n"), whoami );
340 exit(1);
341 }
342 }
343 #else
344 if (! get_shell_list (shell)) {
345 printf (_("Warning: \"%s\" is not listed in /etc/shells.\n"), shell);
346 printf( _("Use %s -l to see list.\n"), whoami );
347 }
348 #endif
349 return 0;
350 }
351
352 /*
353 * get_shell_list () -- if the given shell appears in /etc/shells,
354 * return true. if not, return false.
355 * if the given shell is NULL, /etc/shells is outputted to stdout.
356 */
357 static boolean get_shell_list (shell_name)
358 char *shell_name;
359 {
360 FILE *fp;
361 boolean found;
362 int len;
363
364 found = false;
365 fp = fopen ("/etc/shells", "r");
366 if (! fp) {
367 if (! shell_name) printf (_("No known shells.\n"));
368 return true;
369 }
370 while (fgets (buf, sizeof (buf), fp) != NULL) {
371 /* ignore comments */
372 if (*buf == '#') continue;
373 len = strlen (buf);
374 /* strip the ending newline */
375 if (buf[len - 1] == '\n') buf[len - 1] = 0;
376 /* ignore lines that are too damn long */
377 else continue;
378 /* check or output the shell */
379 if (shell_name) {
380 if (! strcmp (shell_name, buf)) {
381 found = true;
382 break;
383 }
384 }
385 else printf ("%s\n", buf);
386 }
387 fclose (fp);
388 return found;
389 }
390
391 /*
392 * xmalloc () -- malloc that never fails.
393 */
394 static void *xmalloc (bytes)
395 int bytes;
396 {
397 void *vp;
398
399 vp = malloc (bytes);
400 if (! vp && bytes > 0) {
401 perror (_("malloc failed"));
402 exit (-1);
403 }
404 return vp;
405 }