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