1 /* $NetBSD: su_pam.c,v 1.14 2008/04/05 15:59:39 christos 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.14 2008/04/05 15:59:39 christos 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 *, ...);
87 main(int argc
, char **argv
)
89 extern char **environ
;
90 struct passwd
*pwd
, pwres
;
93 int asme
, ch
, asthem
, fastlogin
, prio
, gohome
;
95 enum { UNSET
, YES
, NO
} iscsh
= UNSET
;
96 const char *user
, *shell
, *avshell
;
97 char *username
, *class;
99 char shellbuf
[MAXPATHLEN
], avshellbuf
[MAXPATHLEN
];
101 char hostname
[MAXHOSTNAMELEN
];
106 pam_handle_t
*pamh
= NULL
;
109 extern int _openpam_debug
;
113 #ifdef ALLOW_GROUP_CHANGE
117 (void)setprogname(argv
[0]);
118 asme
= asthem
= fastlogin
= 0;
120 shell
= class = NULL
;
121 while ((ch
= getopt(argc
, argv
, ARGSTR
)) != -1)
145 (void)fprintf(stderr
,
146 #ifdef ALLOW_GROUP_CHANGE
147 "Usage: %s [%s] [login[:group] [shell arguments]]\n",
149 "Usage: %s [%s] [login [shell arguments]]\n",
151 getprogname(), ARGSTR
);
156 /* Lower the priority so su runs faster */
158 prio
= getpriority(PRIO_PROCESS
, 0);
162 (void)setpriority(PRIO_PROCESS
, 0, -2);
163 openlog("su", 0, LOG_AUTH
);
165 /* get current login name and shell */
167 username
= getlogin();
168 if (username
== NULL
||
169 getpwnam_r(username
, &pwres
, pwbuf
, sizeof(pwbuf
), &pwd
) != 0 ||
170 pwd
== NULL
|| pwd
->pw_uid
!= ruid
) {
171 if (getpwuid_r(ruid
, &pwres
, pwbuf
, sizeof(pwbuf
), &pwd
) != 0)
175 errx(EXIT_FAILURE
, "who are you?");
176 username
= estrdup(pwd
->pw_name
);
179 if (pwd
->pw_shell
&& *pwd
->pw_shell
) {
180 (void)estrlcpy(shellbuf
, pwd
->pw_shell
, sizeof(shellbuf
));
183 shell
= _PATH_BSHELL
;
187 /* get target login information, default to root */
188 user
= *argv
? *argv
: "root";
189 np
= *argv
? argv
: argv
- 1;
191 #ifdef ALLOW_GROUP_CHANGE
192 if ((p
= strchr(user
, ':')) != NULL
) {
199 #ifdef ALLOW_EMPTY_USER
204 if (getpwnam_r(user
, &pwres
, pwbuf
, sizeof(pwbuf
), &pwd
) != 0 ||
206 errx(EXIT_FAILURE
, "unknown login %s", user
);
211 #define PAM_END(msg) do { func = msg; goto done;} /* NOTREACHED */ while (/*CONSTCOND*/0)
213 if ((pam_err
= pam_start("su", user
, &pamc
, &pamh
)) != PAM_SUCCESS
) {
215 PAM_END("pam_start");
216 /* Things went really bad... */
217 syslog(LOG_ERR
, "pam_start failed: %s",
218 pam_strerror(pamh
, pam_err
));
219 errx(EXIT_FAILURE
, "pam_start failed");
222 #define PAM_END_ITEM(item) PAM_END("pam_set_item(" # item ")")
223 #define PAM_SET_ITEM(item, var) \
224 if ((pam_err = pam_set_item(pamh, (item), (var))) != PAM_SUCCESS) \
228 * Fill hostname, username and tty
230 PAM_SET_ITEM(PAM_RUSER
, username
);
231 if (gethostname(hostname
, sizeof(hostname
)) != -1)
232 PAM_SET_ITEM(PAM_RHOST
, hostname
);
234 if ((tty
= ttyname(STDERR_FILENO
)) != NULL
)
235 PAM_SET_ITEM(PAM_TTY
, tty
);
240 if ((pam_err
= pam_authenticate(pamh
, 0)) != PAM_SUCCESS
) {
241 syslog(LOG_WARNING
, "BAD SU %s to %s%s: %s",
242 username
, user
, ontty(), pam_strerror(pamh
, pam_err
));
243 (void)pam_end(pamh
, pam_err
);
244 errx(EXIT_FAILURE
, "Sorry: %s", pam_strerror(pamh
, pam_err
));
250 switch(pam_err
= pam_acct_mgmt(pamh
, 0)) {
251 case PAM_NEW_AUTHTOK_REQD
:
252 pam_err
= pam_chauthtok(pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
253 if (pam_err
!= PAM_SUCCESS
)
254 PAM_END("pam_chauthok");
259 PAM_END("pam_acct_mgmt");
264 * pam_authenticate might have changed the target user.
265 * refresh pwd and user
267 pam_err
= pam_get_item(pamh
, PAM_USER
, &newuser
);
268 if (pam_err
!= PAM_SUCCESS
) {
270 "pam_get_item(PAM_USER): %s", pam_strerror(pamh
, pam_err
));
272 user
= (char *)__UNCONST(newuser
);
273 if (getpwnam_r(user
, &pwres
, pwbuf
, sizeof(pwbuf
), &pwd
) != 0 ||
275 (void)pam_end(pamh
, pam_err
);
276 syslog(LOG_ERR
, "unknown login: %s", username
);
277 errx(EXIT_FAILURE
, "unknown login: %s", username
);
281 #define ERRX_PAM_END(args) do { \
282 (void)pam_end(pamh, pam_err); \
284 } while (/* CONSTCOND */0)
286 #define ERR_PAM_END(args) do { \
287 (void)pam_end(pamh, pam_err); \
289 } while (/* CONSTCOND */0)
291 /* force the usage of specified class */
294 ERRX_PAM_END((EXIT_FAILURE
, "Only root may use -c"));
296 pwd
->pw_class
= class;
299 if ((lc
= login_getclass(pwd
->pw_class
)) == NULL
)
300 ERRX_PAM_END((EXIT_FAILURE
,
301 "Unknown class %s\n", pwd
->pw_class
));
304 /* if asme and non-standard target shell, must be root */
305 if (chshell(pwd
->pw_shell
) == 0 && ruid
)
306 ERRX_PAM_END((EXIT_FAILURE
,
307 "permission denied (shell)."));
308 } else if (pwd
->pw_shell
&& *pwd
->pw_shell
) {
309 shell
= pwd
->pw_shell
;
312 shell
= _PATH_BSHELL
;
316 if ((p
= strrchr(shell
, '/')) != NULL
)
321 /* if we're forking a csh, we want to slightly muck the args */
323 iscsh
= strstr(avshell
, "csh") ? YES
: NO
;
326 * Initialize the supplemental groups before pam gets to them,
327 * so that other pam modules get a chance to add more when
328 * we do setcred. Note, we don't relinguish our set-userid yet
330 /* if we aren't changing users, keep the current group members */
331 if (ruid
!= pwd
->pw_uid
&&
332 setusercontext(lc
, pwd
, pwd
->pw_uid
, LOGIN_SETGROUP
) == -1)
333 ERR_PAM_END((EXIT_FAILURE
, "setting user context"));
335 #ifdef ALLOW_GROUP_CHANGE
336 addgroup(lc
, gname
, pwd
, ruid
, "Group Password:");
338 if ((pam_err
= pam_setcred(pamh
, PAM_ESTABLISH_CRED
)) != PAM_SUCCESS
)
339 PAM_END("pam_setcred");
347 struct sigaction sa
, sa_int
, sa_pipe
, sa_quit
;
350 if ((pam_err
= pam_open_session(pamh
, 0)) != PAM_SUCCESS
)
351 PAM_END("pam_open_session");
354 * In order to call pam_close_session after the
355 * command terminates, we need to fork.
357 sa
.sa_flags
= SA_RESTART
;
358 sa
.sa_handler
= SIG_IGN
;
359 (void)sigemptyset(&sa
.sa_mask
);
360 (void)sigaction(SIGINT
, &sa
, &sa_int
);
361 (void)sigaction(SIGQUIT
, &sa
, &sa_quit
);
362 (void)sigaction(SIGPIPE
, &sa
, &sa_pipe
);
363 sa
.sa_handler
= SIG_DFL
;
364 (void)sigaction(SIGTSTP
, &sa
, NULL
);
366 * Use a pipe to guarantee the order of execution of
367 * the parent and the child.
369 if (pipe(fds
) == -1) {
374 switch (pid
= fork()) {
376 logit("fork failed (%s)", strerror(errno
));
381 (void)read(fds
[0], &status
, 1);
383 (void)sigaction(SIGINT
, &sa_int
, NULL
);
384 (void)sigaction(SIGQUIT
, &sa_quit
, NULL
);
385 (void)sigaction(SIGPIPE
, &sa_pipe
, NULL
);
389 sa
.sa_handler
= SIG_IGN
;
390 (void)sigaction(SIGTTOU
, &sa
, NULL
);
392 (void)setpgid(pid
, pid
);
393 (void)tcsetpgrp(STDERR_FILENO
, pid
);
395 (void)sigaction(SIGPIPE
, &sa_pipe
, NULL
);
397 * Parent: wait for the child to terminate
398 * and call pam_close_session.
400 while ((xpid
= waitpid(pid
, &status
, WUNTRACED
))
402 if (WIFSTOPPED(status
)) {
403 (void)kill(getpid(), SIGSTOP
);
404 (void)tcsetpgrp(STDERR_FILENO
,
406 (void)kill(pid
, SIGCONT
);
413 (void)tcsetpgrp(STDERR_FILENO
, getpgid(0));
416 logit("Error waiting for pid %d (%s)", pid
,
418 } else if (xpid
!= pid
) {
420 logit("Wrong PID: %d != %d", pid
, xpid
);
423 pam_err
= pam_setcred(pamh
, PAM_DELETE_CRED
);
424 if (pam_err
!= PAM_SUCCESS
)
425 logit("pam_setcred: %s",
426 pam_strerror(pamh
, pam_err
));
427 pam_err
= pam_close_session(pamh
, 0);
428 if (pam_err
!= PAM_SUCCESS
)
429 logit("pam_close_session: %s",
430 pam_strerror(pamh
, pam_err
));
431 (void)pam_end(pamh
, pam_err
);
432 exit(WEXITSTATUS(status
));
438 * The child: starting here, we don't have to care about
439 * handling PAM issues if we exit, the parent will do the
452 * Create an empty environment
454 environ
= emalloc(sizeof(char *));
458 * Add PAM environement, before the LOGIN_CAP stuff:
459 * if the login class is unspecified, we'll get the
460 * same data from PAM, if -c was used, the specified
461 * class must override PAM.
463 if ((pamenv
= pam_getenvlist(pamh
)) != NULL
) {
467 * XXX Here FreeBSD filters out
468 * SHELL, LOGNAME, MAIL, CDPATH, IFS, PATH
469 * how could we get untrusted data here?
471 for (envitem
= pamenv
; *envitem
; envitem
++) {
472 (void)putenv(*envitem
);
479 if (setusercontext(lc
, pwd
, pwd
->pw_uid
, LOGIN_SETPATH
|
480 LOGIN_SETENV
| LOGIN_SETUMASK
) == -1)
481 err(EXIT_FAILURE
, "setting user context");
483 (void)setenv("TERM", p
, 1);
484 if (gohome
&& chdir(pwd
->pw_dir
) == -1)
485 errx(EXIT_FAILURE
, "no directory");
488 if (asthem
|| pwd
->pw_uid
) {
489 (void)setenv("LOGNAME", pwd
->pw_name
, 1);
490 (void)setenv("USER", pwd
->pw_name
, 1);
492 (void)setenv("HOME", pwd
->pw_dir
, 1);
493 (void)setenv("SHELL", shell
, 1);
495 (void)setenv("SU_FROM", username
, 1);
499 *np
-- = __UNCONST("-f");
501 *np
-- = __UNCONST("-m");
504 (void)unsetenv("ENV");
509 (void)estrlcpy(avshellbuf
+ 1, avshell
, sizeof(avshellbuf
) - 1);
510 avshell
= avshellbuf
;
511 } else if (iscsh
== YES
) {
512 /* csh strips the first character... */
514 (void)estrlcpy(avshellbuf
+ 1, avshell
, sizeof(avshellbuf
) - 1);
515 avshell
= avshellbuf
;
517 *np
= __UNCONST(avshell
);
520 syslog(LOG_NOTICE
, "%s to %s%s",
521 username
, pwd
->pw_name
, ontty());
523 /* Raise our priority back to what we had before */
524 (void)setpriority(PRIO_PROCESS
, 0, prio
);
527 * Set user context, except for umask, and the stuff
528 * we have done before.
530 setwhat
= LOGIN_SETALL
& ~(LOGIN_SETENV
| LOGIN_SETUMASK
|
531 LOGIN_SETLOGIN
| LOGIN_SETPATH
| LOGIN_SETGROUP
);
534 * Don't touch resource/priority settings if -m has been used
535 * or -l and -c hasn't, and we're not su'ing to root.
537 if ((asme
|| (!asthem
&& class == NULL
)) && pwd
->pw_uid
)
538 setwhat
&= ~(LOGIN_SETPRIORITY
| LOGIN_SETRESOURCES
);
540 if (setusercontext(lc
, pwd
, pwd
->pw_uid
, setwhat
) == -1)
541 err(EXIT_FAILURE
, "setusercontext");
543 (void)execv(shell
, np
);
544 err(EXIT_FAILURE
, "%s", shell
);
546 logit("%s: %s", func
, pam_strerror(pamh
, pam_err
));
547 (void)pam_end(pamh
, pam_err
);
552 logit(const char *fmt
, ...)
558 vsyslog(LOG_ERR
, fmt
, ap
);