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