su_pam.c revision 1.9 1 /* $NetBSD: su_pam.c,v 1.9 2005/04/19 03:17:35 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.9 2005/04/19 03:17:35 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 err(EXIT_FAILURE, "setting user context");
454 if (p)
455 (void)setenv("TERM", p, 1);
456 if (gohome && chdir(pwd->pw_dir) < 0)
457 errx(EXIT_FAILURE, "no directory");
458 }
459
460 if (asthem || pwd->pw_uid)
461 (void)setenv("USER", pwd->pw_name, 1);
462 (void)setenv("HOME", pwd->pw_dir, 1);
463 (void)setenv("SHELL", shell, 1);
464 }
465 (void)setenv("SU_FROM", username, 1);
466
467 if (iscsh == YES) {
468 if (fastlogin)
469 *np-- = "-f";
470 if (asme)
471 *np-- = "-m";
472 } else {
473 if (fastlogin)
474 unsetenv("ENV");
475 }
476
477 if (asthem) {
478 avshellbuf[0] = '-';
479 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
480 avshell = avshellbuf;
481 } else if (iscsh == YES) {
482 /* csh strips the first character... */
483 avshellbuf[0] = '_';
484 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
485 avshell = avshellbuf;
486 }
487 *np = avshell;
488
489 if (ruid != 0)
490 syslog(LOG_NOTICE, "%s to %s%s",
491 username, pwd->pw_name, ontty());
492
493 /*
494 * Set user context, except for umask, and the stuff
495 * we have done before.
496 */
497 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV|LOGIN_SETUMASK|
498 LOGIN_SETLOGIN|LOGIN_SETPATH|LOGIN_SETGROUP);
499
500 /*
501 * Don't touch resource/priority settings if -m has been used
502 * or -l and -c hasn't, and we're not su'ing to root.
503 */
504 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
505 setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
506
507 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) == -1)
508 err(EXIT_FAILURE, "setusercontext");
509
510 (void)execv(shell, np);
511 err(EXIT_FAILURE, "%s", shell);
512 done:
513 logit("%s: %s", func, pam_strerror(pamh, pam_err));
514 pam_end(pamh, pam_err);
515 return EXIT_FAILURE;
516 }
517
518 static void
519 logit(const char *fmt, ...)
520 {
521 va_list ap;
522
523 va_start(ap, fmt);
524 vwarnx(fmt, ap);
525 vsyslog(LOG_ERR, fmt, ap);
526 va_end(ap);
527 }
528
529
530 static int
531 chshell(const char *sh)
532 {
533 const char *cp;
534
535 setusershell();
536 while ((cp = getusershell()) != NULL)
537 if (!strcmp(cp, sh))
538 return 1;
539 return 0;
540 }
541
542 static char *
543 ontty(void)
544 {
545 char *p;
546 static char buf[MAXPATHLEN + 4];
547
548 buf[0] = 0;
549 if ((p = ttyname(STDERR_FILENO)) != NULL)
550 (void)snprintf(buf, sizeof buf, " on %s", p);
551 return buf;
552 }
553