]> git.saurik.com Git - apple/shell_cmds.git/blob - su/su.c
a7197f5c5b994d93e04e25fc9040a2d1c62ecfa3
[apple/shell_cmds.git] / su / su.c
1 /*
2 * Copyright (c) 1988, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34 #ifndef lint
35 static const char copyright[] =
36 "@(#) Copyright (c) 1988, 1993, 1994\n\
37 The Regents of the University of California. All rights reserved.\n";
38 #endif /* not lint */
39
40 #ifndef lint
41 #if 0
42 static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";
43 #endif
44 static const char rcsid[] =
45 "$FreeBSD: src/usr.bin/su/su.c,v 1.48 2002/01/24 16:20:17 des Exp $";
46 #endif /* not lint */
47
48 #include <sys/param.h>
49 #include <sys/time.h>
50 #include <sys/resource.h>
51 #include <sys/wait.h>
52
53 #include <err.h>
54 #include <errno.h>
55 #include <grp.h>
56 #include <paths.h>
57 #include <pwd.h>
58 #include <signal.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <syslog.h>
63 #include <unistd.h>
64
65 #include <pam/pam_appl.h>
66 #include <pam/pam_misc.h>
67
68 #define PAM_END() do { \
69 int local_ret; \
70 if (pamh != NULL && creds_set) { \
71 local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \
72 if (local_ret != PAM_SUCCESS) \
73 syslog(LOG_ERR, "pam_setcred: %s", \
74 pam_strerror(pamh, local_ret)); \
75 local_ret = pam_close_session(pamh, 0); \
76 local_ret = pam_end(pamh, local_ret); \
77 if (local_ret != PAM_SUCCESS) \
78 syslog(LOG_ERR, "pam_end: %s", \
79 pam_strerror(pamh, local_ret)); \
80 } \
81 } while (0)
82
83
84 #define PAM_SET_ITEM(what, item) do { \
85 int local_ret; \
86 local_ret = pam_set_item(pamh, what, item); \
87 if (local_ret != PAM_SUCCESS) { \
88 syslog(LOG_ERR, "pam_set_item(" #what "): %s", \
89 pam_strerror(pamh, local_ret)); \
90 errx(1, "pam_set_item(" #what "): %s", \
91 pam_strerror(pamh, local_ret)); \
92 } \
93 } while (0)
94
95 enum tristate { UNSET, YES, NO };
96
97 static pam_handle_t *pamh = NULL;
98 static int creds_set = 0;
99 static char **environ_pam;
100
101 static char *ontty(void);
102 static int chshell(char *);
103 static void usage(void);
104 static int export_pam_environment(void);
105 static int ok_to_export(const char *);
106
107 extern char **environ;
108
109 int
110 main(int argc, char *argv[])
111 {
112 static char *cleanenv;
113 struct passwd *pwd;
114 struct pam_conv conv = {misc_conv, NULL};
115 enum tristate iscsh;
116 union {
117 const char **a;
118 char * const *b;
119 } np;
120 uid_t ruid;
121 gid_t gid;
122 int asme, ch, asthem, fastlogin, prio, i, setwhat, retcode,
123 statusp, child_pid, child_pgrp, ret_pid;
124 char *username, *class, shellbuf[MAXPATHLEN];
125 const char *p, *user, *shell, *mytty, **nargv;
126 const char *avshell;
127 char avshellbuf[MAXPATHLEN];
128
129 shell = class = cleanenv = NULL;
130 asme = asthem = fastlogin = statusp = 0;
131 user = "root";
132 iscsh = UNSET;
133
134 #ifdef __APPLE__
135 while ((ch = getopt(argc, argv, "-flm")) != -1)
136 #else
137 while ((ch = getopt(argc, argv, "-flmc:")) != -1)
138 #endif /* __APPLE__ */
139 switch ((char)ch) {
140 case 'f':
141 fastlogin = 1;
142 break;
143 case '-':
144 case 'l':
145 asme = 0;
146 asthem = 1;
147 break;
148 case 'm':
149 asme = 1;
150 asthem = 0;
151 break;
152 #ifndef __APPLE__
153 case 'c':
154 class = optarg;
155 break;
156 #endif /* !__APPLE__ */
157 case '?':
158 default:
159 usage();
160 }
161
162 if (optind < argc)
163 user = argv[optind++];
164
165 if (user == NULL)
166 usage();
167
168 if (strlen(user) > MAXLOGNAME - 1)
169 errx(1, "username too long");
170
171 nargv = malloc(sizeof(char *) * (argc + 4));
172 if (nargv == NULL)
173 errx(1, "malloc failure");
174
175 nargv[argc + 3] = NULL;
176 for (i = argc; i >= optind; i--)
177 nargv[i + 3] = argv[i];
178 np.a = &nargv[i + 3];
179
180 argv += optind;
181
182 errno = 0;
183 prio = getpriority(PRIO_PROCESS, 0);
184 if (errno)
185 prio = 0;
186
187 setpriority(PRIO_PROCESS, 0, -2);
188 openlog("su", LOG_CONS, LOG_AUTH);
189
190 /* get current login name, real uid and shell */
191 ruid = getuid();
192 username = getlogin();
193 pwd = getpwnam(username);
194 if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
195 pwd = getpwuid(ruid);
196 if (pwd == NULL)
197 errx(1, "who are you?");
198 gid = pwd->pw_gid;
199
200 username = strdup(pwd->pw_name);
201 if (username == NULL)
202 err(1, "strdup failure");
203
204 if (asme) {
205 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
206 /* must copy - pwd memory is recycled */
207 shell = strncpy(shellbuf, pwd->pw_shell,
208 sizeof(shellbuf));
209 shellbuf[sizeof(shellbuf) - 1] = '\0';
210 }
211 else {
212 shell = _PATH_BSHELL;
213 iscsh = NO;
214 }
215 }
216
217 /* Do the whole PAM startup thing */
218 retcode = pam_start("su", user, &conv, &pamh);
219 if (retcode != PAM_SUCCESS) {
220 syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
221 errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
222 }
223
224 PAM_SET_ITEM(PAM_RUSER, getlogin());
225
226 mytty = ttyname(STDERR_FILENO);
227 if (!mytty)
228 mytty = "tty";
229 PAM_SET_ITEM(PAM_TTY, mytty);
230
231 retcode = pam_authenticate(pamh, 0);
232 if (retcode != PAM_SUCCESS) {
233 syslog(LOG_ERR, "pam_authenticate: %s",
234 pam_strerror(pamh, retcode));
235 errx(1, "Sorry");
236 }
237 retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
238 if (retcode == PAM_SUCCESS)
239 user = p;
240 else
241 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
242 pam_strerror(pamh, retcode));
243
244 retcode = pam_acct_mgmt(pamh, 0);
245 if (retcode == PAM_NEW_AUTHTOK_REQD) {
246 retcode = pam_chauthtok(pamh,
247 PAM_CHANGE_EXPIRED_AUTHTOK);
248 if (retcode != PAM_SUCCESS) {
249 syslog(LOG_ERR, "pam_chauthtok: %s",
250 pam_strerror(pamh, retcode));
251 errx(1, "Sorry");
252 }
253 }
254 if (retcode != PAM_SUCCESS) {
255 syslog(LOG_ERR, "pam_acct_mgmt: %s",
256 pam_strerror(pamh, retcode));
257 errx(1, "Sorry");
258 }
259
260 /* get target login information, default to root */
261 pwd = getpwnam(user);
262 if (pwd == NULL)
263 errx(1, "unknown login: %s", user);
264
265 /* if asme and non-standard target shell, must be root */
266 if (asme) {
267 if (ruid != 0 && !chshell(pwd->pw_shell))
268 errx(1, "permission denied (shell).");
269 }
270 else if (pwd->pw_shell && *pwd->pw_shell) {
271 shell = strncpy(shellbuf, pwd->pw_shell, sizeof(shellbuf));
272 shellbuf[sizeof(shellbuf) - 1] = '\0';
273 iscsh = UNSET;
274 }
275 else {
276 shell = _PATH_BSHELL;
277 iscsh = NO;
278 }
279
280 if ((p = strrchr(shell, '/')) != NULL)
281 avshell = p+1;
282 else
283 avshell = shell;
284
285 /* if we're forking a csh, we want to slightly muck the args */
286 if (iscsh == UNSET) {
287 iscsh = strcmp(avshell, "csh") ? (strcmp(avshell, "tcsh") ? NO : YES) : YES;
288 }
289 setpriority(PRIO_PROCESS, 0, prio);
290
291 /*
292 * PAM modules might add supplementary groups in pam_setcred(), so
293 * initialize them first.
294 */
295 if( initgroups(user, pwd->pw_gid) )
296 err(1, "initgroups failed");
297
298 retcode = pam_open_session(pamh, 0);
299 if( retcode != PAM_SUCCESS ) {
300 syslog(LOG_ERR, "pam_open_session(pamh, 0): %s",
301 pam_strerror(pamh, retcode));
302 }
303
304 retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
305 if (retcode != PAM_SUCCESS)
306 syslog(LOG_ERR, "pam_setcred(pamh, PAM_ESTABLISH_CRED): %s",
307 pam_strerror(pamh, retcode));
308 else
309 creds_set = 1;
310
311 /*
312 * We must fork() before setuid() because we need to call
313 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
314 */
315
316 statusp = 1;
317 child_pid = fork();
318 switch (child_pid) {
319 default:
320 while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
321 if (WIFSTOPPED(statusp)) {
322 child_pgrp = tcgetpgrp(1);
323 kill(getpid(), SIGSTOP);
324 tcsetpgrp(1, child_pgrp);
325 kill(child_pid, SIGCONT);
326 statusp = 1;
327 continue;
328 }
329 break;
330 }
331 if (ret_pid == -1)
332 err(1, "waitpid");
333 PAM_END();
334 exit(WEXITSTATUS(statusp));
335 case -1:
336 err(1, "fork");
337 PAM_END();
338 exit(1);
339 case 0:
340 if( setgid(pwd->pw_gid) )
341 err(1, "setgid");
342 /* Call initgroups(2) after setgid(2) to re-establish memberd */
343 if( initgroups(user, pwd->pw_gid) )
344 err(1, "initgroups");
345 if( setuid(pwd->pw_uid) )
346 err(1, "setuid");
347
348 if (!asme) {
349 if (asthem) {
350 p = getenv("TERM");
351 environ = &cleanenv;
352 }
353
354 if (asthem || pwd->pw_uid)
355 setenv("USER", pwd->pw_name, 1);
356 setenv("HOME", pwd->pw_dir, 1);
357 setenv("SHELL", shell, 1);
358
359 if (asthem) {
360 /*
361 * Add any environmental variables that the
362 * PAM modules may have set.
363 */
364 environ_pam = pam_getenvlist(pamh);
365 if (environ_pam)
366 export_pam_environment();
367
368 #ifdef __APPLE__
369 /* 5276965: As documented, set $PATH. */
370 setenv("PATH", "/bin:/usr/bin", 1);
371 #else
372 /* set the su'd user's environment & umask */
373 setusercontext(lc, pwd, pwd->pw_uid,
374 LOGIN_SETPATH | LOGIN_SETUMASK |
375 LOGIN_SETENV);
376 #endif
377 if (p)
378 setenv("TERM", p, 1);
379
380 p = pam_getenv(pamh, "HOME");
381 if (chdir(p ? p : pwd->pw_dir) < 0)
382 errx(1, "no directory");
383 }
384 }
385
386 if (iscsh == YES) {
387 if (fastlogin)
388 *np.a-- = "-f";
389 if (asme)
390 *np.a-- = "-m";
391 }
392
393 if (asthem) {
394 avshellbuf[0] = '-';
395 strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
396 avshell = avshellbuf;
397 }
398
399 /* csh *no longer* strips the first character... */
400 *np.a = avshell;
401
402 if (ruid != 0)
403 syslog(LOG_NOTICE, "%s to %s%s", username, user,
404 ontty());
405
406 execv(shell, np.b);
407 err(1, "%s", shell);
408 }
409 }
410
411 static int
412 export_pam_environment(void)
413 {
414 char **pp;
415
416 for (pp = environ_pam; *pp != NULL; pp++) {
417 if (ok_to_export(*pp))
418 putenv(*pp);
419 free(*pp);
420 }
421 return PAM_SUCCESS;
422 }
423
424 /*
425 * Sanity checks on PAM environmental variables:
426 * - Make sure there is an '=' in the string.
427 * - Make sure the string doesn't run on too long.
428 * - Do not export certain variables. This list was taken from the
429 * Solaris pam_putenv(3) man page.
430 */
431 static int
432 ok_to_export(const char *s)
433 {
434 static const char *noexport[] = {
435 "SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH",
436 "IFS", "PATH", NULL
437 };
438 const char **pp;
439 size_t n;
440
441 if (strlen(s) > 1024 || strchr(s, '=') == NULL)
442 return 0;
443 if (strncmp(s, "LD_", 3) == 0)
444 return 0;
445 for (pp = noexport; *pp != NULL; pp++) {
446 n = strlen(*pp);
447 if (s[n] == '=' && strncmp(s, *pp, n) == 0)
448 return 0;
449 }
450 return 1;
451 }
452
453 static void
454 usage(void)
455 {
456
457 #ifdef __APPLE__
458 fprintf(stderr, "usage: su [-] [-flm] [login [args]]\n");
459 #else
460 fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
461 #endif /* __APPLE__ */
462 exit(1);
463 }
464
465 static int
466 chshell(char *sh)
467 {
468 int r;
469 char *cp;
470
471 r = 0;
472 setusershell();
473 do {
474 cp = getusershell();
475 r = strcmp(cp, sh);
476 } while (!r && cp != NULL);
477 endusershell();
478 return r;
479 }
480
481 static char *
482 ontty(void)
483 {
484 char *p;
485 static char buf[MAXPATHLEN + 4];
486
487 buf[0] = 0;
488 p = ttyname(STDERR_FILENO);
489 if (p)
490 snprintf(buf, sizeof(buf), " on %s", p);
491 return buf;
492 }