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