su.c revision 1.63 1 /* $NetBSD: su.c,v 1.63 2005/01/09 21:32:38 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.c,v 1.63 2005/01/09 21:32:38 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 #ifdef USE_PAM
51 #include <sys/wait.h>
52 #endif
53 #include <err.h>
54 #include <errno.h>
55 #include <grp.h>
56 #include <paths.h>
57 #include <pwd.h>
58 #include <stdio.h>
59 #ifdef SKEY
60 #include <skey.h>
61 #endif
62 #include <stdlib.h>
63 #include <string.h>
64 #include <syslog.h>
65 #include <time.h>
66 #include <tzfile.h>
67 #include <unistd.h>
68
69 #ifdef USE_PAM
70 #include <security/pam_appl.h>
71 #include <security/openpam.h> /* for openpam_ttyconv() */
72
73 static pam_handle_t *pamh = NULL;
74 static const struct pam_conv pamc = { &openpam_ttyconv, NULL };
75 #endif
76
77 #ifdef LOGIN_CAP
78 #include <login_cap.h>
79 #endif
80
81 #if defined(KERBEROS) && !defined(USE_PAM)
82 #include <des.h>
83 #include <krb.h>
84 #include <netdb.h>
85
86 static int kerberos __P((char *, char *, int));
87 static int koktologin __P((char *, char *, char *));
88
89 #endif
90
91 #if defined(KERBEROS5) && !defined(USE_PAM)
92 #include <krb5.h>
93
94 static int kerberos5 __P((char *, char *, int));
95
96 #endif
97
98 #if defined(KERBEROS) || defined(KERBEROS5)
99
100 #define ARGSTRX "-Kdflm"
101
102 int use_kerberos = 1;
103
104 #else
105 #define ARGSTRX "-dflm"
106 #endif
107
108 #ifndef SU_GROUP
109 #define SU_GROUP "wheel"
110 #endif
111
112 #ifdef LOGIN_CAP
113 #define ARGSTR ARGSTRX "c:"
114 #else
115 #define ARGSTR ARGSTRX
116 #endif
117
118 int main __P((int, char **));
119
120 static int chshell __P((const char *));
121 static char *ontty __P((void));
122 #ifndef USE_PAM
123 static int check_ingroup __P((int, const char *, const char *, int));
124 #endif
125
126
127 #ifndef USE_PAM
128 int
129 main(argc, argv)
130 int argc;
131 char **argv;
132 {
133 extern char **environ;
134 struct passwd *pwd;
135 char *p;
136 #ifdef BSD4_4
137 struct timeval tp;
138 #endif
139 uid_t ruid;
140 int asme, ch, asthem, fastlogin, prio, gohome;
141 enum { UNSET, YES, NO } iscsh = UNSET;
142 char *user, *shell, *avshell, *username, **np;
143 char *userpass, *class;
144 char shellbuf[MAXPATHLEN], avshellbuf[MAXPATHLEN];
145 time_t pw_warntime = _PASSWORD_WARNDAYS * SECSPERDAY;
146 #ifdef LOGIN_CAP
147 login_cap_t *lc;
148 #endif
149
150 asme = asthem = fastlogin = 0;
151 gohome = 1;
152 shell = class = NULL;
153 while ((ch = getopt(argc, argv, ARGSTR)) != -1)
154 switch((char)ch) {
155 #if defined(KERBEROS) || defined(KERBEROS5)
156 case 'K':
157 use_kerberos = 0;
158 break;
159 #endif
160 #ifdef LOGIN_CAP
161 case 'c':
162 class = optarg;
163 break;
164 #endif
165 case 'd':
166 asme = 0;
167 asthem = 1;
168 gohome = 0;
169 break;
170 case 'f':
171 fastlogin = 1;
172 break;
173 case '-':
174 case 'l':
175 asme = 0;
176 asthem = 1;
177 break;
178 case 'm':
179 asme = 1;
180 asthem = 0;
181 break;
182 case '?':
183 default:
184 (void)fprintf(stderr,
185 "usage: %s [%s] [login [shell arguments]]\n",
186 getprogname(), ARGSTR);
187 exit(1);
188 }
189 argv += optind;
190
191 /* Lower the priority so su runs faster */
192 errno = 0;
193 prio = getpriority(PRIO_PROCESS, 0);
194 if (errno)
195 prio = 0;
196 if (prio > -2)
197 (void)setpriority(PRIO_PROCESS, 0, -2);
198 openlog("su", 0, LOG_AUTH);
199
200 /* get current login name and shell */
201 ruid = getuid();
202 username = getlogin();
203 if (username == NULL || (pwd = getpwnam(username)) == NULL ||
204 pwd->pw_uid != ruid)
205 pwd = getpwuid(ruid);
206 if (pwd == NULL)
207 errx(1, "who are you?");
208 username = strdup(pwd->pw_name);
209 userpass = strdup(pwd->pw_passwd);
210 if (username == NULL || userpass == NULL)
211 err(1, "strdup");
212
213
214 if (asme) {
215 if (pwd->pw_shell && *pwd->pw_shell) {
216 strlcpy(shellbuf, pwd->pw_shell, sizeof(shellbuf));
217 shell = shellbuf;
218 } else {
219 shell = _PATH_BSHELL;
220 iscsh = NO;
221 }
222 }
223 /* get target login information, default to root */
224 user = *argv ? *argv : "root";
225 np = *argv ? argv : argv-1;
226
227 if ((pwd = getpwnam(user)) == NULL)
228 errx(1, "unknown login %s", user);
229
230 #ifdef LOGIN_CAP
231 /* force the usage of specified class */
232 if (class) {
233 if (ruid)
234 errx(1, "Only root may use -c");
235
236 pwd->pw_class = class;
237 }
238 if ((lc = login_getclass(pwd->pw_class)) == NULL)
239 errx(1, "Unknown class %s\n", pwd->pw_class);
240
241 pw_warntime = login_getcaptime(lc, "password-warn",
242 _PASSWORD_WARNDAYS * SECSPERDAY,
243 _PASSWORD_WARNDAYS * SECSPERDAY);
244 #endif
245
246 if (ruid
247 #ifdef KERBEROS5
248 && (!use_kerberos || kerberos5(username, user, pwd->pw_uid))
249 #endif
250 #ifdef KERBEROS
251 && (!use_kerberos || kerberos(username, user, pwd->pw_uid))
252 #endif
253 ) {
254 char *pass = pwd->pw_passwd;
255 int ok = pwd->pw_uid != 0;
256
257 #ifdef SU_ROOTAUTH
258 /*
259 * Allow those in group rootauth to su to root, by supplying
260 * their own password.
261 */
262 if (!ok) {
263 if ((ok = check_ingroup(-1, SU_ROOTAUTH, username, 0))) {
264 pass = userpass;
265 user = username;
266 }
267 }
268 #endif
269 /*
270 * Only allow those in group SU_GROUP to su to root,
271 * but only if that group has any members.
272 * If SU_GROUP has no members, allow anyone to su root
273 */
274 if (!ok) {
275 ok = check_ingroup(-1, SU_GROUP, username, 1);
276 }
277 if (!ok)
278 errx(1,
279 "you are not listed in the correct secondary group (%s) to su %s.",
280 SU_GROUP, user);
281 /* if target requires a password, verify it */
282 if (*pass) {
283 p = getpass("Password:");
284 #ifdef SKEY
285 if (strcasecmp(p, "s/key") == 0) {
286 if (skey_haskey(user))
287 errx(1, "Sorry, you have no s/key.");
288 else {
289 if (skey_authenticate(user)) {
290 goto badlogin;
291 }
292 }
293
294 } else
295 #endif
296 if (strcmp(pass, crypt(p, pass))) {
297 #ifdef SKEY
298 badlogin:
299 #endif
300 fprintf(stderr, "Sorry\n");
301 syslog(LOG_WARNING,
302 "BAD SU %s to %s%s", username,
303 pwd->pw_name, ontty());
304 exit(1);
305 }
306 }
307 }
308
309 if (asme) {
310 /* if asme and non-standard target shell, must be root */
311 if (!chshell(pwd->pw_shell) && ruid)
312 errx(1,"permission denied (shell).");
313 } else if (pwd->pw_shell && *pwd->pw_shell) {
314 shell = pwd->pw_shell;
315 iscsh = UNSET;
316 } else {
317 shell = _PATH_BSHELL;
318 iscsh = NO;
319 }
320
321 if ((p = strrchr(shell, '/')) != NULL)
322 avshell = p+1;
323 else
324 avshell = shell;
325
326 /* if we're forking a csh, we want to slightly muck the args */
327 if (iscsh == UNSET)
328 iscsh = strstr(avshell, "csh") ? YES : NO;
329
330 /* set permissions */
331 #ifdef LOGIN_CAP
332 if (setusercontext(lc, pwd, pwd->pw_uid,
333 (asthem ? (LOGIN_SETPRIORITY | LOGIN_SETUMASK) : 0) |
334 LOGIN_SETRESOURCES | LOGIN_SETGROUP | LOGIN_SETUSER))
335 err(1, "setting user context");
336 #else
337 if (setgid(pwd->pw_gid) < 0)
338 err(1, "setgid");
339 if (initgroups(user, pwd->pw_gid))
340 errx(1, "initgroups failed");
341 if (setuid(pwd->pw_uid) < 0)
342 err(1, "setuid");
343 #endif
344
345 if (!asme) {
346 if (asthem) {
347 p = getenv("TERM");
348 /* Create an empty environment */
349 if ((environ = malloc(sizeof(char *))) == NULL)
350 err(1, NULL);
351 environ[0] = NULL;
352 #ifdef LOGIN_CAP
353 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH))
354 err(1, "setting user context");
355 #else
356 (void)setenv("PATH", _PATH_DEFPATH, 1);
357 #endif
358 if (p)
359 (void)setenv("TERM", p, 1);
360 if (gohome && chdir(pwd->pw_dir) < 0)
361 errx(1, "no directory");
362 }
363
364 if (asthem || pwd->pw_uid)
365 (void)setenv("USER", pwd->pw_name, 1);
366 (void)setenv("HOME", pwd->pw_dir, 1);
367 (void)setenv("SHELL", shell, 1);
368 }
369 (void)setenv("SU_FROM", username, 1);
370
371 if (iscsh == YES) {
372 if (fastlogin)
373 *np-- = "-f";
374 if (asme)
375 *np-- = "-m";
376 } else {
377 if (fastlogin)
378 unsetenv("ENV");
379 }
380
381 if (asthem) {
382 avshellbuf[0] = '-';
383 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
384 avshell = avshellbuf;
385 } else if (iscsh == YES) {
386 /* csh strips the first character... */
387 avshellbuf[0] = '_';
388 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
389 avshell = avshellbuf;
390 }
391 *np = avshell;
392
393 #ifdef BSD4_4
394 if (pwd->pw_change || pwd->pw_expire)
395 (void)gettimeofday(&tp, (struct timezone *)NULL);
396 if (pwd->pw_change) {
397 if (tp.tv_sec >= pwd->pw_change) {
398 (void)printf("%s -- %s's password has expired.\n",
399 (ruid ? "Sorry" : "Note"), user);
400 if (ruid != 0)
401 exit(1);
402 } else if (pwd->pw_change - tp.tv_sec < pw_warntime)
403 (void)printf("Warning: %s's password expires on %s",
404 user, ctime(&pwd->pw_change));
405 }
406 if (pwd->pw_expire) {
407 if (tp.tv_sec >= pwd->pw_expire) {
408 (void)printf("%s -- %s's account has expired.\n",
409 (ruid ? "Sorry" : "Note"), user);
410 if (ruid != 0)
411 exit(1);
412 } else if (pwd->pw_expire - tp.tv_sec <
413 _PASSWORD_WARNDAYS * SECSPERDAY)
414 (void)printf("Warning: %s's account expires on %s",
415 user, ctime(&pwd->pw_expire));
416 }
417 #endif
418 if (ruid != 0)
419 syslog(LOG_NOTICE, "%s to %s%s",
420 username, pwd->pw_name, ontty());
421
422 /* Raise our priority back to what we had before */
423 (void)setpriority(PRIO_PROCESS, 0, prio);
424
425 execv(shell, np);
426 err(1, "%s", shell);
427 /* NOTREACHED */
428 }
429
430 #else /* USE_PAM */
431
432 int
433 main(argc, argv)
434 int argc;
435 char **argv;
436 {
437 extern char **environ;
438 struct passwd *pwd;
439 char *p;
440 uid_t ruid;
441 int asme, ch, asthem, fastlogin, prio, gohome;
442 enum { UNSET, YES, NO } iscsh = UNSET;
443 char *user, *shell, *avshell, *username, **np;
444 char *class;
445 char shellbuf[MAXPATHLEN], avshellbuf[MAXPATHLEN];
446 int pam_err;
447 char hostname[MAXHOSTNAMELEN];
448 char *tty;
449 const void *newuser;
450 #ifdef LOGIN_CAP
451 login_cap_t *lc;
452 #endif
453
454 asme = asthem = fastlogin = 0;
455 gohome = 1;
456 shell = class = NULL;
457 while ((ch = getopt(argc, argv, ARGSTR)) != -1)
458 switch((char)ch) {
459 #if defined(KERBEROS) || defined(KERBEROS5)
460 case 'K':
461 fprintf(stderr, "%s: -K is not supported anymore\n",
462 getprogname());
463 use_kerberos = 0;
464 break;
465 #endif
466 #ifdef LOGIN_CAP
467 case 'c':
468 class = optarg;
469 break;
470 #endif
471 case 'd':
472 asme = 0;
473 asthem = 1;
474 gohome = 0;
475 break;
476 case 'f':
477 fastlogin = 1;
478 break;
479 case '-':
480 case 'l':
481 asme = 0;
482 asthem = 1;
483 break;
484 case 'm':
485 asme = 1;
486 asthem = 0;
487 break;
488 case '?':
489 default:
490 (void)fprintf(stderr,
491 "usage: %s [%s] [login [shell arguments]]\n",
492 getprogname(), ARGSTR);
493 exit(1);
494 }
495 argv += optind;
496
497 /* Lower the priority so su runs faster */
498 errno = 0;
499 prio = getpriority(PRIO_PROCESS, 0);
500 if (errno)
501 prio = 0;
502 if (prio > -2)
503 (void)setpriority(PRIO_PROCESS, 0, -2);
504 openlog("su", 0, LOG_AUTH);
505
506 /* get current login name and shell */
507 ruid = getuid();
508 username = getlogin();
509 if (username == NULL || (pwd = getpwnam(username)) == NULL ||
510 pwd->pw_uid != ruid)
511 pwd = getpwuid(ruid);
512 if (pwd == NULL)
513 errx(1, "who are you?");
514 if ((username = strdup(pwd->pw_name)) == NULL)
515 err(1, "strdup");
516
517
518 if (asme) {
519 if (pwd->pw_shell && *pwd->pw_shell) {
520 strlcpy(shellbuf, pwd->pw_shell, sizeof(shellbuf));
521 shell = shellbuf;
522 } else {
523 shell = _PATH_BSHELL;
524 iscsh = NO;
525 }
526 }
527 /* get target login information, default to root */
528 user = *argv ? *argv : "root";
529 np = *argv ? argv : argv-1;
530
531 if ((pwd = getpwnam(user)) == NULL)
532 errx(1, "unknown login %s", user);
533
534 /*
535 * PAM initialization
536 */
537 #define PAM_END(func) do { \
538 syslog(LOG_ERR, "%s: %s", func, pam_strerror(pamh, pam_err)); \
539 warnx("%s: %s", func, pam_strerror(pamh, pam_err)); \
540 pam_end(pamh, pam_err); \
541 exit(1); \
542 } while (/* CONSTCOND */0)
543
544 if ((pam_err = pam_start("su", user, &pamc, &pamh)) != PAM_SUCCESS) {
545 if (pamh != NULL)
546 PAM_END("pam_start");
547 /* Things went really bad... */
548 syslog(LOG_ERR, "pam_start failed");
549 errx(1, "pam_start failed");
550 }
551
552 /*
553 * Fill hostname, username and tty
554 */
555 if ((pam_err = pam_set_item(pamh, PAM_RUSER, username)) != PAM_SUCCESS)
556 PAM_END("pam_set_item(PAM_RUSER)");
557
558 if ((gethostname(hostname, sizeof(hostname)) == 0) &&
559 ((pam_err = pam_set_item(pamh,
560 PAM_RHOST, hostname) != PAM_SUCCESS)))
561 PAM_END("pam_set_item(PAM_RHOST)");
562
563 if (((tty = ttyname(STDERR_FILENO)) != NULL) &&
564 ((pam_err = pam_set_item(pamh, PAM_TTY, tty)) != PAM_SUCCESS))
565 PAM_END("pam_set_item(PAM_TTY)");
566
567 /*
568 * Authentication
569 */
570 if ((pam_err = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
571 syslog(LOG_WARNING, "BAD SU %s to %s%s",
572 username, user, ontty());
573 pam_end(pamh, pam_err);
574 errx(1, "Sorry");
575 }
576
577 /*
578 * Authorization
579 */
580 switch(pam_err = pam_acct_mgmt(pamh, 0)) {
581 case PAM_NEW_AUTHTOK_REQD:
582 pam_err = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
583 if (pam_err != PAM_SUCCESS)
584 PAM_END("pam_chauthok");
585 break;
586 case PAM_SUCCESS:
587 break;
588 default:
589 PAM_END("pam_acct_mgmt");
590 break;
591 }
592
593 /*
594 * pam_authenticate might have changed the target user.
595 * refresh pwd and user
596 */
597 pam_err = pam_get_item(pamh, PAM_USER, &newuser);
598 if (pam_err != PAM_SUCCESS) {
599 syslog(LOG_WARNING,
600 "pam_get_item(PAM_USER): %s", pam_strerror(pamh, pam_err));
601 } else {
602 user = (char *)newuser;
603 if ((pwd = getpwnam(user)) == NULL) {
604 pam_end(pamh, pam_err);
605 syslog(LOG_ERR, "unknown login: %s", username);
606 errx(1, "unknown login: %s", username);
607 }
608 }
609
610 #define ERRX_PAM_END(args) do { \
611 pam_end(pamh, pam_err); \
612 errx args; \
613 } while (/* CONSTOCOND */0)
614
615 #define ERR_PAM_END(args) do { \
616 pam_end(pamh, pam_err); \
617 err args; \
618 } while (/* CONSTOCOND */0)
619
620 #ifdef LOGIN_CAP
621 /* force the usage of specified class */
622 if (class) {
623 if (ruid)
624 ERRX_PAM_END((1, "Only root may use -c"));
625
626 pwd->pw_class = class;
627 }
628 if ((lc = login_getclass(pwd->pw_class)) == NULL)
629 ERRX_PAM_END((1, "Unknown class %s\n", pwd->pw_class));
630 #endif
631
632 if (asme) {
633 /* if asme and non-standard target shell, must be root */
634 if (!chshell(pwd->pw_shell) && ruid)
635 ERRX_PAM_END((1,"permission denied (shell)."));
636 } else if (pwd->pw_shell && *pwd->pw_shell) {
637 shell = pwd->pw_shell;
638 iscsh = UNSET;
639 } else {
640 shell = _PATH_BSHELL;
641 iscsh = NO;
642 }
643
644 if ((p = strrchr(shell, '/')) != NULL)
645 avshell = p+1;
646 else
647 avshell = shell;
648
649 /* if we're forking a csh, we want to slightly muck the args */
650 if (iscsh == UNSET)
651 iscsh = strstr(avshell, "csh") ? YES : NO;
652
653 /*
654 * Set permissions. We change the user credentials (UID) here
655 * XXX PAM should come before LOGIN_CAP so that the class
656 * specified through -c can override PAM. But as we might drop
657 * root UID on both operations, it is not possible to do that.
658 * If a login class was specified, skip PAM.
659 */
660 #ifdef LOGIN_CAP
661 if (class) {
662 if (setusercontext(lc, pwd, pwd->pw_uid,
663 (asthem ? (LOGIN_SETPRIORITY | LOGIN_SETUMASK) : 0) |
664 LOGIN_SETRESOURCES | LOGIN_SETGROUP | LOGIN_SETUSER))
665 ERR_PAM_END((1, "setting user context"));
666 } else
667 #endif
668 {
669 pam_err = pam_setcred(pamh, PAM_ESTABLISH_CRED);
670 if (pam_err != PAM_SUCCESS)
671 PAM_END("pam_setcred");
672 }
673
674 /*
675 * Manage session.
676 */
677 if (asthem) {
678 pid_t pid, xpid;
679 void *oint;
680 void *oabrt;
681 int status;
682
683 if ((pam_err = pam_open_session(pamh, 0)) != PAM_SUCCESS)
684 PAM_END("pam_open_session");
685
686 /*
687 * In order to call pam_close_session after the
688 * command terminates, we need to fork.
689 * Make sure signals cannot kill the parent.
690 * This is copied from crontab(8), which has to
691 * cope with a similar situation. XXX FreeBSD
692 * has a much more complicated code (CVS logs
693 * tell about workaround in libpthread, but we
694 * might miss useful stuff)
695 */
696 oint = signal(SIGINT, SIG_IGN);
697 oabrt = signal(SIGABRT, SIG_IGN);
698
699 switch (pid = fork()) {
700 case -1:
701 pam_err = pam_close_session(pamh, 0);
702 if (pam_err != PAM_SUCCESS) {
703 syslog(LOG_ERR, "pam_close_session: %s",
704 pam_strerror(pamh, pam_err));
705 warnx("pam_close_session: %s",
706 pam_strerror(pamh, pam_err));
707 }
708 ERR_PAM_END((1, "fork"));
709 break;
710
711 case 0: /* Child */
712 break;
713
714 default:
715 /*
716 * Parent: wait for the child to terminate
717 * and call pam_close_session.
718 */
719 if ((xpid = wait(&status)) != pid) {
720 pam_err = pam_close_session(pamh, 0);
721 if (pam_err != PAM_SUCCESS) {
722 syslog(LOG_ERR,
723 "pam_close_session: %s",
724 pam_strerror(pamh, pam_err));
725 warnx("pam_close_session: %s",
726 pam_strerror(pamh, pam_err));
727 }
728 ERRX_PAM_END((1,
729 "wrong PID: %d != %d", pid, xpid));
730 }
731
732 (void)signal(SIGINT, oint);
733 (void)signal(SIGABRT, oabrt);
734
735 pam_err = pam_close_session(pamh, 0);
736 if (pam_err != PAM_SUCCESS)
737 PAM_END("pam_open_session");
738
739 pam_end(pamh, PAM_SUCCESS);
740 exit(0);
741 break;
742 }
743 }
744
745 /*
746 * The child: starting here, we don't have to care about
747 * handling PAM issues if we exit, the parent will do the
748 * job when we exit.
749 */
750 #undef PAM_END
751 #undef ERR_PAM_END
752 #undef ERRX_PAM_END
753
754 if (!asme) {
755 if (asthem) {
756 char **pamenv;
757
758 p = getenv("TERM");
759 /*
760 * Create an empty environment
761 */
762 if ((environ = malloc(sizeof(char *))) == NULL)
763 err(1, NULL);
764 environ[0] = NULL;
765
766 /*
767 * Add PAM environement, before the LOGIN_CAP stuff:
768 * if the login class is unspecified, we'll get the
769 * same data from PAM, if -c was used, the specified
770 * class must override PAM.
771 */
772 if ((pamenv = pam_getenvlist(pamh)) != NULL) {
773 char **envitem;
774
775 /*
776 * XXX Here FreeBSD filters out
777 * SHELL, LOGNAME, MAIL, CDPATH, IFS, PATH
778 * how could we get untrusted data here?
779 */
780 for (envitem = pamenv; *envitem; envitem++) {
781 putenv(*envitem);
782 free(*envitem);
783 }
784
785 free(pamenv);
786 }
787
788 #ifdef LOGIN_CAP
789 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH))
790 err(1, "setting user context");
791 #else
792 (void)setenv("PATH", _PATH_DEFPATH, 1);
793 #endif
794 if (p)
795 (void)setenv("TERM", p, 1);
796 if (gohome && chdir(pwd->pw_dir) < 0)
797 errx(1, "no directory");
798 }
799
800 if (asthem || pwd->pw_uid)
801 (void)setenv("USER", pwd->pw_name, 1);
802 (void)setenv("HOME", pwd->pw_dir, 1);
803 (void)setenv("SHELL", shell, 1);
804 }
805 (void)setenv("SU_FROM", username, 1);
806
807 if (iscsh == YES) {
808 if (fastlogin)
809 *np-- = "-f";
810 if (asme)
811 *np-- = "-m";
812 } else {
813 if (fastlogin)
814 unsetenv("ENV");
815 }
816
817 if (asthem) {
818 avshellbuf[0] = '-';
819 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
820 avshell = avshellbuf;
821 } else if (iscsh == YES) {
822 /* csh strips the first character... */
823 avshellbuf[0] = '_';
824 (void)strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
825 avshell = avshellbuf;
826 }
827 *np = avshell;
828
829 if (ruid != 0)
830 syslog(LOG_NOTICE, "%s to %s%s",
831 username, pwd->pw_name, ontty());
832
833 /* Raise our priority back to what we had before */
834 (void)setpriority(PRIO_PROCESS, 0, prio);
835
836 execv(shell, np);
837 err(1, "%s", shell);
838 /* NOTREACHED */
839 }
840 #endif /* USE_PAM */
841
842 static int
843 chshell(sh)
844 const char *sh;
845 {
846 const char *cp;
847
848 setusershell();
849 while ((cp = getusershell()) != NULL)
850 if (!strcmp(cp, sh))
851 return (1);
852 return (0);
853 }
854
855 static char *
856 ontty()
857 {
858 char *p;
859 static char buf[MAXPATHLEN + 4];
860
861 buf[0] = 0;
862 if ((p = ttyname(STDERR_FILENO)) != NULL)
863 (void)snprintf(buf, sizeof buf, " on %s", p);
864 return (buf);
865 }
866
867 #ifndef USE_PAM
868 #ifdef KERBEROS5
869 static int
870 kerberos5(username, user, uid)
871 char *username, *user;
872 int uid;
873 {
874 krb5_error_code ret;
875 krb5_context context;
876 krb5_principal princ = NULL;
877 krb5_ccache ccache, ccache2;
878 char *cc_name;
879 const char *filename;
880
881 ret = krb5_init_context(&context);
882 if (ret)
883 return (1);
884
885 if (strcmp (user, "root") == 0)
886 ret = krb5_make_principal(context, &princ,
887 NULL, username, "root", NULL);
888 else
889 ret = krb5_make_principal(context, &princ,
890 NULL, user, NULL);
891 if (ret)
892 goto fail;
893 if (!krb5_kuserok(context, princ, user) && !uid) {
894 warnx ("kerberos5: not in %s's ACL.", user);
895 goto fail;
896 }
897 ret = krb5_cc_gen_new(context, &krb5_mcc_ops, &ccache);
898 if (ret)
899 goto fail;
900 ret = krb5_verify_user_lrealm(context, princ, ccache, NULL, TRUE,
901 NULL);
902 if (ret) {
903 krb5_cc_destroy(context, ccache);
904 switch (ret) {
905 case KRB5_LIBOS_PWDINTR :
906 break;
907 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
908 case KRB5KRB_AP_ERR_MODIFIED:
909 krb5_warnx(context, "Password incorrect");
910 break;
911 default :
912 krb5_warn(context, ret, "krb5_verify_user");
913 break;
914 }
915 goto fail;
916 }
917 ret = krb5_cc_gen_new(context, &krb5_fcc_ops, &ccache2);
918 if (ret) {
919 krb5_cc_destroy(context, ccache);
920 goto fail;
921 }
922 ret = krb5_cc_copy_cache(context, ccache, ccache2);
923 if (ret) {
924 krb5_cc_destroy(context, ccache);
925 krb5_cc_destroy(context, ccache2);
926 goto fail;
927 }
928
929 filename = krb5_cc_get_name(context, ccache2);
930 asprintf(&cc_name, "%s:%s", krb5_cc_get_type(context, ccache2),
931 filename);
932 if (chown (filename, uid, -1) < 0) {
933 warn("chown %s", filename);
934 free(cc_name);
935 krb5_cc_destroy(context, ccache);
936 krb5_cc_destroy(context, ccache2);
937 goto fail;
938 }
939
940 setenv("KRB5CCNAME", cc_name, 1);
941 free(cc_name);
942 krb5_cc_close(context, ccache2);
943 krb5_cc_destroy(context, ccache);
944 return (0);
945
946 fail:
947 if (princ != NULL)
948 krb5_free_principal (context, princ);
949 krb5_free_context (context);
950 return (1);
951 }
952 #endif
953
954 #ifdef KERBEROS
955 static int
956 kerberos(username, user, uid)
957 char *username, *user;
958 int uid;
959 {
960 KTEXT_ST ticket;
961 AUTH_DAT authdata;
962 struct hostent *hp;
963 int kerno;
964 u_long faddr;
965 char lrealm[REALM_SZ], krbtkfile[MAXPATHLEN];
966 char hostname[MAXHOSTNAMELEN + 1], savehost[MAXHOSTNAMELEN + 1];
967
968 if (krb_get_lrealm(lrealm, 1) != KSUCCESS)
969 return (1);
970 if (koktologin(username, lrealm, user) && !uid) {
971 warnx("kerberos: not in %s's ACL.", user);
972 return (1);
973 }
974 (void)snprintf(krbtkfile, sizeof krbtkfile, "%s_%s_%d", TKT_ROOT,
975 user, getuid());
976
977 (void)setenv("KRBTKFILE", krbtkfile, 1);
978 (void)krb_set_tkt_string(krbtkfile);
979 /*
980 * Set real as well as effective ID to 0 for the moment,
981 * to make the kerberos library do the right thing.
982 */
983 if (setuid(0) < 0) {
984 warn("setuid");
985 return (1);
986 }
987
988 /*
989 * Little trick here -- if we are su'ing to root,
990 * we need to get a ticket for "xxx.root", where xxx represents
991 * the name of the person su'ing. Otherwise (non-root case),
992 * we need to get a ticket for "yyy.", where yyy represents
993 * the name of the person being su'd to, and the instance is null
994 *
995 * We should have a way to set the ticket lifetime,
996 * with a system default for root.
997 */
998 {
999 char prompt[128];
1000 char passw[256];
1001
1002 (void)snprintf (prompt, sizeof(prompt),
1003 "%s's Password: ",
1004 krb_unparse_name_long ((uid == 0 ? username : user),
1005 (uid == 0 ? "root" : ""),
1006 lrealm));
1007 if (des_read_pw_string (passw, sizeof (passw), prompt, 0)) {
1008 memset (passw, 0, sizeof (passw));
1009 return (1);
1010 }
1011 if (strlen(passw) == 0)
1012 return (1); /* Empty passwords are not allowed */
1013
1014 kerno = krb_get_pw_in_tkt((uid == 0 ? username : user),
1015 (uid == 0 ? "root" : ""), lrealm,
1016 KRB_TICKET_GRANTING_TICKET,
1017 lrealm,
1018 DEFAULT_TKT_LIFE,
1019 passw);
1020 memset (passw, 0, strlen (passw));
1021 }
1022
1023 if (kerno != KSUCCESS) {
1024 if (kerno == KDC_PR_UNKNOWN) {
1025 warnx("kerberos: principal unknown: %s.%s@%s",
1026 (uid == 0 ? username : user),
1027 (uid == 0 ? "root" : ""), lrealm);
1028 return (1);
1029 }
1030 warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
1031 syslog(LOG_WARNING,
1032 "BAD Kerberos SU: %s to %s%s: %s",
1033 username, user, ontty(), krb_err_txt[kerno]);
1034 return (1);
1035 }
1036
1037 if (chown(krbtkfile, uid, -1) < 0) {
1038 warn("chown");
1039 (void)unlink(krbtkfile);
1040 return (1);
1041 }
1042
1043 (void)setpriority(PRIO_PROCESS, 0, -2);
1044
1045 if (gethostname(hostname, sizeof(hostname)) == -1) {
1046 warn("gethostname");
1047 dest_tkt();
1048 return (1);
1049 }
1050 hostname[sizeof(hostname) - 1] = '\0';
1051
1052 (void)strlcpy(savehost, krb_get_phost(hostname), sizeof(savehost));
1053 savehost[sizeof(savehost) - 1] = '\0';
1054
1055 kerno = krb_mk_req(&ticket, "rcmd", savehost, lrealm, 33);
1056
1057 if (kerno == KDC_PR_UNKNOWN) {
1058 warnx("Warning: TGT not verified.");
1059 syslog(LOG_WARNING,
1060 "%s to %s%s, TGT not verified (%s); %s.%s not registered?",
1061 username, user, ontty(), krb_err_txt[kerno],
1062 "rcmd", savehost);
1063 } else if (kerno != KSUCCESS) {
1064 warnx("Unable to use TGT: %s", krb_err_txt[kerno]);
1065 syslog(LOG_WARNING, "failed su: %s to %s%s: %s",
1066 username, user, ontty(), krb_err_txt[kerno]);
1067 dest_tkt();
1068 return (1);
1069 } else {
1070 if (!(hp = gethostbyname(hostname))) {
1071 warnx("can't get addr of %s", hostname);
1072 dest_tkt();
1073 return (1);
1074 }
1075 memmove((char *)&faddr, (char *)hp->h_addr, sizeof(faddr));
1076
1077 if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
1078 &authdata, "")) != KSUCCESS) {
1079 warnx("kerberos: unable to verify rcmd ticket: %s",
1080 krb_err_txt[kerno]);
1081 syslog(LOG_WARNING,
1082 "failed su: %s to %s%s: %s", username,
1083 user, ontty(), krb_err_txt[kerno]);
1084 dest_tkt();
1085 return (1);
1086 }
1087 }
1088 return (0);
1089 }
1090
1091 static int
1092 koktologin(name, realm, toname)
1093 char *name, *realm, *toname;
1094 {
1095 return krb_kuserok(name,
1096 strcmp (toname, "root") == 0 ? "root" : "",
1097 realm,
1098 toname);
1099 }
1100 #endif
1101
1102 static int
1103 check_ingroup (gid, gname, user, ifempty)
1104 int gid;
1105 const char *gname;
1106 const char *user;
1107 int ifempty;
1108 {
1109 struct group *gr;
1110 char **g;
1111 #ifdef SU_INDIRECT_GROUP
1112 char **gr_mem;
1113 int n = 0;
1114 int i = 0;
1115 #endif
1116 int ok = 0;
1117
1118 if (gname == NULL)
1119 gr = getgrgid((gid_t) gid);
1120 else
1121 gr = getgrnam(gname);
1122
1123 /*
1124 * XXX we are relying on the fact that we only set ifempty when
1125 * calling to check for SU_GROUP and that is the only time a
1126 * missing group is acceptable.
1127 */
1128 if (gr == NULL)
1129 return ifempty;
1130 if (!*gr->gr_mem) /* empty */
1131 return ifempty;
1132
1133 /*
1134 * Ok, first see if user is in gr_mem
1135 */
1136 for (g = gr->gr_mem; *g; ++g) {
1137 if (strcmp(*g, user) == 0)
1138 return 1; /* ok */
1139 #ifdef SU_INDIRECT_GROUP
1140 ++n; /* count them */
1141 #endif
1142 }
1143 #ifdef SU_INDIRECT_GROUP
1144 /*
1145 * No.
1146 * Now we need to duplicate the gr_mem list, and recurse for
1147 * each member to see if it is a group, and if so whether user is
1148 * in it.
1149 */
1150 gr_mem = malloc((n + 1) * sizeof (char *));
1151 for (g = gr->gr_mem, i = 0; *g; ++g) {
1152 gr_mem[i] = strdup(*g);
1153 if (!gr_mem[i])
1154 err(1, "strdup");
1155 i++;
1156 }
1157 gr_mem[i++] = NULL;
1158
1159 for (g = gr_mem; ok == 0 && *g; ++g) {
1160 /*
1161 * If we get this far we don't accept empty/missing groups.
1162 */
1163 ok = check_ingroup(-1, *g, user, 0);
1164 }
1165 for (g = gr_mem; *g; ++g) {
1166 free(*g);
1167 }
1168 free(gr_mem);
1169 #endif
1170 return ok;
1171 }
1172 #endif /* USE_PAM */
1173