]>
Commit | Line | Data |
---|---|---|
9678ebcd JM |
1 | /* su for GNU. Run a shell with substitute user and group IDs. |
2 | Copyright (C) 1992 Free Software Foundation, Inc. | |
3 | ||
4 | This program is free software; you can redistribute it and/or modify | |
5 | it under the terms of the GNU General Public License as published by | |
6 | the Free Software Foundation; either version 2, or (at your option) | |
7 | any later version. | |
8 | ||
9 | This program is distributed in the hope that it will be useful, | |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | GNU General Public License for more details. | |
13 | ||
14 | You should have received a copy of the GNU General Public License | |
15 | along with this program; if not, write to the Free Software | |
16 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ | |
17 | ||
18 | /* Run a shell with the real and effective UID and GID and groups | |
19 | of USER, default `root'. | |
20 | ||
21 | The shell run is taken from USER's password entry, /bin/sh if | |
22 | none is specified there. If the account has a password, su | |
23 | prompts for a password unless run by a user with real UID 0. | |
24 | ||
25 | Does not change the current directory. | |
26 | Sets `HOME' and `SHELL' from the password entry for USER, and if | |
27 | USER is not root, sets `USER' and `LOGNAME' to USER. | |
28 | The subshell is not a login shell. | |
29 | ||
30 | If one or more ARGs are given, they are passed as additional | |
31 | arguments to the subshell. | |
32 | ||
33 | Does not handle /bin/sh or other shells specially | |
34 | (setting argv[0] to "-su", passing -c only to certain shells, etc.). | |
35 | I don't see the point in doing that, and it's ugly. | |
36 | ||
37 | This program intentionally does not support a "wheel group" that | |
38 | restricts who can su to UID 0 accounts. RMS considers that to | |
39 | be fascist. | |
40 | ||
41 | Options: | |
42 | -, -l, --login Make the subshell a login shell. | |
43 | Unset all environment variables except | |
44 | TERM, HOME and SHELL (set as above), and USER | |
45 | and LOGNAME (set unconditionally as above), and | |
46 | set PATH to a default value. | |
47 | Change to USER's home directory. | |
48 | Prepend "-" to the shell's name. | |
49 | -c, --commmand=COMMAND | |
50 | Pass COMMAND to the subshell with a -c option | |
51 | instead of starting an interactive shell. | |
52 | -f, --fast Pass the -f option to the subshell. | |
53 | -m, -p, --preserve-environment | |
54 | Do not change HOME, USER, LOGNAME, SHELL. | |
55 | Run $SHELL instead of USER's shell from /etc/passwd | |
56 | unless not the superuser and USER's shell is | |
57 | restricted. | |
58 | Overridden by --login and --shell. | |
59 | -s, --shell=shell Run SHELL instead of USER's shell from /etc/passwd | |
60 | unless not the superuser and USER's shell is | |
61 | restricted. | |
62 | ||
63 | Compile-time options: | |
64 | -DSYSLOG_SUCCESS Log successful su's (by default, to root) with syslog. | |
65 | -DSYSLOG_FAILURE Log failed su's (by default, to root) with syslog. | |
66 | ||
67 | -DSYSLOG_NON_ROOT Log all su's, not just those to root (UID 0). | |
68 | Never logs attempted su's to nonexistent accounts. | |
69 | ||
70 | Written by David MacKenzie <djm@gnu.ai.mit.edu>. */ | |
71 | ||
72 | #include <stdio.h> | |
73 | #include <getopt.h> | |
74 | #include <sys/types.h> | |
75 | #include <pwd.h> | |
76 | #include "system.h" | |
77 | ||
78 | #ifdef HAVE_SYSLOG_H | |
79 | #include <syslog.h> | |
80 | void log_su (); | |
81 | #else | |
82 | #ifdef SYSLOG_SUCCESS | |
83 | #undef SYSLOG_SUCCESS | |
84 | #endif | |
85 | #ifdef SYSLOG_FAILURE | |
86 | #undef SYSLOG_FAILURE | |
87 | #endif | |
88 | #ifdef SYSLOG_NON_ROOT | |
89 | #undef SYSLOG_NON_ROOT | |
90 | #endif | |
91 | #endif | |
92 | ||
93 | #ifdef _POSIX_VERSION | |
94 | #include <limits.h> | |
95 | #ifdef NGROUPS_MAX | |
96 | #undef NGROUPS_MAX | |
97 | #endif | |
98 | #define NGROUPS_MAX sysconf (_SC_NGROUPS_MAX) | |
99 | #else /* not _POSIX_VERSION */ | |
100 | struct passwd *getpwuid (); | |
101 | struct group *getgrgid (); | |
102 | uid_t getuid (); | |
103 | #include <sys/param.h> | |
104 | #if !defined(NGROUPS_MAX) && defined(NGROUPS) | |
105 | #define NGROUPS_MAX NGROUPS | |
106 | #endif | |
107 | #endif /* not _POSIX_VERSION */ | |
108 | ||
109 | #ifdef _POSIX_SOURCE | |
110 | #define endgrent() | |
111 | #define endpwent() | |
112 | #endif | |
113 | ||
114 | #ifdef HAVE_SHADOW_H | |
115 | #include <shadow.h> | |
116 | #endif | |
117 | ||
118 | /* The default PATH for simulated logins to non-superuser accounts. */ | |
119 | #define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin" | |
120 | ||
121 | /* The default PATH for simulated logins to superuser accounts. */ | |
122 | #define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc" | |
123 | ||
124 | /* The shell to run if none is given in the user's passwd entry. */ | |
125 | #define DEFAULT_SHELL "/bin/sh" | |
126 | ||
127 | /* The user to become if none is specified. */ | |
128 | #define DEFAULT_USER "root" | |
129 | ||
130 | char *crypt (); | |
131 | char *getpass (); | |
132 | char *getusershell (); | |
133 | void endusershell (); | |
134 | void setusershell (); | |
135 | ||
136 | char *basename (); | |
137 | char *concat (); | |
138 | char *xmalloc (); | |
139 | char *xrealloc (); | |
140 | int correct_password (); | |
141 | int elements (); | |
142 | int restricted_shell (); | |
143 | void change_identity (); | |
144 | void error (); | |
145 | void modify_environment (); | |
146 | void run_shell (); | |
147 | void usage (); | |
148 | void xputenv (); | |
149 | ||
150 | extern char **environ; | |
151 | ||
152 | /* The name this program was run with. */ | |
153 | char *program_name; | |
154 | ||
155 | /* If nonzero, pass the `-f' option to the subshell. */ | |
156 | int fast_startup; | |
157 | ||
158 | /* If nonzero, simulate a login instead of just starting a shell. */ | |
159 | int simulate_login; | |
160 | ||
161 | /* If nonzero, change some environment vars to indicate the user su'd to. */ | |
162 | int change_environment; | |
163 | ||
164 | struct option longopts[] = | |
165 | { | |
166 | {"command", 1, 0, 'c'}, | |
167 | {"fast", 0, &fast_startup, 1}, | |
168 | {"login", 0, &simulate_login, 1}, | |
169 | {"preserve-environment", 0, &change_environment, 0}, | |
170 | {"shell", 1, 0, 's'}, | |
171 | {0, 0, 0, 0} | |
172 | }; | |
173 | ||
174 | void | |
175 | main (argc, argv) | |
176 | int argc; | |
177 | char **argv; | |
178 | { | |
179 | int optc; | |
180 | char *new_user = DEFAULT_USER; | |
181 | char *command = 0; | |
182 | char **additional_args = 0; | |
183 | char *shell = 0; | |
184 | struct passwd *pw; | |
185 | ||
186 | program_name = argv[0]; | |
187 | fast_startup = 0; | |
188 | simulate_login = 0; | |
189 | change_environment = 1; | |
190 | ||
191 | while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, (int *) 0)) | |
192 | != EOF) | |
193 | { | |
194 | switch (optc) | |
195 | { | |
196 | case 0: | |
197 | break; | |
198 | case 'c': | |
199 | command = optarg; | |
200 | break; | |
201 | case 'f': | |
202 | fast_startup = 1; | |
203 | break; | |
204 | case 'l': | |
205 | simulate_login = 1; | |
206 | break; | |
207 | case 'm': | |
208 | case 'p': | |
209 | change_environment = 0; | |
210 | break; | |
211 | case 's': | |
212 | shell = optarg; | |
213 | break; | |
214 | default: | |
215 | usage (); | |
216 | } | |
217 | } | |
218 | if (optind < argc && !strcmp (argv[optind], "-")) | |
219 | { | |
220 | simulate_login = 1; | |
221 | ++optind; | |
222 | } | |
223 | if (optind < argc) | |
224 | new_user = argv[optind++]; | |
225 | if (optind < argc) | |
226 | additional_args = argv + optind; | |
227 | ||
228 | pw = getpwnam (new_user); | |
229 | if (pw == 0) | |
230 | error (1, 0, "user %s does not exist", new_user); | |
231 | endpwent (); | |
232 | if (!correct_password (pw)) | |
233 | { | |
234 | #ifdef SYSLOG_FAILURE | |
235 | log_su (pw, 0); | |
236 | #endif | |
237 | error (1, 0, "incorrect password"); | |
238 | } | |
239 | #ifdef SYSLOG_SUCCESS | |
240 | else | |
241 | { | |
242 | log_su (pw, 1); | |
243 | } | |
244 | #endif | |
245 | ||
246 | if (pw->pw_shell == 0 || pw->pw_shell[0] == 0) | |
247 | pw->pw_shell = DEFAULT_SHELL; | |
248 | if (shell == 0 && change_environment == 0) | |
249 | shell = getenv ("SHELL"); | |
250 | if (shell != 0 && getuid () && restricted_shell (pw->pw_shell)) | |
251 | { | |
252 | /* The user being su'd to has a nonstandard shell, and so is | |
253 | probably a uucp account or has restricted access. Don't | |
254 | compromise the account by allowing access with a standard | |
255 | shell. */ | |
256 | error (0, 0, "using restricted shell %s", pw->pw_shell); | |
257 | shell = 0; | |
258 | } | |
259 | if (shell == 0) | |
260 | shell = pw->pw_shell; | |
261 | shell = strcpy (xmalloc (strlen (shell) + 1), shell); | |
262 | modify_environment (pw, shell); | |
263 | ||
264 | change_identity (pw); | |
265 | if (simulate_login && chdir (pw->pw_dir)) | |
266 | error (0, errno, "warning: cannot change directory to %s", pw->pw_dir); | |
267 | run_shell (shell, command, additional_args); | |
268 | } | |
269 | ||
270 | /* Ask the user for a password. | |
271 | Return 1 if the user gives the correct password for entry PW, | |
272 | 0 if not. Return 1 without asking for a password if run by UID 0 | |
273 | or if PW has an empty password. */ | |
274 | ||
275 | int | |
276 | correct_password (pw) | |
277 | struct passwd *pw; | |
278 | { | |
279 | char *unencrypted, *encrypted, *correct; | |
280 | #ifdef HAVE_SHADOW_H | |
281 | /* Shadow passwd stuff for SVR3 and maybe other systems. */ | |
282 | struct spwd *sp = getspnam (pw->pw_name); | |
283 | ||
284 | endspent (); | |
285 | if (sp) | |
286 | correct = sp->sp_pwdp; | |
287 | else | |
288 | #endif | |
289 | correct = pw->pw_passwd; | |
290 | ||
291 | if (getuid () == 0 || correct == 0 || correct[0] == '\0') | |
292 | return 1; | |
293 | ||
294 | unencrypted = getpass ("Password:"); | |
295 | encrypted = crypt (unencrypted, correct); | |
296 | bzero (unencrypted, strlen (unencrypted)); | |
297 | return strcmp (encrypted, correct) == 0; | |
298 | } | |
299 | ||
300 | /* Update `environ' for the new shell based on PW, with SHELL being | |
301 | the value for the SHELL environment variable. */ | |
302 | ||
303 | void | |
304 | modify_environment (pw, shell) | |
305 | struct passwd *pw; | |
306 | char *shell; | |
307 | { | |
308 | char *term; | |
309 | ||
310 | if (simulate_login) | |
311 | { | |
312 | /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH. | |
313 | Unset all other environment variables. */ | |
314 | term = getenv ("TERM"); | |
315 | environ = (char **) xmalloc (2 * sizeof (char *)); | |
316 | environ[0] = 0; | |
317 | if (term) | |
318 | xputenv (concat ("TERM", "=", term)); | |
319 | xputenv (concat ("HOME", "=", pw->pw_dir)); | |
320 | xputenv (concat ("SHELL", "=", shell)); | |
321 | xputenv (concat ("USER", "=", pw->pw_name)); | |
322 | xputenv (concat ("LOGNAME", "=", pw->pw_name)); | |
323 | xputenv (concat ("PATH", "=", pw->pw_uid | |
324 | ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH)); | |
325 | } | |
326 | else | |
327 | { | |
328 | /* Set HOME, SHELL, and if not becoming a super-user, | |
329 | USER and LOGNAME. */ | |
330 | if (change_environment) | |
331 | { | |
332 | xputenv (concat ("HOME", "=", pw->pw_dir)); | |
333 | xputenv (concat ("SHELL", "=", shell)); | |
334 | if (pw->pw_uid) | |
335 | { | |
336 | xputenv (concat ("USER", "=", pw->pw_name)); | |
337 | xputenv (concat ("LOGNAME", "=", pw->pw_name)); | |
338 | } | |
339 | } | |
340 | } | |
341 | } | |
342 | ||
343 | /* Become the user and group(s) specified by PW. */ | |
344 | ||
345 | void | |
346 | change_identity (pw) | |
347 | struct passwd *pw; | |
348 | { | |
349 | #ifdef NGROUPS_MAX | |
350 | errno = 0; | |
351 | if (initgroups (pw->pw_name, pw->pw_gid) == -1) | |
352 | error (1, errno, "cannot set groups"); | |
353 | endgrent (); | |
354 | #endif | |
355 | if (setgid (pw->pw_gid)) | |
356 | error (1, errno, "cannot set group id"); | |
357 | if (setuid (pw->pw_uid)) | |
358 | error (1, errno, "cannot set user id"); | |
359 | } | |
360 | ||
361 | /* Run SHELL, or DEFAULT_SHELL if SHELL is empty. | |
362 | If COMMAND is nonzero, pass it to the shell with the -c option. | |
363 | If ADDITIONAL_ARGS is nonzero, pass it to the shell as more | |
364 | arguments. */ | |
365 | ||
366 | void | |
367 | run_shell (shell, command, additional_args) | |
368 | char *shell; | |
369 | char *command; | |
370 | char **additional_args; | |
371 | { | |
372 | char **args; | |
373 | int argno = 1; | |
374 | ||
375 | if (additional_args) | |
376 | args = (char **) xmalloc (sizeof (char *) | |
377 | * (10 + elements (additional_args))); | |
378 | else | |
379 | args = (char **) xmalloc (sizeof (char *) * 10); | |
380 | if (simulate_login) | |
381 | { | |
382 | args[0] = xmalloc (strlen (shell) + 2); | |
383 | args[0][0] = '-'; | |
384 | strcpy (args[0] + 1, basename (shell)); | |
385 | } | |
386 | else | |
387 | args[0] = basename (shell); | |
388 | if (fast_startup) | |
389 | args[argno++] = "-f"; | |
390 | if (command) | |
391 | { | |
392 | args[argno++] = "-c"; | |
393 | args[argno++] = command; | |
394 | } | |
395 | if (additional_args) | |
396 | for (; *additional_args; ++additional_args) | |
397 | args[argno++] = *additional_args; | |
398 | args[argno] = 0; | |
399 | execv (shell, args); | |
400 | error (1, errno, "cannot run %s", shell); | |
401 | } | |
402 | ||
403 | #if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE) | |
404 | /* Log the fact that someone has run su to the user given by PW; | |
405 | if SUCCESSFUL is nonzero, they gave the correct password, etc. */ | |
406 | ||
407 | void | |
408 | log_su (pw, successful) | |
409 | struct passwd *pw; | |
410 | int successful; | |
411 | { | |
412 | char *new_user, *old_user, *tty; | |
413 | ||
414 | #ifndef SYSLOG_NON_ROOT | |
415 | if (pw->pw_uid) | |
416 | return; | |
417 | #endif | |
418 | new_user = pw->pw_name; | |
419 | /* The utmp entry (via getlogin) is probably the best way to identify | |
420 | the user, especially if someone su's from a su-shell. */ | |
421 | old_user = getlogin (); | |
422 | if (old_user == 0) | |
423 | old_user = ""; | |
424 | tty = ttyname (2); | |
425 | if (tty == 0) | |
426 | tty = ""; | |
427 | /* 4.2BSD openlog doesn't have the third parameter. */ | |
428 | openlog (basename (program_name), 0 | |
429 | #ifdef LOG_AUTH | |
430 | , LOG_AUTH | |
431 | #endif | |
432 | ); | |
433 | syslog (LOG_NOTICE, | |
434 | #ifdef SYSLOG_NON_ROOT | |
435 | "%s(to %s) %s on %s", | |
436 | #else | |
437 | "%s%s on %s", | |
438 | #endif | |
439 | successful ? "" : "FAILED SU ", | |
440 | #ifdef SYSLOG_NON_ROOT | |
441 | new_user, | |
442 | #endif | |
443 | old_user, tty); | |
444 | closelog (); | |
445 | } | |
446 | #endif | |
447 | ||
448 | /* Return 1 if SHELL is a restricted shell (one not returned by | |
449 | getusershell), else 0, meaning it is a standard shell. */ | |
450 | ||
451 | int | |
452 | restricted_shell (shell) | |
453 | char *shell; | |
454 | { | |
455 | char *line; | |
456 | ||
457 | setusershell (); | |
458 | while (line = getusershell ()) | |
459 | { | |
460 | if (*line != '#' && strcmp (line, shell) == 0) | |
461 | { | |
462 | endusershell (); | |
463 | return 0; | |
464 | } | |
465 | } | |
466 | endusershell (); | |
467 | return 1; | |
468 | } | |
469 | ||
470 | /* Return the number of elements in ARR, a null-terminated array. */ | |
471 | ||
472 | int | |
473 | elements (arr) | |
474 | char **arr; | |
475 | { | |
476 | int n = 0; | |
477 | ||
478 | for (n = 0; *arr; ++arr) | |
479 | ++n; | |
480 | return n; | |
481 | } | |
482 | ||
483 | /* Add VAL to the environment, checking for out of memory errors. */ | |
484 | ||
485 | void | |
486 | xputenv (val) | |
487 | char *val; | |
488 | { | |
489 | if (putenv (val)) | |
490 | error (1, 0, "virtual memory exhausted"); | |
491 | } | |
492 | ||
493 | /* Return a newly-allocated string whose contents concatenate | |
494 | those of S1, S2, S3. */ | |
495 | ||
496 | char * | |
497 | concat (s1, s2, s3) | |
498 | char *s1, *s2, *s3; | |
499 | { | |
500 | int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3); | |
501 | char *result = (char *) xmalloc (len1 + len2 + len3 + 1); | |
502 | ||
503 | strcpy (result, s1); | |
504 | strcpy (result + len1, s2); | |
505 | strcpy (result + len1 + len2, s3); | |
506 | result[len1 + len2 + len3] = 0; | |
507 | ||
508 | return result; | |
509 | } | |
510 | ||
511 | void | |
512 | usage () | |
513 | { | |
514 | fprintf (stderr, "\ | |
515 | Usage: %s [-flmp] [-c command] [-s shell] [--login] [--fast]\n\ | |
516 | [--preserve-environment] [--command=command] [--shell=shell] [-]\n\ | |
517 | [user [arg...]]\n", program_name); | |
518 | exit (1); | |
519 | } |