su_pam.c revision 1.8 1 /* $NetBSD: su_pam.c,v 1.8 2005/04/02 16:12:52 he 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.8 2005/04/02 16:12:52 he 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->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 errx(EXIT_FAILURE, "unknown login %s", user);
183
184 /*
185 * PAM initialization
186 */
187 #define PAM_END(msg) do { func = msg; goto done; } while (/*CONSTCOND*/0)
188
189 if ((pam_err = pam_start("su", user, &pamc, &pamh)) != PAM_SUCCESS) {
190 if (pamh != NULL)
191 PAM_END("pam_start");
192 /* Things went really bad... */
193 syslog(LOG_ERR, "pam_start failed: %s",
194 pam_strerror(pamh, pam_err));
195 errx(EXIT_FAILURE, "pam_start failed");
196 }
197
198 #define PAM_END_ITEM(item) PAM_END("pam_set_item(" # item ")")
199 #define PAM_SET_ITEM(item, var) \
200 if ((pam_err = pam_set_item(pamh, (item), (var))) != PAM_SUCCESS) \
201 PAM_END_ITEM(item)
202
203 /*
204 * Fill hostname, username and tty
205 */
206 PAM_SET_ITEM(PAM_RUSER, username);
207 if (gethostname(hostname, sizeof(hostname)) != -1)
208 PAM_SET_ITEM(PAM_RHOST, hostname);
209
210 if ((tty = ttyname(STDERR_FILENO)) != NULL)
211 PAM_SET_ITEM(PAM_TTY, tty);
212
213 /*
214 * Authentication
215 */
216 if ((pam_err = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
217 syslog(LOG_WARNING, "BAD SU %s to %s%s: %s",
218 username, user, ontty(), pam_strerror(pamh, pam_err));
219 pam_end(pamh, pam_err);
220 errx(EXIT_FAILURE, "Sorry: %s", pam_strerror(pamh, pam_err));
221 }
222
223 /*
224 * Authorization
225 */
226 switch(pam_err = pam_acct_mgmt(pamh, 0)) {
227 case PAM_NEW_AUTHTOK_REQD:
228 pam_err = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
229 if (pam_err != PAM_SUCCESS)
230 PAM_END("pam_chauthok");
231 break;
232 case PAM_SUCCESS:
233 break;
234 default:
235 PAM_END("pam_acct_mgmt");
236 break;
237 }
238
239 /*
240 * pam_authenticate might have changed the target user.
241 * refresh pwd and user
242 */
243 pam_err = pam_get_item(pamh, PAM_USER, &newuser);
244 if (pam_err != PAM_SUCCESS) {
245 syslog(LOG_WARNING,
246 "pam_get_item(PAM_USER): %s", pam_strerror(pamh, pam_err));
247 } else {
248 user = (char *)newuser;
249 if (getpwnam_r(user, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0) {
250 pam_end(pamh, pam_err);
251 syslog(LOG_ERR, "unknown login: %s", username);
252 errx(EXIT_FAILURE, "unknown login: %s", username);
253 }
254 }
255
256 #define ERRX_PAM_END(args) do { \
257 pam_end(pamh, pam_err); \
258 errx args; \
259 } while (/* CONSTOCOND */0)
260
261 #define ERR_PAM_END(args) do { \
262 pam_end(pamh, pam_err); \
263 err args; \
264 } while (/* CONSTOCOND */0)
265
266 /* force the usage of specified class */
267 if (class) {
268 if (ruid)
269 ERRX_PAM_END((EXIT_FAILURE, "Only root may use -c"));
270
271 pwd->pw_class = class;
272 }
273
274 if ((lc = login_getclass(pwd->pw_class)) == NULL)
275 ERRX_PAM_END((EXIT_FAILURE,
276 "Unknown class %s\n", pwd->pw_class));
277
278 if (asme) {
279 /* if asme and non-standard target shell, must be root */
280 if (!chshell(pwd->pw_shell) && ruid)
281 ERRX_PAM_END((EXIT_FAILURE,
282 "permission denied (shell)."));
283 } else if (pwd->pw_shell && *pwd->pw_shell) {
284 shell = pwd->pw_shell;
285 iscsh = UNSET;
286 } else {
287 shell = _PATH_BSHELL;
288 iscsh = NO;
289 }
290
291 if ((p = strrchr(shell, '/')) != NULL)
292 avshell = p + 1;
293 else
294 avshell = shell;
295
296 /* if we're forking a csh, we want to slightly muck the args */
297 if (iscsh == UNSET)
298 iscsh = strstr(avshell, "csh") ? YES : NO;
299
300 /*
301 * Initialize the supplemental groups before pam gets to them,
302 * so that other pam modules get a chance to add more when
303 * we do setcred. Note, we don't relinguish our set-userid yet
304 */
305 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
306 ERR_PAM_END((EXIT_FAILURE, "setting user context"));
307
308 if ((pam_err = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS)
309 PAM_END("pam_setcred");
310
311 /*
312 * Manage session.
313 */
314 if (asthem) {
315 pid_t pid, xpid;
316 int status = 1;
317 struct sigaction sa, sa_int, sa_pipe, sa_quit;
318 int fds[2];
319
320 if ((pam_err = pam_open_session(pamh, 0)) != PAM_SUCCESS)
321 PAM_END("pam_open_session");
322
323 /*
324 * In order to call pam_close_session after the
325 * command terminates, we need to fork.
326 */
327 sa.sa_flags = SA_RESTART;
328 sa.sa_handler = SIG_IGN;
329 sigemptyset(&sa.sa_mask);
330 (void)sigaction(SIGINT, &sa, &sa_int);
331 (void)sigaction(SIGQUIT, &sa, &sa_quit);
332 (void)sigaction(SIGPIPE, &sa, &sa_pipe);
333 sa.sa_handler = SIG_DFL;
334 (void)sigaction(SIGTSTP, &sa, NULL);
335 /*
336 * Use a pipe to guarantee the order of execution of
337 * the parent and the child.
338 */
339 if (pipe(fds) == -1) {
340 warn("pipe failed");
341 goto out;
342 }
343
344 switch (pid = fork()) {
345 case -1:
346 logit("fork failed (%s)", strerror(errno));
347 goto out;
348
349 case 0: /* Child */
350 (void)close(fds[1]);
351 (void)read(fds[0], &status, 1);
352 (void)close(fds[0]);
353 (void)sigaction(SIGINT, &sa_int, NULL);
354 (void)sigaction(SIGQUIT, &sa_quit, NULL);
355 (void)sigaction(SIGPIPE, &sa_pipe, NULL);
356 break;
357
358 default:
359 sa.sa_handler = SIG_IGN;
360 (void)sigaction(SIGTTOU, &sa, NULL);
361 (void)close(fds[0]);
362 (void)setpgid(pid, pid);
363 (void)tcsetpgrp(STDERR_FILENO, pid);
364 (void)close(fds[1]);
365 (void)sigaction(SIGPIPE, &sa_pipe, NULL);
366 /*
367 * Parent: wait for the child to terminate
368 * and call pam_close_session.
369 */
370 while ((xpid = waitpid(pid, &status, WUNTRACED))
371 == pid) {
372 if (WIFSTOPPED(status)) {
373 (void)kill(getpid(), SIGSTOP);
374 (void)tcsetpgrp(STDERR_FILENO,
375 getpgid(pid));
376 (void)kill(pid, SIGCONT);
377 status = 1;
378 continue;
379 }
380 break;
381 }
382
383 (void)tcsetpgrp(STDERR_FILENO, getpgid(0));
384
385 if (xpid == -1) {
386 logit("Error waiting for pid %d (%s)", pid,
387 strerror(errno));
388 } else if (xpid != pid) {
389 /* Can't happen. */
390 logit("Wrong PID: %d != %d", pid, xpid);
391 }
392 out:
393 pam_err = pam_setcred(pamh, PAM_DELETE_CRED);
394 if (pam_err != PAM_SUCCESS)
395 logit("pam_setcred: %s",
396 pam_strerror(pamh, pam_err));
397 pam_err = pam_close_session(pamh, 0);
398 if (pam_err != PAM_SUCCESS)
399 logit("pam_close_session: %s",
400 pam_strerror(pamh, pam_err));
401 pam_end(pamh, pam_err);
402 exit(WEXITSTATUS(status));
403 break;
404 }
405 }
406
407 /*
408 * The child: starting here, we don't have to care about
409 * handling PAM issues if we exit, the parent will do the
410 * job when we exit.
411 */
412 #undef PAM_END
413 #undef ERR_PAM_END
414 #undef ERRX_PAM_END
415
416 if (!asme) {
417 if (asthem) {
418 char **pamenv;
419
420 p = getenv("TERM");
421 /*
422 * Create an empty environment
423 */
424 if ((environ = malloc(sizeof(char *))) == NULL)
425 err(EXIT_FAILURE, NULL);
426 environ[0] = NULL;
427
428 /*
429 * Add PAM environement, before the LOGIN_CAP stuff:
430 * if the login class is unspecified, we'll get the
431 * same data from PAM, if -c was used, the specified
432 * class must override PAM.
433 */
434 if ((pamenv = pam_getenvlist(pamh)) != NULL) {
435 char **envitem;
436
437 /*
438 * XXX Here FreeBSD filters out
439 * SHELL, LOGNAME, MAIL, CDPATH, IFS, PATH
440 * how could we get untrusted data here?
441 */
442 for (envitem = pamenv; *envitem; envitem++) {
443 putenv(*envitem);
444 free(*envitem);
445 }
446
447 free(pamenv);
448 }
449
450 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH))
451 err(EXIT_FAILURE, "setting user context");
452 if (p)
453 (void)setenv("TERM", p, 1);
454 if (gohome && chdir(pwd->pw_dir) < 0)
455 errx(EXIT_FAILURE, "no directory");
456 }
457
458 if (asthem || pwd->pw_uid)
459 (void)setenv("USER", pwd->pw_name, 1);
460 (void)setenv("HOME", pwd->pw_dir, 1);
461 (void)setenv("SHELL", shell, 1);
462 }
463 (void)setenv("SU_FROM", username, 1);
464
465 if (iscsh == YES) {
466 if (fastlogin)
467 *np-- = "-f";
468 if (asme)
469 *np-- = "-m";
470 } else {
471 if (fastlogin)
472 unsetenv("ENV");
473 }
474
475 if (asthem) {
476 avshellbuf[0] = '-';
477 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
478 avshell = avshellbuf;
479 } else if (iscsh == YES) {
480 /* csh strips the first character... */
481 avshellbuf[0] = '_';
482 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
483 avshell = avshellbuf;
484 }
485 *np = avshell;
486
487 if (ruid != 0)
488 syslog(LOG_NOTICE, "%s to %s%s",
489 username, pwd->pw_name, ontty());
490
491 /*
492 * Set user context, except for umask, and the stuff
493 * we have done before.
494 */
495 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV|LOGIN_SETUMASK|
496 LOGIN_SETLOGIN|LOGIN_SETPATH|LOGIN_SETGROUP);
497
498 /*
499 * Don't touch resource/priority settings if -m has been used
500 * or -l and -c hasn't, and we're not su'ing to root.
501 */
502 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
503 setwhat &= ~(LOGIN_SETPRIORITY|LOGIN_SETRESOURCES);
504
505 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) == -1)
506 err(EXIT_FAILURE, "setusercontext");
507
508 (void)execv(shell, np);
509 err(EXIT_FAILURE, "%s", shell);
510 done:
511 logit("%s: %s", func, pam_strerror(pamh, pam_err));
512 pam_end(pamh, pam_err);
513 return EXIT_FAILURE;
514 }
515
516 static void
517 logit(const char *fmt, ...)
518 {
519 va_list ap;
520
521 va_start(ap, fmt);
522 vwarnx(fmt, ap);
523 vsyslog(LOG_ERR, fmt, ap);
524 va_end(ap);
525 }
526
527
528 static int
529 chshell(const char *sh)
530 {
531 const char *cp;
532
533 setusershell();
534 while ((cp = getusershell()) != NULL)
535 if (!strcmp(cp, sh))
536 return 1;
537 return 0;
538 }
539
540 static char *
541 ontty(void)
542 {
543 char *p;
544 static char buf[MAXPATHLEN + 4];
545
546 buf[0] = 0;
547 if ((p = ttyname(STDERR_FILENO)) != NULL)
548 (void)snprintf(buf, sizeof buf, " on %s", p);
549 return buf;
550 }
551