]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/su/su.c
This commit was generated by cvs2svn to compensate for changes in r151937,
[FreeBSD/FreeBSD.git] / usr.bin / su / su.c
1 /*
2  * Copyright (c) 2002, 2005 Networks Associates Technologies, Inc.
3  * All rights reserved.
4  *
5  * Portions of this software were developed for the FreeBSD Project by
6  * ThinkSec AS and NAI Labs, the Security Research Division of Network
7  * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
8  * ("CBOSS"), as part of the DARPA CHATS research program.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 /*-
32  * Copyright (c) 1988, 1993, 1994
33  *      The Regents of the University of California.  All rights reserved.
34  *
35  * Redistribution and use in source and binary forms, with or without
36  * modification, are permitted provided that the following conditions
37  * are met:
38  * 1. Redistributions of source code must retain the above copyright
39  *    notice, this list of conditions and the following disclaimer.
40  * 2. Redistributions in binary form must reproduce the above copyright
41  *    notice, this list of conditions and the following disclaimer in the
42  *    documentation and/or other materials provided with the distribution.
43  * 3. All advertising materials mentioning features or use of this software
44  *    must display the following acknowledgement:
45  *      This product includes software developed by the University of
46  *      California, Berkeley and its contributors.
47  * 4. Neither the name of the University nor the names of its contributors
48  *    may be used to endorse or promote products derived from this software
49  *    without specific prior written permission.
50  *
51  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
52  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
53  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
54  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
55  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
56  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
57  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
58  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
59  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
60  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
61  * SUCH DAMAGE.
62  */
63
64 #ifndef lint
65 static const char copyright[] =
66 "@(#) Copyright (c) 1988, 1993, 1994\n\
67         The Regents of the University of California.  All rights reserved.\n";
68 #endif /* not lint */
69
70 #if 0
71 #ifndef lint
72 static char sccsid[] = "@(#)su.c        8.3 (Berkeley) 4/2/94";
73 #endif /* not lint */
74 #endif
75
76 #include <sys/cdefs.h>
77 __FBSDID("$FreeBSD$");
78
79 #include <sys/param.h>
80 #include <sys/time.h>
81 #include <sys/resource.h>
82 #include <sys/wait.h>
83
84 #include <err.h>
85 #include <errno.h>
86 #include <grp.h>
87 #include <login_cap.h>
88 #include <paths.h>
89 #include <pwd.h>
90 #include <signal.h>
91 #include <stdio.h>
92 #include <stdlib.h>
93 #include <string.h>
94 #include <syslog.h>
95 #include <unistd.h>
96
97 #include <security/pam_appl.h>
98 #include <security/openpam.h>
99
100 #define PAM_END() do {                                                  \
101         int local_ret;                                                  \
102         if (pamh != NULL) {                                             \
103                 local_ret = pam_setcred(pamh, PAM_DELETE_CRED);         \
104                 if (local_ret != PAM_SUCCESS)                           \
105                         syslog(LOG_ERR, "pam_setcred: %s",              \
106                                 pam_strerror(pamh, local_ret));         \
107                 if (asthem) {                                           \
108                         local_ret = pam_close_session(pamh, 0);         \
109                         if (local_ret != PAM_SUCCESS)                   \
110                                 syslog(LOG_ERR, "pam_close_session: %s",\
111                                         pam_strerror(pamh, local_ret)); \
112                 }                                                       \
113                 local_ret = pam_end(pamh, local_ret);                   \
114                 if (local_ret != PAM_SUCCESS)                           \
115                         syslog(LOG_ERR, "pam_end: %s",                  \
116                                 pam_strerror(pamh, local_ret));         \
117         }                                                               \
118 } while (0)
119
120
121 #define PAM_SET_ITEM(what, item) do {                                   \
122         int local_ret;                                                  \
123         local_ret = pam_set_item(pamh, what, item);                     \
124         if (local_ret != PAM_SUCCESS) {                                 \
125                 syslog(LOG_ERR, "pam_set_item(" #what "): %s",          \
126                         pam_strerror(pamh, local_ret));                 \
127                 errx(1, "pam_set_item(" #what "): %s",                  \
128                         pam_strerror(pamh, local_ret));                 \
129                 /* NOTREACHED */                                        \
130         }                                                               \
131 } while (0)
132
133 enum tristate { UNSET, YES, NO };
134
135 static pam_handle_t *pamh = NULL;
136 static char     **environ_pam;
137
138 static char     *ontty(void);
139 static int      chshell(const char *);
140 static void     usage(void) __dead2;
141 static void     export_pam_environment(void);
142 static int      ok_to_export(const char *);
143
144 extern char     **environ;
145
146 int
147 main(int argc, char *argv[])
148 {
149         static char     *cleanenv;
150         struct passwd   *pwd;
151         struct pam_conv conv = { openpam_ttyconv, NULL };
152         enum tristate   iscsh;
153         login_cap_t     *lc;
154         union {
155                 const char      **a;
156                 char            * const *b;
157         }               np;
158         uid_t           ruid;
159         pid_t           child_pid, child_pgrp, pid;
160         int             asme, ch, asthem, fastlogin, prio, i, retcode,
161                         statusp, setmaclabel;
162         u_int           setwhat;
163         char            *username, *class, shellbuf[MAXPATHLEN];
164         const char      *p, *user, *shell, *mytty, **nargv;
165         struct sigaction sa, sa_int, sa_quit, sa_pipe;
166         int temp, fds[2];
167
168         shell = class = cleanenv = NULL;
169         asme = asthem = fastlogin = statusp = 0;
170         user = "root";
171         iscsh = UNSET;
172         setmaclabel = 0;
173
174         while ((ch = getopt(argc, argv, "-flmsc:")) != -1)
175                 switch ((char)ch) {
176                 case 'f':
177                         fastlogin = 1;
178                         break;
179                 case '-':
180                 case 'l':
181                         asme = 0;
182                         asthem = 1;
183                         break;
184                 case 'm':
185                         asme = 1;
186                         asthem = 0;
187                         break;
188                 case 's':
189                         setmaclabel = 1;
190                         break;
191                 case 'c':
192                         class = optarg;
193                         break;
194                 case '?':
195                 default:
196                         usage();
197                 /* NOTREACHED */
198                 }
199
200         if (optind < argc)
201                 user = argv[optind++];
202
203         if (user == NULL)
204                 usage();
205         /* NOTREACHED */
206
207         if (strlen(user) > MAXLOGNAME - 1)
208                 errx(1, "username too long");
209
210         /*
211          * Try to provide more helpful debugging output if su(1) is running
212          * non-setuid, or was run from a file system not mounted setuid.
213          */
214         if (geteuid() != 0)
215                 errx(1, "not running setuid");
216
217         nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
218         if (nargv == NULL)
219                 errx(1, "malloc failure");
220
221         nargv[argc + 3] = NULL;
222         for (i = argc; i >= optind; i--)
223                 nargv[i + 3] = argv[i];
224         np.a = &nargv[i + 3];
225
226         argv += optind;
227
228         errno = 0;
229         prio = getpriority(PRIO_PROCESS, 0);
230         if (errno)
231                 prio = 0;
232
233         setpriority(PRIO_PROCESS, 0, -2);
234         openlog("su", LOG_CONS, LOG_AUTH);
235
236         /* get current login name, real uid and shell */
237         ruid = getuid();
238         username = getlogin();
239         pwd = getpwnam(username);
240         if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
241                 pwd = getpwuid(ruid);
242         if (pwd == NULL)
243                 errx(1, "who are you?");
244
245         username = strdup(pwd->pw_name);
246         if (username == NULL)
247                 err(1, "strdup failure");
248
249         if (asme) {
250                 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
251                         /* must copy - pwd memory is recycled */
252                         shell = strncpy(shellbuf, pwd->pw_shell,
253                             sizeof(shellbuf));
254                         shellbuf[sizeof(shellbuf) - 1] = '\0';
255                 }
256                 else {
257                         shell = _PATH_BSHELL;
258                         iscsh = NO;
259                 }
260         }
261
262         /* Do the whole PAM startup thing */
263         retcode = pam_start("su", user, &conv, &pamh);
264         if (retcode != PAM_SUCCESS) {
265                 syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
266                 errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
267         }
268
269         PAM_SET_ITEM(PAM_RUSER, username);
270
271         mytty = ttyname(STDERR_FILENO);
272         if (!mytty)
273                 mytty = "tty";
274         PAM_SET_ITEM(PAM_TTY, mytty);
275
276         retcode = pam_authenticate(pamh, 0);
277         if (retcode != PAM_SUCCESS) {
278                 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
279                     username, user, mytty);
280                 errx(1, "Sorry");
281         }
282         retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
283         if (retcode == PAM_SUCCESS)
284                 user = p;
285         else
286                 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
287                     pam_strerror(pamh, retcode));
288         pwd = getpwnam(user);
289         if (pwd == NULL)
290                 errx(1, "unknown login: %s", user);
291
292         retcode = pam_acct_mgmt(pamh, 0);
293         if (retcode == PAM_NEW_AUTHTOK_REQD) {
294                 retcode = pam_chauthtok(pamh,
295                         PAM_CHANGE_EXPIRED_AUTHTOK);
296                 if (retcode != PAM_SUCCESS) {
297                         syslog(LOG_ERR, "pam_chauthtok: %s",
298                             pam_strerror(pamh, retcode));
299                         errx(1, "Sorry");
300                 }
301         }
302         if (retcode != PAM_SUCCESS) {
303                 syslog(LOG_ERR, "pam_acct_mgmt: %s",
304                         pam_strerror(pamh, retcode));
305                 errx(1, "Sorry");
306         }
307
308         /* get target login information */
309         if (class == NULL)
310                 lc = login_getpwclass(pwd);
311         else {
312                 if (ruid != 0)
313                         errx(1, "only root may use -c");
314                 lc = login_getclass(class);
315                 if (lc == NULL)
316                         errx(1, "unknown class: %s", class);
317         }
318
319         /* if asme and non-standard target shell, must be root */
320         if (asme) {
321                 if (ruid != 0 && !chshell(pwd->pw_shell))
322                         errx(1, "permission denied (shell)");
323         }
324         else if (pwd->pw_shell && *pwd->pw_shell) {
325                 shell = pwd->pw_shell;
326                 iscsh = UNSET;
327         }
328         else {
329                 shell = _PATH_BSHELL;
330                 iscsh = NO;
331         }
332
333         /* if we're forking a csh, we want to slightly muck the args */
334         if (iscsh == UNSET) {
335                 p = strrchr(shell, '/');
336                 if (p)
337                         ++p;
338                 else
339                         p = shell;
340                 iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
341         }
342         setpriority(PRIO_PROCESS, 0, prio);
343
344         /* Switch to home directory */
345         if (asthem) {
346                 if (chdir(pwd->pw_dir) < 0)
347                         errx(1, "no directory");
348         }
349
350         /*
351          * PAM modules might add supplementary groups in pam_setcred(), so
352          * initialize them first.
353          */
354         if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
355                 err(1, "setusercontext");
356
357         retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
358         if (retcode != PAM_SUCCESS) {
359                 syslog(LOG_ERR, "pam_setcred: %s",
360                     pam_strerror(pamh, retcode));
361                 errx(1, "failed to establish credentials.");
362         }
363         if (asthem) {
364                 retcode = pam_open_session(pamh, 0);
365                 if (retcode != PAM_SUCCESS) {
366                         syslog(LOG_ERR, "pam_open_session: %s",
367                             pam_strerror(pamh, retcode));
368                         errx(1, "failed to open session.");
369                 }
370         }
371
372         /*
373          * We must fork() before setuid() because we need to call
374          * pam_setcred(pamh, PAM_DELETE_CRED) as root.
375          */
376         sa.sa_flags = SA_RESTART;
377         sa.sa_handler = SIG_IGN;
378         sigemptyset(&sa.sa_mask);
379         sigaction(SIGINT, &sa, &sa_int);
380         sigaction(SIGQUIT, &sa, &sa_quit);
381         sigaction(SIGPIPE, &sa, &sa_pipe);
382         sa.sa_handler = SIG_DFL;
383         sigaction(SIGTSTP, &sa, NULL);
384         statusp = 1;
385         if (pipe(fds) == -1) {
386                 PAM_END();
387                 err(1, "pipe");
388         }
389         child_pid = fork();
390         switch (child_pid) {
391         default:
392                 sa.sa_handler = SIG_IGN;
393                 sigaction(SIGTTOU, &sa, NULL);
394                 close(fds[0]);
395                 setpgid(child_pid, child_pid);
396                 tcsetpgrp(STDERR_FILENO, child_pid);
397                 close(fds[1]);
398                 sigaction(SIGPIPE, &sa_pipe, NULL);
399                 while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
400                         if (WIFSTOPPED(statusp)) {
401                                 kill(getpid(), SIGSTOP);
402                                 child_pgrp = getpgid(child_pid);
403                                 tcsetpgrp(1, child_pgrp);
404                                 kill(child_pid, SIGCONT);
405                                 statusp = 1;
406                                 continue;
407                         }
408                         break;
409                 }
410                 tcsetpgrp(STDERR_FILENO, getpgrp());
411                 if (pid == -1)
412                         err(1, "waitpid");
413                 PAM_END();
414                 exit(WEXITSTATUS(statusp));
415         case -1:
416                 PAM_END();
417                 err(1, "fork");
418         case 0:
419                 close(fds[1]);
420                 read(fds[0], &temp, 1);
421                 close(fds[0]);
422                 sigaction(SIGPIPE, &sa_pipe, NULL);
423                 sigaction(SIGINT, &sa_int, NULL);
424                 sigaction(SIGQUIT, &sa_quit, NULL);
425
426                 /*
427                  * Set all user context except for: Environmental variables
428                  * Umask Login records (wtmp, etc) Path
429                  */
430                 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
431                            LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
432                            LOGIN_SETMAC);
433                 /*
434                  * If -s is present, also set the MAC label.
435                  */
436                 if (setmaclabel)
437                         setwhat |= LOGIN_SETMAC;
438                 /*
439                  * Don't touch resource/priority settings if -m has been used
440                  * or -l and -c hasn't, and we're not su'ing to root.
441                  */
442                 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
443                         setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
444                 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
445                         err(1, "setusercontext");
446
447                 if (!asme) {
448                         if (asthem) {
449                                 p = getenv("TERM");
450                                 environ = &cleanenv;
451                         }
452
453                         if (asthem || pwd->pw_uid)
454                                 setenv("USER", pwd->pw_name, 1);
455                         setenv("HOME", pwd->pw_dir, 1);
456                         setenv("SHELL", shell, 1);
457
458                         if (asthem) {
459                                 /*
460                                  * Add any environmental variables that the
461                                  * PAM modules may have set.
462                                  */
463                                 environ_pam = pam_getenvlist(pamh);
464                                 if (environ_pam)
465                                         export_pam_environment();
466
467                                 /* set the su'd user's environment & umask */
468                                 setusercontext(lc, pwd, pwd->pw_uid,
469                                         LOGIN_SETPATH | LOGIN_SETUMASK |
470                                         LOGIN_SETENV);
471                                 if (p)
472                                         setenv("TERM", p, 1);
473                         }
474                 }
475                 login_close(lc);
476
477                 if (iscsh == YES) {
478                         if (fastlogin)
479                                 *np.a-- = "-f";
480                         if (asme)
481                                 *np.a-- = "-m";
482                 }
483                 /* csh strips the first character... */
484                 *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
485
486                 if (ruid != 0)
487                         syslog(LOG_NOTICE, "%s to %s%s", username, user,
488                             ontty());
489
490                 execv(shell, np.b);
491                 err(1, "%s", shell);
492         }
493 }
494
495 static void
496 export_pam_environment(void)
497 {
498         char    **pp;
499
500         for (pp = environ_pam; *pp != NULL; pp++) {
501                 if (ok_to_export(*pp))
502                         putenv(*pp);
503                 free(*pp);
504         }
505 }
506
507 /*
508  * Sanity checks on PAM environmental variables:
509  * - Make sure there is an '=' in the string.
510  * - Make sure the string doesn't run on too long.
511  * - Do not export certain variables.  This list was taken from the
512  *   Solaris pam_putenv(3) man page.
513  * Note that if the user is chrooted, PAM may have a better idea than we
514  * do of where her home directory is.
515  */
516 static int
517 ok_to_export(const char *s)
518 {
519         static const char *noexport[] = {
520                 "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
521                 "IFS", "PATH", NULL
522         };
523         const char **pp;
524         size_t n;
525
526         if (strlen(s) > 1024 || strchr(s, '=') == NULL)
527                 return 0;
528         if (strncmp(s, "LD_", 3) == 0)
529                 return 0;
530         for (pp = noexport; *pp != NULL; pp++) {
531                 n = strlen(*pp);
532                 if (s[n] == '=' && strncmp(s, *pp, n) == 0)
533                         return 0;
534         }
535         return 1;
536 }
537
538 static void
539 usage(void)
540 {
541
542         fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
543         exit(1);
544         /* NOTREACHED */
545 }
546
547 static int
548 chshell(const char *sh)
549 {
550         int r;
551         char *cp;
552
553         r = 0;
554         setusershell();
555         while ((cp = getusershell()) != NULL && !r)
556             r = (strcmp(cp, sh) == 0);
557         endusershell();
558         return r;
559 }
560
561 static char *
562 ontty(void)
563 {
564         char *p;
565         static char buf[MAXPATHLEN + 4];
566
567         buf[0] = 0;
568         p = ttyname(STDERR_FILENO);
569         if (p)
570                 snprintf(buf, sizeof(buf), " on %s", p);
571         return buf;
572 }