su_pam.c revision 1.11 1 /* $NetBSD: su_pam.c,v 1.11 2005/12/15 14:01:31 christos Exp $ */
2
3 /*
4 * Copyright (c) 1988 The Regents of the University of California.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
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.
18 *
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
29 * SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __COPYRIGHT(
35 "@(#) Copyright (c) 1988 The Regents of the University of California.\n\
36 All rights reserved.\n");
37 #endif /* not lint */
38
39 #ifndef lint
40 #if 0
41 static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";*/
42 #else
43 __RCSID("$NetBSD: su_pam.c,v 1.11 2005/12/15 14:01:31 christos Exp $");
44 #endif
45 #endif /* not lint */
46
47 #include <sys/param.h>
48 #include <sys/time.h>
49 #include <sys/resource.h>
50 #include <sys/wait.h>
51 #include <err.h>
52 #include <errno.h>
53 #include <grp.h>
54 #include <paths.h>
55 #include <pwd.h>
56 #include <signal.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <syslog.h>
61 #include <time.h>
62 #include <tzfile.h>
63 #include <unistd.h>
64 #include <login_cap.h>
65
66 #include <security/pam_appl.h>
67 #include <security/openpam.h> /* for openpam_ttyconv() */
68
69 static const struct pam_conv pamc = { &openpam_ttyconv, NULL };
70
71 static void logit(const char *, ...);
72 static int chshell(const char *);
73 static char *ontty(void);
74
75 int main(int, char **);
76
77 #define ARGSTRX "-dflm"
78
79 #ifdef LOGIN_CAP
80 #define ARGSTR ARGSTRX "c:"
81 #else
82 #define ARGSTR ARGSTRX
83 #endif
84
85 int
86 main(int argc, char **argv)
87 {
88 extern char **environ;
89 struct passwd *pwd, pwres;
90 char *p;
91 uid_t ruid;
92 int asme, ch, asthem, fastlogin, prio, gohome, setwhat;
93 enum { UNSET, YES, NO } iscsh = UNSET;
94 char *user, *shell, *avshell, *username, **np;
95 char *class;
96 char shellbuf[MAXPATHLEN], avshellbuf[MAXPATHLEN];
97 int pam_err;
98 char hostname[MAXHOSTNAMELEN];
99 char *tty;
100 const char *func;
101 const void *newuser;
102 login_cap_t *lc;
103 pam_handle_t *pamh = NULL;
104 char pwbuf[1024];
105 #ifdef PAM_DEBUG
106 extern int _openpam_debug;
107
108 _openpam_debug = 1;
109 #endif
110
111 asme = asthem = fastlogin = 0;
112 gohome = 1;
113 shell = class = NULL;
114 while ((ch = getopt(argc, argv, ARGSTR)) != -1)
115 switch((char)ch) {
116 case 'c':
117 class = optarg;
118 break;
119 case 'd':
120 asme = 0;
121 asthem = 1;
122 gohome = 0;
123 break;
124 case 'f':
125 fastlogin = 1;
126 break;
127 case '-':
128 case 'l':
129 asme = 0;
130 asthem = 1;
131 break;
132 case 'm':
133 asme = 1;
134 asthem = 0;
135 break;
136 case '?':
137 default:
138 (void)fprintf(stderr,
139 "Usage: %s [%s] [login [shell arguments]]\n",
140 getprogname(), ARGSTR);
141 exit(EXIT_FAILURE);
142 }
143 argv += optind;
144
145 /* Lower the priority so su runs faster */
146 errno = 0;
147 prio = getpriority(PRIO_PROCESS, 0);
148 if (errno)
149 prio = 0;
150 if (prio > -2)
151 (void)setpriority(PRIO_PROCESS, 0, -2);
152 openlog("su", 0, LOG_AUTH);
153
154 /* get current login name and shell */
155 ruid = getuid();
156 username = getlogin();
157 if (username == NULL ||
158 getpwnam_r(username, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0 ||
159 pwd == NULL || pwd->pw_uid != ruid) {
160 if (getpwuid_r(ruid, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0)
161 pwd = NULL;
162 }
163 if (pwd == NULL)
164 errx(EXIT_FAILURE, "who are you?");
165 if ((username = strdup(pwd->pw_name)) == NULL)
166 err(EXIT_FAILURE, "strdup");
167
168 if (asme) {
169 if (pwd->pw_shell && *pwd->pw_shell) {
170 strlcpy(shellbuf, pwd->pw_shell, sizeof(shellbuf));
171 shell = shellbuf;
172 } else {
173 shell = _PATH_BSHELL;
174 iscsh = NO;
175 }
176 }
177 /* get target login information, default to root */
178 user = *argv ? *argv : "root";
179 np = *argv ? argv : argv - 1;
180
181 if (getpwnam_r(user, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0 ||
182 pwd == NULL)
183 errx(EXIT_FAILURE, "unknown login %s", user);
184
185 /*
186 * PAM initialization
187 */
188 #define PAM_END(msg) do { func = msg; goto done; } while (/*CONSTCOND*/0)
189
190 if ((pam_err = pam_start("su", user, &pamc, &pamh)) != PAM_SUCCESS) {
191 if (pamh != NULL)
192 PAM_END("pam_start");
193 /* Things went really bad... */
194 syslog(LOG_ERR, "pam_start failed: %s",
195 pam_strerror(pamh, pam_err));
196 errx(EXIT_FAILURE, "pam_start failed");
197 }
198
199 #define PAM_END_ITEM(item) PAM_END("pam_set_item(" # item ")")
200 #define PAM_SET_ITEM(item, var) \
201 if ((pam_err = pam_set_item(pamh, (item), (var))) != PAM_SUCCESS) \
202 PAM_END_ITEM(item)
203
204 /*
205 * Fill hostname, username and tty
206 */
207 PAM_SET_ITEM(PAM_RUSER, username);
208 if (gethostname(hostname, sizeof(hostname)) != -1)
209 PAM_SET_ITEM(PAM_RHOST, hostname);
210
211 if ((tty = ttyname(STDERR_FILENO)) != NULL)
212 PAM_SET_ITEM(PAM_TTY, tty);
213
214 /*
215 * Authentication
216 */
217 if ((pam_err = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
218 syslog(LOG_WARNING, "BAD SU %s to %s%s: %s",
219 username, user, ontty(), pam_strerror(pamh, pam_err));
220 pam_end(pamh, pam_err);
221 errx(EXIT_FAILURE, "Sorry: %s", pam_strerror(pamh, pam_err));
222 }
223
224 /*
225 * Authorization
226 */
227 switch(pam_err = pam_acct_mgmt(pamh, 0)) {
228 case PAM_NEW_AUTHTOK_REQD:
229 pam_err = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
230 if (pam_err != PAM_SUCCESS)
231 PAM_END("pam_chauthok");
232 break;
233 case PAM_SUCCESS:
234 break;
235 default:
236 PAM_END("pam_acct_mgmt");
237 break;
238 }
239
240 /*
241 * pam_authenticate might have changed the target user.
242 * refresh pwd and user
243 */
244 pam_err = pam_get_item(pamh, PAM_USER, &newuser);
245 if (pam_err != PAM_SUCCESS) {
246 syslog(LOG_WARNING,
247 "pam_get_item(PAM_USER): %s", pam_strerror(pamh, pam_err));
248 } else {
249 user = (char *)newuser;
250 if (getpwnam_r(user, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0 ||
251 pwd == NULL) {
252 pam_end(pamh, pam_err);
253 syslog(LOG_ERR, "unknown login: %s", username);
254 errx(EXIT_FAILURE, "unknown login: %s", username);
255 }
256 }
257
258 #define ERRX_PAM_END(args) do { \
259 pam_end(pamh, pam_err); \
260 errx args; \
261 } while (/* CONSTOCOND */0)
262
263 #define ERR_PAM_END(args) do { \
264 pam_end(pamh, pam_err); \
265 err args; \
266 } while (/* CONSTOCOND */0)
267
268 /* force the usage of specified class */
269 if (class) {
270 if (ruid)
271 ERRX_PAM_END((EXIT_FAILURE, "Only root may use -c"));
272
273 pwd->pw_class = class;
274 }
275
276 if ((lc = login_getclass(pwd->pw_class)) == NULL)
277 ERRX_PAM_END((EXIT_FAILURE,
278 "Unknown class %s\n", pwd->pw_class));
279
280 if (asme) {
281 /* if asme and non-standard target shell, must be root */
282 if (!chshell(pwd->pw_shell) && ruid)
283 ERRX_PAM_END((EXIT_FAILURE,
284 "permission denied (shell)."));
285 } else if (pwd->pw_shell && *pwd->pw_shell) {
286 shell = pwd->pw_shell;
287 iscsh = UNSET;
288 } else {
289 shell = _PATH_BSHELL;
290 iscsh = NO;
291 }
292
293 if ((p = strrchr(shell, '/')) != NULL)
294 avshell = p + 1;
295 else
296 avshell = shell;
297
298 /* if we're forking a csh, we want to slightly muck the args */
299 if (iscsh == UNSET)
300 iscsh = strstr(avshell, "csh") ? YES : NO;
301
302 /*
303 * Initialize the supplemental groups before pam gets to them,
304 * so that other pam modules get a chance to add more when
305 * we do setcred. Note, we don't relinguish our set-userid yet
306 */
307 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
308 ERR_PAM_END((EXIT_FAILURE, "setting user context"));
309
310 if ((pam_err = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS)
311 PAM_END("pam_setcred");
312
313 /*
314 * Manage session.
315 */
316 if (asthem) {
317 pid_t pid, xpid;
318 int status = 1;
319 struct sigaction sa, sa_int, sa_pipe, sa_quit;
320 int fds[2];
321
322 if ((pam_err = pam_open_session(pamh, 0)) != PAM_SUCCESS)
323 PAM_END("pam_open_session");
324
325 /*
326 * In order to call pam_close_session after the
327 * command terminates, we need to fork.
328 */
329 sa.sa_flags = SA_RESTART;
330 sa.sa_handler = SIG_IGN;
331 sigemptyset(&sa.sa_mask);
332 (void)sigaction(SIGINT, &sa, &sa_int);
333 (void)sigaction(SIGQUIT, &sa, &sa_quit);
334 (void)sigaction(SIGPIPE, &sa, &sa_pipe);
335 sa.sa_handler = SIG_DFL;
336 (void)sigaction(SIGTSTP, &sa, NULL);
337 /*
338 * Use a pipe to guarantee the order of execution of
339 * the parent and the child.
340 */
341 if (pipe(fds) == -1) {
342 warn("pipe failed");
343 goto out;
344 }
345
346 switch (pid = fork()) {
347 case -1:
348 logit("fork failed (%s)", strerror(errno));
349 goto out;
350
351 case 0: /* Child */
352 (void)close(fds[1]);
353 (void)read(fds[0], &status, 1);
354 (void)close(fds[0]);
355 (void)sigaction(SIGINT, &sa_int, NULL);
356 (void)sigaction(SIGQUIT, &sa_quit, NULL);
357 (void)sigaction(SIGPIPE, &sa_pipe, NULL);
358 break;
359
360 default:
361 sa.sa_handler = SIG_IGN;
362 (void)sigaction(SIGTTOU, &sa, NULL);
363 (void)close(fds[0]);
364 (void)setpgid(pid, pid);
365 (void)tcsetpgrp(STDERR_FILENO, pid);
366 (void)close(fds[1]);
367 (void)sigaction(SIGPIPE, &sa_pipe, NULL);
368 /*
369 * Parent: wait for the child to terminate
370 * and call pam_close_session.
371 */
372 while ((xpid = waitpid(pid, &status, WUNTRACED))
373 == pid) {
374 if (WIFSTOPPED(status)) {
375 (void)kill(getpid(), SIGSTOP);
376 (void)tcsetpgrp(STDERR_FILENO,
377 getpgid(pid));
378 (void)kill(pid, SIGCONT);
379 status = 1;
380 continue;
381 }
382 break;
383 }
384
385 (void)tcsetpgrp(STDERR_FILENO, getpgid(0));
386
387 if (xpid == -1) {
388 logit("Error waiting for pid %d (%s)", pid,
389 strerror(errno));
390 } else if (xpid != pid) {
391 /* Can't happen. */
392 logit("Wrong PID: %d != %d", pid, xpid);
393 }
394 out:
395 pam_err = pam_setcred(pamh, PAM_DELETE_CRED);
396 if (pam_err != PAM_SUCCESS)
397 logit("pam_setcred: %s",
398 pam_strerror(pamh, pam_err));
399 pam_err = pam_close_session(pamh, 0);
400 if (pam_err != PAM_SUCCESS)
401 logit("pam_close_session: %s",
402 pam_strerror(pamh, pam_err));
403 pam_end(pamh, pam_err);
404 exit(WEXITSTATUS(status));
405 break;
406 }
407 }
408
409 /*
410 * The child: starting here, we don't have to care about
411 * handling PAM issues if we exit, the parent will do the
412 * job when we exit.
413 */
414 #undef PAM_END
415 #undef ERR_PAM_END
416 #undef ERRX_PAM_END
417
418 if (!asme) {
419 if (asthem) {
420 char **pamenv;
421
422 p = getenv("TERM");
423 /*
424 * Create an empty environment
425 */
426 if ((environ = malloc(sizeof(char *))) == NULL)
427 err(EXIT_FAILURE, NULL);
428 environ[0] = NULL;
429
430 /*
431 * Add PAM environement, before the LOGIN_CAP stuff:
432 * if the login class is unspecified, we'll get the
433 * same data from PAM, if -c was used, the specified
434 * class must override PAM.
435 */
436 if ((pamenv = pam_getenvlist(pamh)) != NULL) {
437 char **envitem;
438
439 /*
440 * XXX Here FreeBSD filters out
441 * SHELL, LOGNAME, MAIL, CDPATH, IFS, PATH
442 * how could we get untrusted data here?
443 */
444 for (envitem = pamenv; *envitem; envitem++) {
445 putenv(*envitem);
446 free(*envitem);
447 }
448
449 free(pamenv);
450 }
451
452 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|
453 LOGIN_SETENV|LOGIN_SETUMASK))
454 err(EXIT_FAILURE, "setting user context");
455 if (p)
456 (void)setenv("TERM", p, 1);
457 if (gohome && chdir(pwd->pw_dir) < 0)
458 errx(EXIT_FAILURE, "no directory");
459 }
460
461 if (asthem || pwd->pw_uid) {
462 (void)setenv("LOGNAME", pwd->pw_name, 1);
463 (void)setenv("USER", pwd->pw_name, 1);
464 }
465 (void)setenv("HOME", pwd->pw_dir, 1);
466 (void)setenv("SHELL", shell, 1);
467 }
468 (void)setenv("SU_FROM", username, 1);
469
470 if (iscsh == YES) {
471 if (fastlogin)
472 *np-- = "-f";
473 if (asme)
474 *np-- = "-m";
475 } else {
476 if (fastlogin)
477 unsetenv("ENV");
478 }
479
480 if (asthem) {
481 avshellbuf[0] = '-';
482 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
483 avshell = avshellbuf;
484 } else if (iscsh == YES) {
485 /* csh strips the first character... */
486 avshellbuf[0] = '_';
487 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
488 avshell = avshellbuf;
489 }
490 *np = avshell;
491
492 if (ruid != 0)
493 syslog(LOG_NOTICE, "%s to %s%s",
494 username, pwd->pw_name, ontty());
495
496 /*
497 * Set user context, except for umask, and the stuff
498 * we have done before.
499 */
500 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV|LOGIN_SETUMASK|
501 LOGIN_SETLOGIN|LOGIN_SETPATH|LOGIN_SETGROUP);
502
503 /*
504 * Don't touch resource/priority settings if -m has been used
505 * or -l and -c hasn't, and we're not su'ing to root.
506 */
507 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
508 setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
509
510 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) == -1)
511 err(EXIT_FAILURE, "setusercontext");
512
513 (void)execv(shell, np);
514 err(EXIT_FAILURE, "%s", shell);
515 done:
516 logit("%s: %s", func, pam_strerror(pamh, pam_err));
517 pam_end(pamh, pam_err);
518 return EXIT_FAILURE;
519 }
520
521 static void
522 logit(const char *fmt, ...)
523 {
524 va_list ap;
525
526 va_start(ap, fmt);
527 vwarnx(fmt, ap);
528 vsyslog(LOG_ERR, fmt, ap);
529 va_end(ap);
530 }
531
532
533 static int
534 chshell(const char *sh)
535 {
536 const char *cp;
537
538 setusershell();
539 while ((cp = getusershell()) != NULL)
540 if (!strcmp(cp, sh))
541 return 1;
542 return 0;
543 }
544
545 static char *
546 ontty(void)
547 {
548 char *p;
549 static char buf[MAXPATHLEN + 4];
550
551 buf[0] = 0;
552 if ((p = ttyname(STDERR_FILENO)) != NULL)
553 (void)snprintf(buf, sizeof buf, " on %s", p);
554 return buf;
555 }
556