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