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