]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/su.c
Initial revision
[thirdparty/util-linux.git] / login-utils / su.c
CommitLineData
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>
80void 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 */
100struct passwd *getpwuid ();
101struct group *getgrgid ();
102uid_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
130char *crypt ();
131char *getpass ();
132char *getusershell ();
133void endusershell ();
134void setusershell ();
135
136char *basename ();
137char *concat ();
138char *xmalloc ();
139char *xrealloc ();
140int correct_password ();
141int elements ();
142int restricted_shell ();
143void change_identity ();
144void error ();
145void modify_environment ();
146void run_shell ();
147void usage ();
148void xputenv ();
149
150extern char **environ;
151
152/* The name this program was run with. */
153char *program_name;
154
155/* If nonzero, pass the `-f' option to the subshell. */
156int fast_startup;
157
158/* If nonzero, simulate a login instead of just starting a shell. */
159int simulate_login;
160
161/* If nonzero, change some environment vars to indicate the user su'd to. */
162int change_environment;
163
164struct 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
174void
175main (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
275int
276correct_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
303void
304modify_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
345void
346change_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
366void
367run_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
407void
408log_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
451int
452restricted_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
472int
473elements (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
485void
486xputenv (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
496char *
497concat (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
511void
512usage ()
513{
514 fprintf (stderr, "\
515Usage: %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}