1 /* $NetBSD: su_pam.c,v 1.20 2015/08/09 09:39:21 shm Exp $ */
4 * Copyright (c) 1988 The Regents of the University of California.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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
32 #include <sys/cdefs.h>
34 __COPYRIGHT("@(#) Copyright (c) 1988\
35 The Regents of the University of California. All rights reserved.");
40 static char sccsid
[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";*/
42 __RCSID("$NetBSD: su_pam.c,v 1.20 2015/08/09 09:39:21 shm Exp $");
46 #include <sys/param.h>
48 #include <sys/resource.h>
64 #include <login_cap.h>
66 #include <security/pam_appl.h>
67 #include <security/openpam.h> /* for openpam_ttyconv() */
69 #ifdef ALLOW_GROUP_CHANGE
74 static const struct pam_conv pamc
= { &openpam_ttyconv
, NULL
};
76 #define ARGSTRX "-dflm"
79 #define ARGSTR ARGSTRX "c:"
81 #define ARGSTR ARGSTRX
84 static void logit(const char *, ...) __printflike(1, 2);
87 safe_pam_strerror(pam_handle_t
*pamh
, int pam_err
) {
90 if ((msg
= pam_strerror(pamh
, pam_err
)) != NULL
)
93 static char buf
[1024];
94 snprintf(buf
, sizeof(buf
), "Unknown pam error %d", pam_err
);
99 main(int argc
, char **argv
)
101 extern char **environ
;
102 struct passwd
*pwd
, pwres
;
105 int asme
, ch
, asthem
, fastlogin
, prio
, gohome
;
107 enum { UNSET
, YES
, NO
} iscsh
= UNSET
;
108 const char *user
, *shell
, *avshell
;
109 char *username
, *class;
111 char shellbuf
[MAXPATHLEN
], avshellbuf
[MAXPATHLEN
];
113 char hostname
[MAXHOSTNAMELEN
];
118 pam_handle_t
*pamh
= NULL
;
121 extern int _openpam_debug
;
125 #ifdef ALLOW_GROUP_CHANGE
129 (void)setprogname(argv
[0]);
130 asme
= asthem
= fastlogin
= 0;
132 shell
= class = NULL
;
133 while ((ch
= getopt(argc
, argv
, ARGSTR
)) != -1)
157 (void)fprintf(stderr
,
158 #ifdef ALLOW_GROUP_CHANGE
159 "Usage: %s [%s] [login[:group] [shell arguments]]\n",
161 "Usage: %s [%s] [login [shell arguments]]\n",
163 getprogname(), ARGSTR
);
168 /* Lower the priority so su runs faster */
170 prio
= getpriority(PRIO_PROCESS
, 0);
174 (void)setpriority(PRIO_PROCESS
, 0, -2);
175 openlog("su", 0, LOG_AUTH
);
177 /* get current login name and shell */
179 username
= getlogin();
180 if (username
== NULL
||
181 getpwnam_r(username
, &pwres
, pwbuf
, sizeof(pwbuf
), &pwd
) != 0 ||
182 pwd
== NULL
|| pwd
->pw_uid
!= ruid
) {
183 if (getpwuid_r(ruid
, &pwres
, pwbuf
, sizeof(pwbuf
), &pwd
) != 0)
187 errx(EXIT_FAILURE
, "who are you?");
188 username
= estrdup(pwd
->pw_name
);
191 if (pwd
->pw_shell
&& *pwd
->pw_shell
) {
192 (void)estrlcpy(shellbuf
, pwd
->pw_shell
, sizeof(shellbuf
));
195 shell
= _PATH_BSHELL
;
199 /* get target login information, default to root */
200 user
= *argv
? *argv
: "root";
201 np
= *argv
? argv
: argv
- 1;
203 #ifdef ALLOW_GROUP_CHANGE
204 if ((p
= strchr(user
, ':')) != NULL
) {
211 #ifdef ALLOW_EMPTY_USER
216 if (getpwnam_r(user
, &pwres
, pwbuf
, sizeof(pwbuf
), &pwd
) != 0 ||
218 errx(EXIT_FAILURE
, "unknown login %s", user
);
223 #define PAM_END(msg) do { func = msg; goto done;} /* NOTREACHED */ while (/*CONSTCOND*/0)
225 if ((pam_err
= pam_start("su", user
, &pamc
, &pamh
)) != PAM_SUCCESS
) {
227 PAM_END("pam_start");
228 /* Things went really bad... */
229 syslog(LOG_ERR
, "pam_start failed: %s",
230 safe_pam_strerror(pamh
, pam_err
));
231 errx(EXIT_FAILURE
, "pam_start failed");
234 #define PAM_END_ITEM(item) PAM_END("pam_set_item(" # item ")")
235 #define PAM_SET_ITEM(item, var) \
236 if ((pam_err = pam_set_item(pamh, (item), (var))) != PAM_SUCCESS) \
240 * Fill hostname, username and tty
242 PAM_SET_ITEM(PAM_RUSER
, username
);
243 if (gethostname(hostname
, sizeof(hostname
)) != -1)
244 PAM_SET_ITEM(PAM_RHOST
, hostname
);
246 if ((tty
= ttyname(STDERR_FILENO
)) != NULL
)
247 PAM_SET_ITEM(PAM_TTY
, tty
);
252 if ((pam_err
= pam_authenticate(pamh
, 0)) != PAM_SUCCESS
) {
253 syslog(LOG_WARNING
, "BAD SU %s to %s%s: %s",
254 username
, user
, ontty(), safe_pam_strerror(pamh
, pam_err
));
255 (void)pam_end(pamh
, pam_err
);
256 errx(EXIT_FAILURE
, "Sorry: %s", safe_pam_strerror(NULL
, pam_err
));
262 switch(pam_err
= pam_acct_mgmt(pamh
, 0)) {
263 case PAM_NEW_AUTHTOK_REQD
:
264 pam_err
= pam_chauthtok(pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
265 if (pam_err
!= PAM_SUCCESS
)
266 PAM_END("pam_chauthok");
271 PAM_END("pam_acct_mgmt");
276 * pam_authenticate might have changed the target user.
277 * refresh pwd and user
279 pam_err
= pam_get_item(pamh
, PAM_USER
, &newuser
);
280 if (pam_err
!= PAM_SUCCESS
) {
282 "pam_get_item(PAM_USER): %s", safe_pam_strerror(pamh
, pam_err
));
284 user
= (char *)__UNCONST(newuser
);
285 if (getpwnam_r(user
, &pwres
, pwbuf
, sizeof(pwbuf
), &pwd
) != 0 ||
287 (void)pam_end(pamh
, pam_err
);
288 syslog(LOG_ERR
, "unknown login: %s", username
);
289 errx(EXIT_FAILURE
, "unknown login: %s", username
);
293 #define ERRX_PAM_END(args) do { \
294 (void)pam_end(pamh, pam_err); \
296 } while (/* CONSTCOND */0)
298 #define ERR_PAM_END(args) do { \
299 (void)pam_end(pamh, pam_err); \
301 } while (/* CONSTCOND */0)
303 /* force the usage of specified class */
306 ERRX_PAM_END((EXIT_FAILURE
, "Only root may use -c"));
308 pwd
->pw_class
= class;
311 if ((lc
= login_getclass(pwd
->pw_class
)) == NULL
)
312 ERRX_PAM_END((EXIT_FAILURE
,
313 "Unknown class %s\n", pwd
->pw_class
));
316 /* if asme and non-standard target shell, must be root */
317 if (chshell(pwd
->pw_shell
) == 0 && ruid
)
318 ERRX_PAM_END((EXIT_FAILURE
,
319 "permission denied (shell)."));
320 } else if (pwd
->pw_shell
&& *pwd
->pw_shell
) {
321 shell
= pwd
->pw_shell
;
324 shell
= _PATH_BSHELL
;
328 if ((p
= strrchr(shell
, '/')) != NULL
)
333 /* if we're forking a csh, we want to slightly muck the args */
335 iscsh
= strstr(avshell
, "csh") ? YES
: NO
;
338 * Initialize the supplemental groups before pam gets to them,
339 * so that other pam modules get a chance to add more when
340 * we do setcred. Note, we don't relinguish our set-userid yet
342 /* if we aren't changing users, keep the current group members */
343 if (ruid
!= pwd
->pw_uid
&&
344 setusercontext(lc
, pwd
, pwd
->pw_uid
, LOGIN_SETGROUP
) == -1)
345 ERR_PAM_END((EXIT_FAILURE
, "setting user context"));
347 #ifdef ALLOW_GROUP_CHANGE
348 addgroup(lc
, gname
, pwd
, ruid
, "Group Password:");
350 if ((pam_err
= pam_setcred(pamh
, PAM_ESTABLISH_CRED
)) != PAM_SUCCESS
)
351 PAM_END("pam_setcred");
359 struct sigaction sa
, sa_int
, sa_pipe
, sa_quit
;
362 if ((pam_err
= pam_open_session(pamh
, 0)) != PAM_SUCCESS
)
363 PAM_END("pam_open_session");
366 * In order to call pam_close_session after the
367 * command terminates, we need to fork.
369 sa
.sa_flags
= SA_RESTART
;
370 sa
.sa_handler
= SIG_IGN
;
371 (void)sigemptyset(&sa
.sa_mask
);
372 (void)sigaction(SIGINT
, &sa
, &sa_int
);
373 (void)sigaction(SIGQUIT
, &sa
, &sa_quit
);
374 (void)sigaction(SIGPIPE
, &sa
, &sa_pipe
);
375 sa
.sa_handler
= SIG_DFL
;
376 (void)sigaction(SIGTSTP
, &sa
, NULL
);
378 * Use a pipe to guarantee the order of execution of
379 * the parent and the child.
381 if (pipe(fds
) == -1) {
386 switch (pid
= fork()) {
388 logit("fork failed (%s)", strerror(errno
));
393 (void)read(fds
[0], &status
, 1);
395 (void)sigaction(SIGINT
, &sa_int
, NULL
);
396 (void)sigaction(SIGQUIT
, &sa_quit
, NULL
);
397 (void)sigaction(SIGPIPE
, &sa_pipe
, NULL
);
401 sa
.sa_handler
= SIG_IGN
;
402 (void)sigaction(SIGTTOU
, &sa
, NULL
);
404 (void)setpgid(pid
, pid
);
405 (void)tcsetpgrp(STDERR_FILENO
, pid
);
407 (void)sigaction(SIGPIPE
, &sa_pipe
, NULL
);
409 * Parent: wait for the child to terminate
410 * and call pam_close_session.
412 while ((xpid
= waitpid(pid
, &status
, WUNTRACED
))
414 if (WIFSTOPPED(status
)) {
415 (void)kill(getpid(), SIGSTOP
);
416 (void)tcsetpgrp(STDERR_FILENO
,
418 (void)kill(pid
, SIGCONT
);
425 (void)tcsetpgrp(STDERR_FILENO
, getpgid(0));
428 logit("Error waiting for pid %d (%s)", pid
,
430 } else if (xpid
!= pid
) {
432 logit("Wrong PID: %d != %d", pid
, xpid
);
435 pam_err
= pam_setcred(pamh
, PAM_DELETE_CRED
);
436 if (pam_err
!= PAM_SUCCESS
)
437 logit("pam_setcred: %s",
438 safe_pam_strerror(pamh
, pam_err
));
439 pam_err
= pam_close_session(pamh
, 0);
440 if (pam_err
!= PAM_SUCCESS
)
441 logit("pam_close_session: %s",
442 safe_pam_strerror(pamh
, pam_err
));
443 (void)pam_end(pamh
, pam_err
);
444 exit(WEXITSTATUS(status
));
450 * The child: starting here, we don't have to care about
451 * handling PAM issues if we exit, the parent will do the
464 * Create an empty environment
466 environ
= emalloc(sizeof(char *));
470 * Add PAM environement, before the LOGIN_CAP stuff:
471 * if the login class is unspecified, we'll get the
472 * same data from PAM, if -c was used, the specified
473 * class must override PAM.
475 if ((pamenv
= pam_getenvlist(pamh
)) != NULL
) {
479 * XXX Here FreeBSD filters out
480 * SHELL, LOGNAME, MAIL, CDPATH, IFS, PATH
481 * how could we get untrusted data here?
483 for (envitem
= pamenv
; *envitem
; envitem
++) {
484 if (putenv(*envitem
) == -1)
491 if (setusercontext(lc
, pwd
, pwd
->pw_uid
, LOGIN_SETPATH
|
492 LOGIN_SETENV
| LOGIN_SETUMASK
) == -1)
493 err(EXIT_FAILURE
, "setting user context");
495 (void)setenv("TERM", p
, 1);
498 if (asthem
|| pwd
->pw_uid
) {
499 (void)setenv("LOGNAME", pwd
->pw_name
, 1);
500 (void)setenv("USER", pwd
->pw_name
, 1);
502 (void)setenv("HOME", pwd
->pw_dir
, 1);
503 (void)setenv("SHELL", shell
, 1);
505 (void)setenv("SU_FROM", username
, 1);
509 *np
-- = __UNCONST("-f");
511 *np
-- = __UNCONST("-m");
514 (void)unsetenv("ENV");
519 (void)estrlcpy(avshellbuf
+ 1, avshell
, sizeof(avshellbuf
) - 1);
520 avshell
= avshellbuf
;
521 } else if (iscsh
== YES
) {
522 /* csh strips the first character... */
524 (void)estrlcpy(avshellbuf
+ 1, avshell
, sizeof(avshellbuf
) - 1);
525 avshell
= avshellbuf
;
527 *np
= __UNCONST(avshell
);
530 syslog(LOG_NOTICE
, "%s to %s%s",
531 username
, pwd
->pw_name
, ontty());
533 /* Raise our priority back to what we had before */
534 (void)setpriority(PRIO_PROCESS
, 0, prio
);
537 * Set user context, except for umask, and the stuff
538 * we have done before.
540 setwhat
= LOGIN_SETALL
& ~(LOGIN_SETENV
| LOGIN_SETUMASK
|
541 LOGIN_SETLOGIN
| LOGIN_SETPATH
| LOGIN_SETGROUP
);
544 * Don't touch resource/priority settings if -m has been used
545 * or -l and -c hasn't, and we're not su'ing to root.
547 if ((asme
|| (!asthem
&& class == NULL
)) && pwd
->pw_uid
)
548 setwhat
&= ~(LOGIN_SETPRIORITY
| LOGIN_SETRESOURCES
);
550 if (setusercontext(lc
, pwd
, pwd
->pw_uid
, setwhat
) == -1)
551 err(EXIT_FAILURE
, "setusercontext");
555 if (gohome
&& chdir(pwd
->pw_dir
) == -1)
556 errx(EXIT_FAILURE
, "no directory");
560 (void)execv(shell
, np
);
561 err(EXIT_FAILURE
, "%s", shell
);
563 logit("%s: %s", func
, safe_pam_strerror(pamh
, pam_err
));
564 (void)pam_end(pamh
, pam_err
);
569 logit(const char *fmt
, ...)
577 vsyslog(LOG_ERR
, fmt
, ap
);