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