]> git.saurik.com Git - apple/shell_cmds.git/blob - su/su.c
shell_cmds-81.tar.gz
[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 struct passwd *pwd;
113 struct pam_conv conv = {misc_conv, NULL};
114 enum tristate iscsh;
115 union {
116 const char **a;
117 char * const *b;
118 } np;
119 uid_t ruid;
120 gid_t gid;
121 int asme, ch, asthem, fastlogin, prio, i, setwhat, retcode,
122 statusp, child_pid, child_pgrp, ret_pid;
123 char *username, *cleanenv, *class, shellbuf[MAXPATHLEN];
124 const char *p, *user, *shell, *mytty, **nargv;
125
126 shell = class = cleanenv = NULL;
127 asme = asthem = fastlogin = statusp = 0;
128 user = "root";
129 iscsh = UNSET;
130
131 while ((ch = getopt(argc, argv, "-flmc:")) != -1)
132 switch ((char)ch) {
133 case 'f':
134 fastlogin = 1;
135 break;
136 case '-':
137 case 'l':
138 asme = 0;
139 asthem = 1;
140 break;
141 case 'm':
142 asme = 1;
143 asthem = 0;
144 break;
145 case 'c':
146 class = optarg;
147 break;
148 case '?':
149 default:
150 usage();
151 }
152
153 if (optind < argc)
154 user = argv[optind++];
155
156 if (user == NULL)
157 usage();
158
159 if (strlen(user) > MAXLOGNAME - 1)
160 errx(1, "username too long");
161
162 nargv = malloc(sizeof(char *) * (argc + 4));
163 if (nargv == NULL)
164 errx(1, "malloc failure");
165
166 nargv[argc + 3] = NULL;
167 for (i = argc; i >= optind; i--)
168 nargv[i + 3] = argv[i];
169 np.a = &nargv[i + 3];
170
171 argv += optind;
172
173 errno = 0;
174 prio = getpriority(PRIO_PROCESS, 0);
175 if (errno)
176 prio = 0;
177
178 setpriority(PRIO_PROCESS, 0, -2);
179 openlog("su", LOG_CONS, LOG_AUTH);
180
181 /* get current login name, real uid and shell */
182 ruid = getuid();
183 username = getlogin();
184 pwd = getpwnam(username);
185 if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
186 pwd = getpwuid(ruid);
187 if (pwd == NULL)
188 errx(1, "who are you?");
189 gid = pwd->pw_gid;
190
191 username = strdup(pwd->pw_name);
192 if (username == NULL)
193 err(1, "strdup failure");
194
195 if (asme) {
196 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
197 /* must copy - pwd memory is recycled */
198 shell = strncpy(shellbuf, pwd->pw_shell,
199 sizeof(shellbuf));
200 shellbuf[sizeof(shellbuf) - 1] = '\0';
201 }
202 else {
203 shell = _PATH_BSHELL;
204 iscsh = NO;
205 }
206 }
207
208 /* Do the whole PAM startup thing */
209 retcode = pam_start("su", user, &conv, &pamh);
210 if (retcode != PAM_SUCCESS) {
211 syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
212 errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
213 }
214
215 PAM_SET_ITEM(PAM_RUSER, getlogin());
216
217 mytty = ttyname(STDERR_FILENO);
218 if (!mytty)
219 mytty = "tty";
220 PAM_SET_ITEM(PAM_TTY, mytty);
221
222 retcode = pam_authenticate(pamh, 0);
223 if (retcode != PAM_SUCCESS) {
224 syslog(LOG_ERR, "pam_authenticate: %s",
225 pam_strerror(pamh, retcode));
226 errx(1, "Sorry");
227 }
228 retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
229 if (retcode == PAM_SUCCESS)
230 user = p;
231 else
232 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
233 pam_strerror(pamh, retcode));
234
235 retcode = pam_acct_mgmt(pamh, 0);
236 if (retcode == PAM_NEW_AUTHTOK_REQD) {
237 retcode = pam_chauthtok(pamh,
238 PAM_CHANGE_EXPIRED_AUTHTOK);
239 if (retcode != PAM_SUCCESS) {
240 syslog(LOG_ERR, "pam_chauthtok: %s",
241 pam_strerror(pamh, retcode));
242 errx(1, "Sorry");
243 }
244 }
245 if (retcode != PAM_SUCCESS) {
246 syslog(LOG_ERR, "pam_acct_mgmt: %s",
247 pam_strerror(pamh, retcode));
248 errx(1, "Sorry");
249 }
250
251 /* get target login information, default to root */
252 pwd = getpwnam(user);
253 if (pwd == NULL)
254 errx(1, "unknown login: %s", user);
255
256 /* if asme and non-standard target shell, must be root */
257 if (asme) {
258 if (ruid != 0 && !chshell(pwd->pw_shell))
259 errx(1, "permission denied (shell).");
260 }
261 else if (pwd->pw_shell && *pwd->pw_shell) {
262 shell = strncpy(shellbuf, pwd->pw_shell, sizeof(shellbuf));
263 shellbuf[sizeof(shellbuf) - 1] = '\0';
264 iscsh = UNSET;
265 }
266 else {
267 shell = _PATH_BSHELL;
268 iscsh = NO;
269 }
270
271 /* if we're forking a csh, we want to slightly muck the args */
272 if (iscsh == UNSET) {
273 p = strrchr(shell, '/');
274 if (p)
275 ++p;
276 else
277 p = shell;
278 iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
279 }
280 setpriority(PRIO_PROCESS, 0, prio);
281
282 /*
283 * PAM modules might add supplementary groups in pam_setcred(), so
284 * initialize them first.
285 */
286 if( initgroups(user, pwd->pw_gid) )
287 err(1, "initgroups failed");
288
289 retcode = pam_open_session(pamh, 0);
290 if( retcode != PAM_SUCCESS ) {
291 syslog(LOG_ERR, "pam_open_session(pamh, 0): %s",
292 pam_strerror(pamh, retcode));
293 }
294
295 retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
296 if (retcode != PAM_SUCCESS)
297 syslog(LOG_ERR, "pam_setcred(pamh, PAM_ESTABLISH_CRED): %s",
298 pam_strerror(pamh, retcode));
299 else
300 creds_set = 1;
301
302 /*
303 * We must fork() before setuid() because we need to call
304 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
305 */
306
307 statusp = 1;
308 child_pid = fork();
309 switch (child_pid) {
310 default:
311 while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
312 if (WIFSTOPPED(statusp)) {
313 child_pgrp = tcgetpgrp(1);
314 kill(getpid(), SIGSTOP);
315 tcsetpgrp(1, child_pgrp);
316 kill(child_pid, SIGCONT);
317 statusp = 1;
318 continue;
319 }
320 break;
321 }
322 if (ret_pid == -1)
323 err(1, "waitpid");
324 PAM_END();
325 exit(WEXITSTATUS(statusp));
326 case -1:
327 err(1, "fork");
328 PAM_END();
329 exit(1);
330 case 0:
331 if( setgid(pwd->pw_gid) )
332 err(1, "setgid");
333 if( setuid(pwd->pw_uid) )
334 err(1, "setuid");
335
336 if (!asme) {
337 if (asthem) {
338 p = getenv("TERM");
339 *environ = NULL;
340
341 /*
342 * Add any environmental variables that the
343 * PAM modules may have set.
344 */
345 environ_pam = pam_getenvlist(pamh);
346 if (environ_pam)
347 export_pam_environment();
348
349 if (p)
350 setenv("TERM", p, 1);
351 if (chdir(pwd->pw_dir) < 0)
352 errx(1, "no directory");
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
360 if (iscsh == YES) {
361 if (fastlogin)
362 *np.a-- = "-f";
363 if (asme)
364 *np.a-- = "-m";
365 }
366 /* csh *no longer* strips the first character... */
367 *np.a = asthem ? "-su" : "su";
368
369 if (ruid != 0)
370 syslog(LOG_NOTICE, "%s to %s%s", username, user,
371 ontty());
372
373 execv(shell, np.b);
374 err(1, "%s", shell);
375 }
376 }
377
378 static int
379 export_pam_environment(void)
380 {
381 char **pp;
382
383 for (pp = environ_pam; *pp != NULL; pp++) {
384 if (ok_to_export(*pp))
385 putenv(*pp);
386 free(*pp);
387 }
388 return PAM_SUCCESS;
389 }
390
391 /*
392 * Sanity checks on PAM environmental variables:
393 * - Make sure there is an '=' in the string.
394 * - Make sure the string doesn't run on too long.
395 * - Do not export certain variables. This list was taken from the
396 * Solaris pam_putenv(3) man page.
397 */
398 static int
399 ok_to_export(const char *s)
400 {
401 static const char *noexport[] = {
402 "SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH",
403 "IFS", "PATH", NULL
404 };
405 const char **pp;
406 size_t n;
407
408 if (strlen(s) > 1024 || strchr(s, '=') == NULL)
409 return 0;
410 if (strncmp(s, "LD_", 3) == 0)
411 return 0;
412 for (pp = noexport; *pp != NULL; pp++) {
413 n = strlen(*pp);
414 if (s[n] == '=' && strncmp(s, *pp, n) == 0)
415 return 0;
416 }
417 return 1;
418 }
419
420 static void
421 usage(void)
422 {
423
424 fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
425 exit(1);
426 }
427
428 static int
429 chshell(char *sh)
430 {
431 int r;
432 char *cp;
433
434 r = 0;
435 setusershell();
436 do {
437 cp = getusershell();
438 r = strcmp(cp, sh);
439 } while (!r && cp != NULL);
440 endusershell();
441 return r;
442 }
443
444 static char *
445 ontty(void)
446 {
447 char *p;
448 static char buf[MAXPATHLEN + 4];
449
450 buf[0] = 0;
451 p = ttyname(STDERR_FILENO);
452 if (p)
453 snprintf(buf, sizeof(buf), " on %s", p);
454 return buf;
455 }