su_pam.c revision 1.1 1 /* $NetBSD: su_pam.c,v 1.1 2005/01/10 03:11:50 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.1 2005/01/10 03:11:50 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
64 #ifdef LOGIN_CAP
65 #include <login_cap.h>
66 #endif
67
68 #include <security/pam_appl.h>
69 #include <security/openpam.h> /* for openpam_ttyconv() */
70
71
72 static pam_handle_t *pamh = NULL;
73 static const struct pam_conv pamc = { &openpam_ttyconv, NULL };
74 static int chshell(const char *);
75 static char *ontty(void);
76
77 int main(int, char **);
78
79 #define ARGSTRX "-dflm"
80
81 #ifdef LOGIN_CAP
82 #define ARGSTR ARGSTRX "c:"
83 #else
84 #define ARGSTR ARGSTRX
85 #endif
86
87 int
88 main(int argc, char **argv)
89 {
90 extern char **environ;
91 struct passwd *pwd;
92 char *p;
93 uid_t ruid;
94 int asme, ch, asthem, fastlogin, prio, gohome;
95 enum { UNSET, YES, NO } iscsh = UNSET;
96 char *user, *shell, *avshell, *username, **np;
97 char *class;
98 char shellbuf[MAXPATHLEN], avshellbuf[MAXPATHLEN];
99 int pam_err;
100 char hostname[MAXHOSTNAMELEN];
101 char *tty;
102 const char *func;
103 const void *newuser;
104 #ifdef LOGIN_CAP
105 login_cap_t *lc;
106 #endif
107 #ifdef PAM_DEBUG
108 extern int _openpam_debug;
109 _openpam_debug = 1;
110 #endif
111
112 asme = asthem = fastlogin = 0;
113 gohome = 1;
114 shell = class = NULL;
115 while ((ch = getopt(argc, argv, ARGSTR)) != -1)
116 switch((char)ch) {
117 #ifdef LOGIN_CAP
118 case 'c':
119 class = optarg;
120 break;
121 #endif
122 case 'd':
123 asme = 0;
124 asthem = 1;
125 gohome = 0;
126 break;
127 case 'f':
128 fastlogin = 1;
129 break;
130 case '-':
131 case 'l':
132 asme = 0;
133 asthem = 1;
134 break;
135 case 'm':
136 asme = 1;
137 asthem = 0;
138 break;
139 case '?':
140 default:
141 (void)fprintf(stderr,
142 "Usage: %s [%s] [login [shell arguments]]\n",
143 getprogname(), ARGSTR);
144 exit(1);
145 }
146 argv += optind;
147
148 /* Lower the priority so su runs faster */
149 errno = 0;
150 prio = getpriority(PRIO_PROCESS, 0);
151 if (errno)
152 prio = 0;
153 if (prio > -2)
154 (void)setpriority(PRIO_PROCESS, 0, -2);
155 openlog("su", 0, LOG_AUTH);
156
157 /* get current login name and shell */
158 ruid = getuid();
159 username = getlogin();
160 if (username == NULL || (pwd = getpwnam(username)) == NULL ||
161 pwd->pw_uid != ruid)
162 pwd = getpwuid(ruid);
163 if (pwd == NULL)
164 errx(1, "who are you?");
165 if ((username = strdup(pwd->pw_name)) == NULL)
166 err(1, "strdup");
167
168
169 if (asme) {
170 if (pwd->pw_shell && *pwd->pw_shell) {
171 strlcpy(shellbuf, pwd->pw_shell, sizeof(shellbuf));
172 shell = shellbuf;
173 } else {
174 shell = _PATH_BSHELL;
175 iscsh = NO;
176 }
177 }
178 /* get target login information, default to root */
179 user = *argv ? *argv : "root";
180 np = *argv ? argv : argv - 1;
181
182 if ((pwd = getpwnam(user)) == NULL)
183 errx(1, "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");
195 errx(1, "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",
218 username, user, ontty());
219 pam_end(pamh, pam_err);
220 errx(1, "Sorry");
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 ((pwd = getpwnam(user)) == NULL) {
250 pam_end(pamh, pam_err);
251 syslog(LOG_ERR, "unknown login: %s", username);
252 errx(1, "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 #ifdef LOGIN_CAP
267 /* force the usage of specified class */
268 if (class) {
269 if (ruid)
270 ERRX_PAM_END((1, "Only root may use -c"));
271
272 pwd->pw_class = class;
273 }
274 if ((lc = login_getclass(pwd->pw_class)) == NULL)
275 ERRX_PAM_END((1, "Unknown class %s\n", pwd->pw_class));
276 #endif
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((1,"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 * Set permissions. We change the user credentials (UID) here
301 * XXX PAM should come before LOGIN_CAP so that the class
302 * specified through -c can override PAM. But as we might drop
303 * root UID on both operations, it is not possible to do that.
304 * If a login class was specified, skip PAM.
305 */
306 #ifdef LOGIN_CAP
307 if (class) {
308 if (setusercontext(lc, pwd, pwd->pw_uid,
309 (asthem ? (LOGIN_SETPRIORITY | LOGIN_SETUMASK) : 0) |
310 LOGIN_SETRESOURCES | LOGIN_SETGROUP | LOGIN_SETUSER))
311 ERR_PAM_END((1, "setting user context"));
312 printf("%d %d\n", asthem, pwd->pw_uid);
313 } else
314 #endif
315 {
316 pam_err = pam_setcred(pamh, PAM_ESTABLISH_CRED);
317 if (pam_err != PAM_SUCCESS)
318 PAM_END("pam_setcred");
319 }
320
321 /*
322 * Manage session.
323 */
324 if (asthem) {
325 pid_t pid, xpid;
326 void *oint;
327 void *oabrt;
328 int status;
329
330 if ((pam_err = pam_open_session(pamh, 0)) != PAM_SUCCESS)
331 PAM_END("pam_open_session");
332
333 /*
334 * In order to call pam_close_session after the
335 * command terminates, we need to fork.
336 * Make sure signals cannot kill the parent.
337 * This is copied from crontab(8), which has to
338 * cope with a similar situation. XXX FreeBSD
339 * has a much more complicated code (CVS logs
340 * tell about workaround in libpthread, but we
341 * might miss useful stuff)
342 */
343 oint = signal(SIGINT, SIG_IGN);
344 oabrt = signal(SIGABRT, SIG_IGN);
345
346 switch (pid = fork()) {
347 case -1:
348 pam_err = pam_close_session(pamh, 0);
349 if (pam_err != PAM_SUCCESS) {
350 syslog(LOG_ERR, "pam_close_session: %s",
351 pam_strerror(pamh, pam_err));
352 warnx("pam_close_session: %s",
353 pam_strerror(pamh, pam_err));
354 }
355 ERR_PAM_END((1, "fork"));
356 break;
357
358 case 0: /* Child */
359 break;
360
361 default:
362 /*
363 * Parent: wait for the child to terminate
364 * and call pam_close_session.
365 */
366 if ((xpid = wait(&status)) != pid) {
367 pam_err = pam_close_session(pamh, 0);
368 if (pam_err != PAM_SUCCESS) {
369 syslog(LOG_ERR,
370 "pam_close_session: %s",
371 pam_strerror(pamh, pam_err));
372 warnx("pam_close_session: %s",
373 pam_strerror(pamh, pam_err));
374 }
375 ERRX_PAM_END((1,
376 "wrong PID: %d != %d", pid, xpid));
377 }
378
379 (void)signal(SIGINT, oint);
380 (void)signal(SIGABRT, oabrt);
381
382 pam_err = pam_close_session(pamh, 0);
383 if (pam_err != PAM_SUCCESS)
384 PAM_END("pam_open_session");
385
386 pam_end(pamh, PAM_SUCCESS);
387 exit(0);
388 break;
389 }
390 }
391
392 /*
393 * The child: starting here, we don't have to care about
394 * handling PAM issues if we exit, the parent will do the
395 * job when we exit.
396 */
397 #undef PAM_END
398 #undef ERR_PAM_END
399 #undef ERRX_PAM_END
400
401 if (!asme) {
402 if (asthem) {
403 char **pamenv;
404
405 p = getenv("TERM");
406 /*
407 * Create an empty environment
408 */
409 if ((environ = malloc(sizeof(char *))) == NULL)
410 err(1, NULL);
411 environ[0] = NULL;
412
413 /*
414 * Add PAM environement, before the LOGIN_CAP stuff:
415 * if the login class is unspecified, we'll get the
416 * same data from PAM, if -c was used, the specified
417 * class must override PAM.
418 */
419 if ((pamenv = pam_getenvlist(pamh)) != NULL) {
420 char **envitem;
421
422 /*
423 * XXX Here FreeBSD filters out
424 * SHELL, LOGNAME, MAIL, CDPATH, IFS, PATH
425 * how could we get untrusted data here?
426 */
427 for (envitem = pamenv; *envitem; envitem++) {
428 putenv(*envitem);
429 free(*envitem);
430 }
431
432 free(pamenv);
433 }
434
435 #ifdef LOGIN_CAP
436 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH))
437 err(1, "setting user context");
438 #else
439 (void)setenv("PATH", _PATH_DEFPATH, 1);
440 #endif
441 if (p)
442 (void)setenv("TERM", p, 1);
443 if (gohome && chdir(pwd->pw_dir) < 0)
444 errx(1, "no directory");
445 }
446
447 if (asthem || pwd->pw_uid)
448 (void)setenv("USER", pwd->pw_name, 1);
449 (void)setenv("HOME", pwd->pw_dir, 1);
450 (void)setenv("SHELL", shell, 1);
451 }
452 (void)setenv("SU_FROM", username, 1);
453
454 if (iscsh == YES) {
455 if (fastlogin)
456 *np-- = "-f";
457 if (asme)
458 *np-- = "-m";
459 } else {
460 if (fastlogin)
461 unsetenv("ENV");
462 }
463
464 if (asthem) {
465 avshellbuf[0] = '-';
466 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
467 avshell = avshellbuf;
468 } else if (iscsh == YES) {
469 /* csh strips the first character... */
470 avshellbuf[0] = '_';
471 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
472 avshell = avshellbuf;
473 }
474 *np = avshell;
475
476 if (ruid != 0)
477 syslog(LOG_NOTICE, "%s to %s%s",
478 username, pwd->pw_name, ontty());
479
480 /* Raise our priority back to what we had before */
481 (void)setpriority(PRIO_PROCESS, 0, prio);
482
483 printf("%d %d\n", geteuid(), getuid());
484 (void)execv(shell, np);
485 err(1, "%s", shell);
486 done:
487 syslog(LOG_ERR, "%s: %s", func, pam_strerror(pamh, pam_err)); \
488 warnx("%s: %s", func, pam_strerror(pamh, pam_err)); \
489 pam_end(pamh, pam_err); \
490 return 1;
491 }
492
493 static int
494 chshell(const char *sh)
495 {
496 const char *cp;
497
498 setusershell();
499 while ((cp = getusershell()) != NULL)
500 if (!strcmp(cp, sh))
501 return 1;
502 return 0;
503 }
504
505 static char *
506 ontty(void)
507 {
508 char *p;
509 static char buf[MAXPATHLEN + 4];
510
511 buf[0] = 0;
512 if ((p = ttyname(STDERR_FILENO)) != NULL)
513 (void)snprintf(buf, sizeof buf, " on %s", p);
514 return buf;
515 }
516