1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright 2012 Milan Jurik. All rights reserved.
24 * Copyright 2014 Nexenta Systems, Inc.
25 */
26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
27 /* All Rights Reserved */
28
29 /* Copyright (c) 1987, 1988 Microsoft Corporation */
30 /* All Rights Reserved */
31
32 /*
33 * su [-] [name [arg ...]] change userid, `-' changes environment.
34 * If SULOG is defined, all attempts to su to another user are
35 * logged there.
36 * If CONSOLE is defined, all successful attempts to su to uid 0
37 * are also logged there.
38 *
39 * If su cannot create, open, or write entries into SULOG,
40 * (or on the CONSOLE, if defined), the entry will not
41 * be logged -- thus losing a record of the su's attempted
42 * during this period.
43 */
44
45 #include <stdio.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <sys/param.h>
49 #include <unistd.h>
50 #include <stdlib.h>
51 #include <crypt.h>
52 #include <pwd.h>
53 #include <shadow.h>
54 #include <time.h>
55 #include <signal.h>
56 #include <fcntl.h>
57 #include <string.h>
58 #include <locale.h>
59 #include <syslog.h>
60 #include <sys/utsname.h>
61 #include <sys/wait.h>
62 #include <grp.h>
63 #include <deflt.h>
64 #include <limits.h>
65 #include <errno.h>
66 #include <stdarg.h>
67 #include <user_attr.h>
68 #include <priv.h>
69
70 #include <bsm/adt.h>
71 #include <bsm/adt_event.h>
72
73 #include <security/pam_appl.h>
74
75 #define PATH "/usr/bin:" /* path for users other than root */
76 #define SUPATH "/usr/sbin:/usr/bin" /* path for root */
77 #define SUPRMT "PS1=# " /* primary prompt for root */
78 #define ELIM 128
79 #define ROOT 0
80 #ifdef DYNAMIC_SU
81 #define EMBEDDED_NAME "embedded_su"
82 #define DEF_ATTEMPTS 3 /* attempts to change password */
83 #endif /* DYNAMIC_SU */
84
85 #define PW_FALSE 1 /* no password change */
86 #define PW_TRUE 2 /* successful password change */
87 #define PW_FAILED 3 /* failed password change */
88
89 /*
90 * Intervals to sleep after failed su
91 */
92 #ifndef SLEEPTIME
93 #define SLEEPTIME 4
94 #endif
95
96 #define DEFAULT_LOGIN "/etc/default/login"
97 #define DEFFILE "/etc/default/su"
98
99
100 char *Sulog, *Console;
101 char *Path, *Supath;
102
103 /*
104 * Locale variables to be propagated to "su -" environment
105 */
106 static char *initvar;
107 static char *initenv[] = {
108 "TZ", "LANG", "LC_CTYPE",
109 "LC_NUMERIC", "LC_TIME", "LC_COLLATE",
110 "LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0};
111 static char mail[30] = { "MAIL=/var/mail/" };
112
113 static void envalt(void);
114 static void log(char *, char *, int);
115 static void to(int);
116
117 enum messagemode { USAGE, ERR, WARN };
118 static void message(enum messagemode, char *, ...);
119
120 static char *alloc_vsprintf(const char *, va_list);
121 static char *tail(char *);
122
123 static void audit_success(int, struct passwd *);
124 static void audit_logout(adt_session_data_t *, au_event_t);
125 static void audit_failure(int, struct passwd *, char *, int);
126
127 #ifdef DYNAMIC_SU
128 static void validate(char *, int *);
129 static int legalenvvar(char *);
130 static int su_conv(int, struct pam_message **, struct pam_response **, void *);
131 static int emb_su_conv(int, struct pam_message **, struct pam_response **,
132 void *);
133 static void freeresponse(int, struct pam_response **response);
134 static struct pam_conv pam_conv = {su_conv, NULL};
135 static struct pam_conv emb_pam_conv = {emb_su_conv, NULL};
136 static void quotemsg(char *, ...);
137 static void readinitblock(void);
138 #else /* !DYNAMIC_SU */
139 static void update_audit(struct passwd *pwd);
140 #endif /* DYNAMIC_SU */
141
142 static pam_handle_t *pamh = NULL; /* Authentication handle */
143 struct passwd pwd;
144 char pwdbuf[1024]; /* buffer for getpwnam_r() */
145 char shell[] = "/usr/bin/sh"; /* default shell */
146 char safe_shell[] = "/sbin/sh"; /* "fallback" shell */
147 char su[PATH_MAX] = "su"; /* arg0 for exec of shprog */
148 char homedir[PATH_MAX] = "HOME=";
149 char logname[20] = "LOGNAME=";
150 char *suprmt = SUPRMT;
151 char termtyp[PATH_MAX] = "TERM=";
152 char *term;
153 char shelltyp[PATH_MAX] = "SHELL=";
154 char *hz;
155 char tznam[PATH_MAX];
156 char hzname[10] = "HZ=";
157 char path[PATH_MAX] = "PATH=";
158 char supath[PATH_MAX] = "PATH=";
159 char *envinit[ELIM];
160 extern char **environ;
161 char *ttyn;
162 char *username; /* the invoker */
163 static int dosyslog = 0; /* use syslog? */
164 char *myname;
165 #ifdef DYNAMIC_SU
166 int pam_flags = 0;
167 boolean_t embedded = B_FALSE;
168 #endif /* DYNAMIC_SU */
169
170 int
main(int argc,char ** argv)171 main(int argc, char **argv)
172 {
173 #ifndef DYNAMIC_SU
174 struct spwd sp;
175 char spbuf[1024]; /* buffer for getspnam_r() */
176 char *password;
177 #endif /* !DYNAMIC_SU */
178 char *nptr;
179 char *pshell;
180 int eflag = 0;
181 int envidx = 0;
182 uid_t uid;
183 gid_t gid;
184 char *dir, *shprog, *name;
185 char *ptr;
186 char *prog = argv[0];
187 #ifdef DYNAMIC_SU
188 int sleeptime = SLEEPTIME;
189 char **pam_env = 0;
190 int flags = 0;
191 int retcode;
192 int idx = 0;
193 #endif /* DYNAMIC_SU */
194 int pw_change = PW_FALSE;
195
196 (void) setlocale(LC_ALL, "");
197 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
198 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it wasn't */
199 #endif
200 (void) textdomain(TEXT_DOMAIN);
201
202 myname = tail(argv[0]);
203
204 #ifdef DYNAMIC_SU
205 if (strcmp(myname, EMBEDDED_NAME) == 0) {
206 embedded = B_TRUE;
207 setbuf(stdin, NULL);
208 setbuf(stdout, NULL);
209 readinitblock();
210 }
211 #endif /* DYNAMIC_SU */
212
213 if (argc > 1 && *argv[1] == '-') {
214 /* Explicitly check for just `-' (no trailing chars) */
215 if (strlen(argv[1]) == 1) {
216 eflag++; /* set eflag if `-' is specified */
217 argv++;
218 argc--;
219 } else {
220 message(USAGE,
221 gettext("Usage: %s [-] [ username [ arg ... ] ]"),
222 prog);
223 exit(1);
224 }
225 }
226
227 /*
228 * Determine specified userid, get their password file entry,
229 * and set variables to values in password file entry fields.
230 */
231 if (argc > 1) {
232 /*
233 * Usernames can't start with a `-', so we check for that to
234 * catch bad usage (like "su - -c ls").
235 */
236 if (*argv[1] == '-') {
237 message(USAGE,
238 gettext("Usage: %s [-] [ username [ arg ... ] ]"),
239 prog);
240 exit(1);
241 } else
242 nptr = argv[1]; /* use valid command-line username */
243 } else
244 nptr = "root"; /* use default "root" username */
245
246 if (defopen(DEFFILE) == 0) {
247
248 if (Sulog = defread("SULOG="))
249 Sulog = strdup(Sulog);
250 if (Console = defread("CONSOLE="))
251 Console = strdup(Console);
252 if (Path = defread("PATH="))
253 Path = strdup(Path);
254 if (Supath = defread("SUPATH="))
255 Supath = strdup(Supath);
256 if ((ptr = defread("SYSLOG=")) != NULL)
257 dosyslog = strcmp(ptr, "YES") == 0;
258
259 (void) defopen(NULL);
260 }
261 (void) strlcat(path, (Path) ? Path : PATH, sizeof (path));
262 (void) strlcat(supath, (Supath) ? Supath : SUPATH, sizeof (supath));
263
264 if ((ttyn = ttyname(0)) == NULL)
265 if ((ttyn = ttyname(1)) == NULL)
266 if ((ttyn = ttyname(2)) == NULL)
267 ttyn = "/dev/???";
268 if ((username = cuserid(NULL)) == NULL)
269 username = "(null)";
270
271 /*
272 * if Sulog defined, create SULOG, if it does not exist, with
273 * mode read/write user. Change owner and group to root
274 */
275 if (Sulog != NULL) {
276 (void) close(open(Sulog, O_WRONLY | O_APPEND | O_CREAT,
277 (S_IRUSR|S_IWUSR)));
278 (void) chown(Sulog, (uid_t)ROOT, (gid_t)ROOT);
279 }
280
281 #ifdef DYNAMIC_SU
282 if (pam_start(embedded ? EMBEDDED_NAME : "su", nptr,
283 embedded ? &emb_pam_conv : &pam_conv, &pamh) != PAM_SUCCESS)
284 exit(1);
285 if (pam_set_item(pamh, PAM_TTY, ttyn) != PAM_SUCCESS)
286 exit(1);
287 if (getpwuid_r(getuid(), &pwd, pwdbuf, sizeof (pwdbuf)) == NULL ||
288 pam_set_item(pamh, PAM_AUSER, pwd.pw_name) != PAM_SUCCESS)
289 exit(1);
290 #endif /* DYNAMIC_SU */
291
292 openlog("su", LOG_CONS, LOG_AUTH);
293
294 #ifdef DYNAMIC_SU
295
296 /*
297 * Use the same value of sleeptime and password required that
298 * login(1) uses.
299 * This is obtained by reading the file /etc/default/login
300 * using the def*() functions
301 */
302 if (defopen(DEFAULT_LOGIN) == 0) {
303 if ((ptr = defread("SLEEPTIME=")) != NULL) {
304 sleeptime = atoi(ptr);
305 if (sleeptime < 0 || sleeptime > 5)
306 sleeptime = SLEEPTIME;
307 }
308
309 if ((ptr = defread("PASSREQ=")) != NULL &&
310 strcasecmp("YES", ptr) == 0)
311 pam_flags |= PAM_DISALLOW_NULL_AUTHTOK;
312
313 (void) defopen((char *)NULL);
314 }
315 /*
316 * Ignore SIGQUIT and SIGINT
317 */
318 (void) signal(SIGQUIT, SIG_IGN);
319 (void) signal(SIGINT, SIG_IGN);
320
321 /* call pam_authenticate() to authenticate the user through PAM */
322 if (getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL)
323 retcode = PAM_USER_UNKNOWN;
324 else if ((flags = (getuid() != (uid_t)ROOT)) != 0) {
325 retcode = pam_authenticate(pamh, pam_flags);
326 } else /* root user does not need to authenticate */
327 retcode = PAM_SUCCESS;
328
329 if (retcode != PAM_SUCCESS) {
330 /*
331 * 1st step: audit and log the error.
332 * 2nd step: sleep.
333 * 3rd step: print out message to user.
334 */
335 /* don't let audit_failure distinguish a role here */
336 audit_failure(PW_FALSE, NULL, nptr, retcode);
337 switch (retcode) {
338 case PAM_USER_UNKNOWN:
339 closelog();
340 (void) sleep(sleeptime);
341 message(ERR, gettext("Unknown id: %s"), nptr);
342 break;
343
344 case PAM_AUTH_ERR:
345 if (Sulog != NULL)
346 log(Sulog, nptr, 0); /* log entry */
347 if (dosyslog)
348 syslog(LOG_CRIT, "'su %s' failed for %s on %s",
349 pwd.pw_name, username, ttyn);
350 closelog();
351 (void) sleep(sleeptime);
352 message(ERR, gettext("Sorry"));
353 break;
354
355 case PAM_CONV_ERR:
356 default:
357 if (dosyslog)
358 syslog(LOG_CRIT, "'su %s' failed for %s on %s",
359 pwd.pw_name, username, ttyn);
360 closelog();
361 (void) sleep(sleeptime);
362 message(ERR, gettext("Sorry"));
363 break;
364 }
365
366 (void) signal(SIGQUIT, SIG_DFL);
367 (void) signal(SIGINT, SIG_DFL);
368 exit(1);
369 }
370 if (flags)
371 validate(username, &pw_change);
372 if (pam_setcred(pamh, PAM_REINITIALIZE_CRED) != PAM_SUCCESS) {
373 message(ERR, gettext("unable to set credentials"));
374 exit(2);
375 }
376 if (dosyslog)
377 syslog(pwd.pw_uid == 0 ? LOG_NOTICE : LOG_INFO,
378 "'su %s' succeeded for %s on %s",
379 pwd.pw_name, username, ttyn);
380 closelog();
381 (void) signal(SIGQUIT, SIG_DFL);
382 (void) signal(SIGINT, SIG_DFL);
383 #else /* !DYNAMIC_SU */
384 if ((getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL) ||
385 (getspnam_r(nptr, &sp, spbuf, sizeof (spbuf)) == NULL)) {
386 message(ERR, gettext("Unknown id: %s"), nptr);
387 audit_failure(PW_FALSE, NULL, nptr, PAM_USER_UNKNOWN);
388 closelog();
389 exit(1);
390 }
391
392 /*
393 * Prompt for password if invoking user is not root or
394 * if specified(new) user requires a password
395 */
396 if (sp.sp_pwdp[0] == '\0' || getuid() == (uid_t)ROOT)
397 goto ok;
398 password = getpass(gettext("Password:"));
399
400 if ((strcmp(sp.sp_pwdp, crypt(password, sp.sp_pwdp)) != 0)) {
401 /* clear password file entry */
402 (void) memset((void *)spbuf, 0, sizeof (spbuf));
403 if (Sulog != NULL)
404 log(Sulog, nptr, 0); /* log entry */
405 message(ERR, gettext("Sorry"));
406 audit_failure(PW_FALSE, NULL, nptr, PAM_AUTH_ERR);
407 if (dosyslog)
408 syslog(LOG_CRIT, "'su %s' failed for %s on %s",
409 pwd.pw_name, username, ttyn);
410 closelog();
411 exit(2);
412 }
413 /* clear password file entry */
414 (void) memset((void *)spbuf, 0, sizeof (spbuf));
415 ok:
416 /* update audit session in a non-pam environment */
417 update_audit(&pwd);
418 if (dosyslog)
419 syslog(pwd.pw_uid == 0 ? LOG_NOTICE : LOG_INFO,
420 "'su %s' succeeded for %s on %s",
421 pwd.pw_name, username, ttyn);
422 #endif /* DYNAMIC_SU */
423
424 audit_success(pw_change, &pwd);
425 uid = pwd.pw_uid;
426 gid = pwd.pw_gid;
427 dir = strdup(pwd.pw_dir);
428 shprog = strdup(pwd.pw_shell);
429 name = strdup(pwd.pw_name);
430
431 if (Sulog != NULL)
432 log(Sulog, nptr, 1); /* log entry */
433
434 /* set user and group ids to specified user */
435
436 /* set the real (and effective) GID */
437 if (setgid(gid) == -1) {
438 message(ERR, gettext("Invalid GID"));
439 exit(2);
440 }
441 /* Initialize the supplementary group access list. */
442 if (!nptr)
443 exit(2);
444 if (initgroups(nptr, gid) == -1) {
445 exit(2);
446 }
447 /* set the real (and effective) UID */
448 if (setuid(uid) == -1) {
449 message(ERR, gettext("Invalid UID"));
450 exit(2);
451 }
452
453 /*
454 * If new user's shell field is neither NULL nor equal to /usr/bin/sh,
455 * set:
456 *
457 * pshell = their shell
458 * su = [-]last component of shell's pathname
459 *
460 * Otherwise, set the shell to /usr/bin/sh and set argv[0] to '[-]su'.
461 */
462 if (shprog[0] != '\0' && strcmp(shell, shprog) != 0) {
463 char *p;
464
465 pshell = shprog;
466 (void) strcpy(su, eflag ? "-" : "");
467
468 if ((p = strrchr(pshell, '/')) != NULL)
469 (void) strlcat(su, p + 1, sizeof (su));
470 else
471 (void) strlcat(su, pshell, sizeof (su));
472 } else {
473 pshell = shell;
474 (void) strcpy(su, eflag ? "-su" : "su");
475 }
476
477 /*
478 * set environment variables for new user;
479 * arg0 for exec of shprog must now contain `-'
480 * so that environment of new user is given
481 */
482 if (eflag) {
483 int j;
484 char *var;
485
486 if (strlen(dir) == 0) {
487 (void) strcpy(dir, "/");
488 message(WARN, gettext("No directory! Using home=/"));
489 }
490 (void) strlcat(homedir, dir, sizeof (homedir));
491 (void) strlcat(logname, name, sizeof (logname));
492 if (hz = getenv("HZ"))
493 (void) strlcat(hzname, hz, sizeof (hzname));
494
495 (void) strlcat(shelltyp, pshell, sizeof (shelltyp));
496
497 if (chdir(dir) < 0) {
498 message(ERR, gettext("No directory!"));
499 exit(1);
500 }
501 envinit[envidx = 0] = homedir;
502 envinit[++envidx] = ((uid == (uid_t)ROOT) ? supath : path);
503 envinit[++envidx] = logname;
504 envinit[++envidx] = hzname;
505 if ((term = getenv("TERM")) != NULL) {
506 (void) strlcat(termtyp, term, sizeof (termtyp));
507 envinit[++envidx] = termtyp;
508 }
509 envinit[++envidx] = shelltyp;
510
511 (void) strlcat(mail, name, sizeof (mail));
512 envinit[++envidx] = mail;
513
514 /*
515 * Fetch the relevant locale/TZ environment variables from
516 * the inherited environment.
517 *
518 * We have a priority here for setting TZ. If TZ is set in
519 * in the inherited environment, that value remains top
520 * priority. If the file /etc/default/login has TIMEZONE set,
521 * that has second highest priority.
522 */
523 tznam[0] = '\0';
524 for (j = 0; initenv[j] != 0; j++) {
525 if (initvar = getenv(initenv[j])) {
526
527 /*
528 * Skip over values beginning with '/' for
529 * security.
530 */
531 if (initvar[0] == '/') continue;
532
533 if (strcmp(initenv[j], "TZ") == 0) {
534 (void) strcpy(tznam, "TZ=");
535 (void) strlcat(tznam, initvar,
536 sizeof (tznam));
537
538 } else {
539 var = (char *)
540 malloc(strlen(initenv[j])
541 + strlen(initvar)
542 + 2);
543 if (var == NULL) {
544 perror("malloc");
545 exit(4);
546 }
547 (void) strcpy(var, initenv[j]);
548 (void) strcat(var, "=");
549 (void) strcat(var, initvar);
550 envinit[++envidx] = var;
551 }
552 }
553 }
554
555 /*
556 * Check if TZ was found. If not then try to read it from
557 * /etc/default/login.
558 */
559 if (tznam[0] == '\0') {
560 if (defopen(DEFAULT_LOGIN) == 0) {
561 if (initvar = defread("TIMEZONE=")) {
562 (void) strcpy(tznam, "TZ=");
563 (void) strlcat(tznam, initvar,
564 sizeof (tznam));
565 }
566 (void) defopen(NULL);
567 }
568 }
569
570 if (tznam[0] != '\0')
571 envinit[++envidx] = tznam;
572
573 #ifdef DYNAMIC_SU
574 /*
575 * set the PAM environment variables -
576 * check for legal environment variables
577 */
578 if ((pam_env = pam_getenvlist(pamh)) != 0) {
579 while (pam_env[idx] != 0) {
580 if (envidx + 2 < ELIM &&
581 legalenvvar(pam_env[idx])) {
582 envinit[++envidx] = pam_env[idx];
583 }
584 idx++;
585 }
586 }
587 #endif /* DYNAMIC_SU */
588 envinit[++envidx] = NULL;
589 environ = envinit;
590 } else {
591 char **pp = environ, **qq, *p;
592
593 while ((p = *pp) != NULL) {
594 if (*p == 'L' && p[1] == 'D' && p[2] == '_') {
595 for (qq = pp; (*qq = qq[1]) != NULL; qq++)
596 ;
597 /* pp is not advanced */
598 } else {
599 pp++;
600 }
601 }
602 }
603
604 #ifdef DYNAMIC_SU
605 if (pamh)
606 (void) pam_end(pamh, PAM_SUCCESS);
607 #endif /* DYNAMIC_SU */
608
609 /*
610 * if new user is root:
611 * if CONSOLE defined, log entry there;
612 * if eflag not set, change environment to that of root.
613 */
614 if (uid == (uid_t)ROOT) {
615 if (Console != NULL)
616 if (strcmp(ttyn, Console) != 0) {
617 (void) signal(SIGALRM, to);
618 (void) alarm(30);
619 log(Console, nptr, 1);
620 (void) alarm(0);
621 }
622 if (!eflag)
623 envalt();
624 }
625
626 /*
627 * Default for SIGCPU and SIGXFSZ. Shells inherit
628 * signal disposition from parent. And the
629 * shells should have default dispositions for these
630 * signals.
631 */
632 (void) signal(SIGXCPU, SIG_DFL);
633 (void) signal(SIGXFSZ, SIG_DFL);
634
635 #ifdef DYNAMIC_SU
636 if (embedded) {
637 (void) puts("SUCCESS");
638 /*
639 * After this point, we're no longer talking the
640 * embedded_su protocol, so turn it off.
641 */
642 embedded = B_FALSE;
643 }
644 #endif /* DYNAMIC_SU */
645
646 /*
647 * if additional arguments, exec shell program with array
648 * of pointers to arguments:
649 * -> if shell = default, then su = [-]su
650 * -> if shell != default, then su = [-]last component of
651 * shell's pathname
652 *
653 * if no additional arguments, exec shell with arg0 of su
654 * where:
655 * -> if shell = default, then su = [-]su
656 * -> if shell != default, then su = [-]last component of
657 * shell's pathname
658 */
659 if (argc > 2) {
660 argv[1] = su;
661 (void) execv(pshell, &argv[1]);
662 } else
663 (void) execl(pshell, su, 0);
664
665
666 /*
667 * Try to clean up after an administrator who has made a mistake
668 * configuring root's shell; if root's shell is other than /sbin/sh,
669 * try exec'ing /sbin/sh instead.
670 */
671 if ((uid == (uid_t)ROOT) && (strcmp(name, "root") == 0) &&
672 (strcmp(safe_shell, pshell) != 0)) {
673 message(WARN,
674 gettext("No shell %s. Trying fallback shell %s."),
675 pshell, safe_shell);
676
677 if (eflag) {
678 (void) strcpy(su, "-sh");
679 (void) strlcpy(shelltyp + strlen("SHELL="),
680 safe_shell, sizeof (shelltyp) - strlen("SHELL="));
681 } else {
682 (void) strcpy(su, "sh");
683 }
684
685 if (argc > 2) {
686 argv[1] = su;
687 (void) execv(safe_shell, &argv[1]);
688 } else {
689 (void) execl(safe_shell, su, 0);
690 }
691 message(ERR, gettext("Couldn't exec fallback shell %s: %s"),
692 safe_shell, strerror(errno));
693 } else {
694 message(ERR, gettext("No shell"));
695 }
696 return (3);
697 }
698
699 /*
700 * Environment altering routine -
701 * This routine is called when a user is su'ing to root
702 * without specifying the - flag.
703 * The user's PATH and PS1 variables are reset
704 * to the correct value for root.
705 * All of the user's other environment variables retain
706 * their current values after the su (if they are exported).
707 */
708 static void
envalt(void)709 envalt(void)
710 {
711 /*
712 * If user has PATH variable in their environment, change its value
713 * to /bin:/etc:/usr/bin ;
714 * if user does not have PATH variable, add it to the user's
715 * environment;
716 * if either of the above fail, an error message is printed.
717 */
718 if (putenv(supath) != 0) {
719 message(ERR,
720 gettext("unable to obtain memory to expand environment"));
721 exit(4);
722 }
723
724 /*
725 * If user has PROMPT variable in their environment, change its value
726 * to # ;
727 * if user does not have PROMPT variable, add it to the user's
728 * environment;
729 * if either of the above fail, an error message is printed.
730 */
731 if (putenv(suprmt) != 0) {
732 message(ERR,
733 gettext("unable to obtain memory to expand environment"));
734 exit(4);
735 }
736 }
737
738 /*
739 * Logging routine -
740 * where = SULOG or CONSOLE
741 * towho = specified user ( user being su'ed to )
742 * how = 0 if su attempt failed; 1 if su attempt succeeded
743 */
744 static void
log(char * where,char * towho,int how)745 log(char *where, char *towho, int how)
746 {
747 FILE *logf;
748 time_t now;
749 struct tm *tmp;
750
751 /*
752 * open SULOG or CONSOLE - if open fails, return
753 */
754 if ((logf = fopen(where, "a")) == NULL)
755 return;
756
757 now = time(0);
758 tmp = localtime(&now);
759
760 /*
761 * write entry into SULOG or onto CONSOLE - if write fails, return
762 */
763 (void) fprintf(logf, "SU %.2d/%.2d %.2d:%.2d %c %s %s-%s\n",
764 tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min,
765 how ? '+' : '-', ttyn + sizeof ("/dev/") - 1, username, towho);
766
767 (void) fclose(logf); /* close SULOG or CONSOLE */
768 }
769
770 /*ARGSUSED*/
771 static void
to(int sig)772 to(int sig)
773 {}
774
775 /*
776 * audit_success - audit successful su
777 *
778 * Entry process audit context established -- i.e., pam_setcred()
779 * or equivalent called.
780 * pw_change = PW_TRUE, if successful password change audit
781 * required.
782 * pwd = passwd entry for new user.
783 */
784
785 static void
audit_success(int pw_change,struct passwd * pwd)786 audit_success(int pw_change, struct passwd *pwd)
787 {
788 adt_session_data_t *ah = NULL;
789 adt_event_data_t *event;
790 au_event_t event_id = ADT_su;
791 userattr_t *user_entry;
792 char *kva_value;
793
794 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
795 syslog(LOG_AUTH | LOG_ALERT,
796 "adt_start_session(ADT_su): %m");
797 return;
798 }
799 if (((user_entry = getusernam(pwd->pw_name)) != NULL) &&
800 ((kva_value = kva_match((kva_t *)user_entry->attr,
801 USERATTR_TYPE_KW)) != NULL) &&
802 ((strcmp(kva_value, USERATTR_TYPE_NONADMIN_KW) == 0) ||
803 (strcmp(kva_value, USERATTR_TYPE_ADMIN_KW) == 0))) {
804 event_id = ADT_role_login;
805 }
806 free_userattr(user_entry); /* OK to use, checks for NULL */
807
808 /* since proc uid/gid not yet updated */
809 if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
810 pwd->pw_gid, NULL, ADT_USER) != 0) {
811 syslog(LOG_AUTH | LOG_ERR,
812 "adt_set_user(ADT_su, ADT_FAILURE): %m");
813 }
814 if ((event = adt_alloc_event(ah, event_id)) == NULL) {
815 syslog(LOG_AUTH | LOG_ALERT, "adt_alloc_event(ADT_su): %m");
816 } else if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
817 syslog(LOG_AUTH | LOG_ALERT,
818 "adt_put_event(ADT_su, ADT_SUCCESS): %m");
819 }
820
821 if (pw_change == PW_TRUE) {
822 /* Also audit password change */
823 adt_free_event(event);
824 if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) {
825 syslog(LOG_AUTH | LOG_ALERT,
826 "adt_alloc_event(ADT_passwd): %m");
827 } else if (adt_put_event(event, ADT_SUCCESS,
828 ADT_SUCCESS) != 0) {
829 syslog(LOG_AUTH | LOG_ALERT,
830 "adt_put_event(ADT_passwd, ADT_SUCCESS): %m");
831 }
832 }
833 adt_free_event(event);
834 /*
835 * The preceeding code is a noop if audit isn't enabled,
836 * but, let's not make a new process when it's not necessary.
837 */
838 if (adt_audit_state(AUC_AUDITING)) {
839 audit_logout(ah, event_id); /* fork to catch logout */
840 }
841 (void) adt_end_session(ah);
842 }
843
844
845 /*
846 * audit_logout - audit successful su logout
847 *
848 * Entry ah = Successful su audit handle
849 * event_id = su event ID: ADT_su, ADT_role_login
850 *
851 * Exit Errors are just ignored and we go on.
852 * su logout event written.
853 */
854 static void
audit_logout(adt_session_data_t * ah,au_event_t event_id)855 audit_logout(adt_session_data_t *ah, au_event_t event_id)
856 {
857 adt_event_data_t *event;
858 int status; /* wait status */
859 pid_t pid;
860 priv_set_t *priv; /* waiting process privs */
861
862 if (event_id == ADT_su) {
863 event_id = ADT_su_logout;
864 } else {
865 event_id = ADT_role_logout;
866 }
867 if ((event = adt_alloc_event(ah, event_id)) == NULL) {
868 syslog(LOG_AUTH | LOG_ALERT,
869 "adt_alloc_event(ADT_su_logout): %m");
870 return;
871 }
872 if ((priv = priv_allocset()) == NULL) {
873 syslog(LOG_AUTH | LOG_ALERT,
874 "su audit_logout: could not alloc basic privs: %m");
875 adt_free_event(event);
876 return;
877 }
878
879 /*
880 * The child returns and continues su processing.
881 * The parent's sole job is to wait for child exit, write the
882 * logout audit record, and replay the child's exit code.
883 */
884 if ((pid = fork()) == 0) {
885 /* child */
886
887 adt_free_event(event);
888 priv_freeset(priv);
889 return;
890 }
891 if (pid == -1) {
892 /* failure */
893
894 syslog(LOG_AUTH | LOG_ALERT,
895 "su audit_logout: could not fork: %m");
896 adt_free_event(event);
897 priv_freeset(priv);
898 return;
899 }
900
901 /* parent process */
902
903 /*
904 * When this routine is called, the current working
905 * directory is the unknown and there are unknown open
906 * files. For the waiting process, change the current
907 * directory to root and close open files so that
908 * directories can be unmounted if necessary.
909 */
910 if (chdir("/") != 0) {
911 syslog(LOG_AUTH | LOG_ALERT,
912 "su audit_logout: could not chdir /: %m");
913 }
914 /*
915 * Reduce privileges to just those needed.
916 */
917 priv_basicset(priv);
918 (void) priv_delset(priv, PRIV_PROC_EXEC);
919 (void) priv_delset(priv, PRIV_PROC_FORK);
920 (void) priv_delset(priv, PRIV_PROC_INFO);
921 (void) priv_delset(priv, PRIV_PROC_SESSION);
922 (void) priv_delset(priv, PRIV_FILE_LINK_ANY);
923 if ((priv_addset(priv, PRIV_PROC_AUDIT) != 0) ||
924 (setppriv(PRIV_SET, PRIV_PERMITTED, priv) != 0)) {
925 syslog(LOG_AUTH | LOG_ALERT,
926 "su audit_logout: could not reduce privs: %m");
927 }
928 closefrom(0);
929 priv_freeset(priv);
930
931 for (;;) {
932 if (pid != waitpid(pid, &status, WUNTRACED)) {
933 if (errno == ECHILD) {
934 /*
935 * No existing child with the given pid. Lets
936 * audit the logout.
937 */
938 break;
939 }
940 continue;
941 }
942
943 if (WIFEXITED(status) || WIFSIGNALED(status)) {
944 /*
945 * The child shell exited or was terminated by
946 * a signal. Lets audit logout.
947 */
948 break;
949 } else if (WIFSTOPPED(status)) {
950 pid_t pgid;
951 int fd;
952 void (*sg_handler)();
953 /*
954 * The child shell has been stopped/suspended.
955 * We need to suspend here as well and pass down
956 * the control to the parent process.
957 */
958 sg_handler = signal(WSTOPSIG(status), SIG_DFL);
959 (void) sigsend(P_PGID, getpgrp(), WSTOPSIG(status));
960 /*
961 * We stop here. When resumed, mark the child
962 * shell group as foreground process group
963 * which gives the child shell a control over
964 * the controlling terminal.
965 */
966 (void) signal(WSTOPSIG(status), sg_handler);
967
968 pgid = getpgid(pid);
969 if ((fd = open("/dev/tty", O_RDWR)) != -1) {
970 /*
971 * Pass down the control over the controlling
972 * terminal iff we are in a foreground process
973 * group. Otherwise, we are in a background
974 * process group and the kernel will send
975 * SIGTTOU signal to stop us (by default).
976 */
977 if (tcgetpgrp(fd) == getpgrp()) {
978 (void) tcsetpgrp(fd, pgid);
979 }
980 (void) close(fd);
981 }
982 /* Wake up the child shell */
983 (void) sigsend(P_PGID, pgid, SIGCONT);
984 }
985 }
986
987 (void) adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS);
988 adt_free_event(event);
989 (void) adt_end_session(ah);
990 exit(WEXITSTATUS(status));
991 }
992
993
994 /*
995 * audit_failure - audit failed su
996 *
997 * Entry New audit context not set.
998 * pw_change == PW_FALSE, if no password change requested.
999 * PW_FAILED, if failed password change audit
1000 * required.
1001 * pwd = NULL, or password entry to use.
1002 * user = username entered. Add to record if pwd == NULL.
1003 * pamerr = PAM error code; reason for failure.
1004 */
1005
1006 static void
audit_failure(int pw_change,struct passwd * pwd,char * user,int pamerr)1007 audit_failure(int pw_change, struct passwd *pwd, char *user, int pamerr)
1008 {
1009 adt_session_data_t *ah; /* audit session handle */
1010 adt_event_data_t *event; /* event to generate */
1011 au_event_t event_id = ADT_su;
1012 userattr_t *user_entry;
1013 char *kva_value;
1014
1015 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
1016 syslog(LOG_AUTH | LOG_ALERT,
1017 "adt_start_session(ADT_su, ADT_FAILURE): %m");
1018 return;
1019 }
1020
1021 if (pwd != NULL) {
1022 /* target user authenticated, merge audit state */
1023 if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
1024 pwd->pw_gid, NULL, ADT_UPDATE) != 0) {
1025 syslog(LOG_AUTH | LOG_ERR,
1026 "adt_set_user(ADT_su, ADT_FAILURE): %m");
1027 }
1028 if (((user_entry = getusernam(pwd->pw_name)) != NULL) &&
1029 ((kva_value = kva_match((kva_t *)user_entry->attr,
1030 USERATTR_TYPE_KW)) != NULL) &&
1031 ((strcmp(kva_value, USERATTR_TYPE_NONADMIN_KW) == 0) ||
1032 (strcmp(kva_value, USERATTR_TYPE_ADMIN_KW) == 0))) {
1033 event_id = ADT_role_login;
1034 }
1035 free_userattr(user_entry); /* OK to use, checks for NULL */
1036 }
1037 if ((event = adt_alloc_event(ah, event_id)) == NULL) {
1038 syslog(LOG_AUTH | LOG_ALERT,
1039 "adt_alloc_event(ADT_su, ADT_FAILURE): %m");
1040 return;
1041 }
1042 /*
1043 * can't tell if user not found is a role, so always use su
1044 * If we do pass in pwd when the JNI is fixed, then can
1045 * distinguish and set name in both su and role_login
1046 */
1047 if (pwd == NULL) {
1048 /*
1049 * this should be "fail_user" rather than "message"
1050 * see adt_xml. The JNI breaks, so for now we leave
1051 * this alone.
1052 */
1053 event->adt_su.message = user;
1054 }
1055 if (adt_put_event(event, ADT_FAILURE,
1056 ADT_FAIL_PAM + pamerr) != 0) {
1057 syslog(LOG_AUTH | LOG_ALERT,
1058 "adt_put_event(ADT_su(ADT_FAIL, %s): %m",
1059 pam_strerror(pamh, pamerr));
1060 }
1061 if (pw_change != PW_FALSE) {
1062 /* Also audit password change failed */
1063 adt_free_event(event);
1064 if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) {
1065 syslog(LOG_AUTH | LOG_ALERT,
1066 "su: adt_alloc_event(ADT_passwd): %m");
1067 } else if (adt_put_event(event, ADT_FAILURE,
1068 ADT_FAIL_PAM + pamerr) != 0) {
1069 syslog(LOG_AUTH | LOG_ALERT,
1070 "su: adt_put_event(ADT_passwd, ADT_FAILURE): %m");
1071 }
1072 }
1073 adt_free_event(event);
1074 (void) adt_end_session(ah);
1075 }
1076
1077 #ifdef DYNAMIC_SU
1078 /*
1079 * su_conv():
1080 * This is the conv (conversation) function called from
1081 * a PAM authentication module to print error messages
1082 * or garner information from the user.
1083 */
1084 /*ARGSUSED*/
1085 static int
su_conv(int num_msg,struct pam_message ** msg,struct pam_response ** response,void * appdata_ptr)1086 su_conv(int num_msg, struct pam_message **msg, struct pam_response **response,
1087 void *appdata_ptr)
1088 {
1089 struct pam_message *m;
1090 struct pam_response *r;
1091 char *temp;
1092 int k;
1093 char respbuf[PAM_MAX_RESP_SIZE];
1094
1095 if (num_msg <= 0)
1096 return (PAM_CONV_ERR);
1097
1098 *response = (struct pam_response *)calloc(num_msg,
1099 sizeof (struct pam_response));
1100 if (*response == NULL)
1101 return (PAM_BUF_ERR);
1102
1103 k = num_msg;
1104 m = *msg;
1105 r = *response;
1106 while (k--) {
1107
1108 switch (m->msg_style) {
1109
1110 case PAM_PROMPT_ECHO_OFF:
1111 errno = 0;
1112 temp = getpassphrase(m->msg);
1113 if (errno == EINTR)
1114 return (PAM_CONV_ERR);
1115 if (temp != NULL) {
1116 r->resp = strdup(temp);
1117 if (r->resp == NULL) {
1118 freeresponse(num_msg, response);
1119 return (PAM_BUF_ERR);
1120 }
1121 }
1122 break;
1123
1124 case PAM_PROMPT_ECHO_ON:
1125 if (m->msg != NULL) {
1126 (void) fputs(m->msg, stdout);
1127 }
1128
1129 (void) fgets(respbuf, sizeof (respbuf), stdin);
1130 temp = strchr(respbuf, '\n');
1131 if (temp != NULL)
1132 *temp = '\0';
1133
1134 r->resp = strdup(respbuf);
1135 if (r->resp == NULL) {
1136 freeresponse(num_msg, response);
1137 return (PAM_BUF_ERR);
1138 }
1139 break;
1140
1141 case PAM_ERROR_MSG:
1142 if (m->msg != NULL) {
1143 (void) fputs(m->msg, stderr);
1144 (void) fputs("\n", stderr);
1145 }
1146 break;
1147
1148 case PAM_TEXT_INFO:
1149 if (m->msg != NULL) {
1150 (void) fputs(m->msg, stdout);
1151 (void) fputs("\n", stdout);
1152 }
1153 break;
1154
1155 default:
1156 break;
1157 }
1158 m++;
1159 r++;
1160 }
1161 return (PAM_SUCCESS);
1162 }
1163
1164 /*
1165 * emb_su_conv():
1166 * This is the conv (conversation) function called from
1167 * a PAM authentication module to print error messages
1168 * or garner information from the user.
1169 * This version is used for embedded_su.
1170 */
1171 /*ARGSUSED*/
1172 static int
emb_su_conv(int num_msg,struct pam_message ** msg,struct pam_response ** response,void * appdata_ptr)1173 emb_su_conv(int num_msg, struct pam_message **msg,
1174 struct pam_response **response, void *appdata_ptr)
1175 {
1176 struct pam_message *m;
1177 struct pam_response *r;
1178 char *temp;
1179 int k;
1180 char respbuf[PAM_MAX_RESP_SIZE];
1181
1182 if (num_msg <= 0)
1183 return (PAM_CONV_ERR);
1184
1185 *response = (struct pam_response *)calloc(num_msg,
1186 sizeof (struct pam_response));
1187 if (*response == NULL)
1188 return (PAM_BUF_ERR);
1189
1190 /* First, send the prompts */
1191 (void) printf("CONV %d\n", num_msg);
1192 k = num_msg;
1193 m = *msg;
1194 while (k--) {
1195 switch (m->msg_style) {
1196
1197 case PAM_PROMPT_ECHO_OFF:
1198 (void) puts("PAM_PROMPT_ECHO_OFF");
1199 goto msg_common;
1200
1201 case PAM_PROMPT_ECHO_ON:
1202 (void) puts("PAM_PROMPT_ECHO_ON");
1203 goto msg_common;
1204
1205 case PAM_ERROR_MSG:
1206 (void) puts("PAM_ERROR_MSG");
1207 goto msg_common;
1208
1209 case PAM_TEXT_INFO:
1210 (void) puts("PAM_TEXT_INFO");
1211 /* fall through to msg_common */
1212 msg_common:
1213 if (m->msg == NULL)
1214 quotemsg(NULL);
1215 else
1216 quotemsg("%s", m->msg);
1217 break;
1218
1219 default:
1220 break;
1221 }
1222 m++;
1223 }
1224
1225 /* Next, collect the responses */
1226 k = num_msg;
1227 m = *msg;
1228 r = *response;
1229 while (k--) {
1230
1231 switch (m->msg_style) {
1232
1233 case PAM_PROMPT_ECHO_OFF:
1234 case PAM_PROMPT_ECHO_ON:
1235 (void) fgets(respbuf, sizeof (respbuf), stdin);
1236
1237 temp = strchr(respbuf, '\n');
1238 if (temp != NULL)
1239 *temp = '\0';
1240
1241 r->resp = strdup(respbuf);
1242 if (r->resp == NULL) {
1243 freeresponse(num_msg, response);
1244 return (PAM_BUF_ERR);
1245 }
1246
1247 break;
1248
1249 case PAM_ERROR_MSG:
1250 case PAM_TEXT_INFO:
1251 break;
1252
1253 default:
1254 break;
1255 }
1256 m++;
1257 r++;
1258 }
1259 return (PAM_SUCCESS);
1260 }
1261
1262 static void
freeresponse(int num_msg,struct pam_response ** response)1263 freeresponse(int num_msg, struct pam_response **response)
1264 {
1265 struct pam_response *r;
1266 int i;
1267
1268 /* free responses */
1269 r = *response;
1270 for (i = 0; i < num_msg; i++, r++) {
1271 if (r->resp != NULL) {
1272 /* Zap it in case it's a password */
1273 (void) memset(r->resp, '\0', strlen(r->resp));
1274 free(r->resp);
1275 }
1276 }
1277 free(*response);
1278 *response = NULL;
1279 }
1280
1281 /*
1282 * Print a message, applying quoting for lines starting with '.'.
1283 *
1284 * I18n note: \n is "safe" in all locales, and all locales use
1285 * a high-bit-set character to start multibyte sequences, so
1286 * scanning for a \n followed by a '.' is safe.
1287 */
1288 static void
quotemsg(char * fmt,...)1289 quotemsg(char *fmt, ...)
1290 {
1291 if (fmt != NULL) {
1292 char *msg;
1293 char *p;
1294 boolean_t bol;
1295 va_list v;
1296
1297 va_start(v, fmt);
1298 msg = alloc_vsprintf(fmt, v);
1299 va_end(v);
1300
1301 bol = B_TRUE;
1302 for (p = msg; *p != '\0'; p++) {
1303 if (bol) {
1304 if (*p == '.')
1305 (void) putchar('.');
1306 bol = B_FALSE;
1307 }
1308 (void) putchar(*p);
1309 if (*p == '\n')
1310 bol = B_TRUE;
1311 }
1312 (void) putchar('\n');
1313 free(msg);
1314 }
1315 (void) putchar('.');
1316 (void) putchar('\n');
1317 }
1318
1319 /*
1320 * validate - Check that the account is valid for switching to.
1321 */
1322 static void
validate(char * usernam,int * pw_change)1323 validate(char *usernam, int *pw_change)
1324 {
1325 int error;
1326 int tries;
1327
1328 if ((error = pam_acct_mgmt(pamh, pam_flags)) != PAM_SUCCESS) {
1329 if (Sulog != NULL)
1330 log(Sulog, pwd.pw_name, 0); /* log entry */
1331 if (error == PAM_NEW_AUTHTOK_REQD) {
1332 tries = 0;
1333 message(ERR, gettext("Password for user "
1334 "'%s' has expired"), pwd.pw_name);
1335 while ((error = pam_chauthtok(pamh,
1336 PAM_CHANGE_EXPIRED_AUTHTOK)) != PAM_SUCCESS) {
1337 if ((error == PAM_AUTHTOK_ERR ||
1338 error == PAM_TRY_AGAIN) &&
1339 (tries++ < DEF_ATTEMPTS)) {
1340 continue;
1341 }
1342 message(ERR, gettext("Sorry"));
1343 audit_failure(PW_FAILED, &pwd, NULL, error);
1344 if (dosyslog)
1345 syslog(LOG_CRIT,
1346 "'su %s' failed for %s on %s",
1347 pwd.pw_name, usernam, ttyn);
1348 closelog();
1349 exit(1);
1350 }
1351 *pw_change = PW_TRUE;
1352 return;
1353 } else {
1354 message(ERR, gettext("Sorry"));
1355 audit_failure(PW_FALSE, &pwd, NULL, error);
1356 if (dosyslog)
1357 syslog(LOG_CRIT, "'su %s' failed for %s on %s",
1358 pwd.pw_name, usernam, ttyn);
1359 closelog();
1360 exit(3);
1361 }
1362 }
1363 }
1364
1365 static char *illegal[] = {
1366 "SHELL=",
1367 "HOME=",
1368 "LOGNAME=",
1369 #ifndef NO_MAIL
1370 "MAIL=",
1371 #endif
1372 "CDPATH=",
1373 "IFS=",
1374 "PATH=",
1375 "TZ=",
1376 "HZ=",
1377 "TERM=",
1378 0
1379 };
1380
1381 /*
1382 * legalenvvar - can PAM modules insert this environmental variable?
1383 */
1384
1385 static int
legalenvvar(char * s)1386 legalenvvar(char *s)
1387 {
1388 register char **p;
1389
1390 for (p = illegal; *p; p++)
1391 if (strncmp(s, *p, strlen(*p)) == 0)
1392 return (0);
1393
1394 if (s[0] == 'L' && s[1] == 'D' && s[2] == '_')
1395 return (0);
1396
1397 return (1);
1398 }
1399
1400 /*
1401 * The embedded_su protocol allows the client application to supply
1402 * an initialization block terminated by a line with just a "." on it.
1403 *
1404 * This initialization block is currently unused, reserved for future
1405 * expansion. Ignore it. This is made very slightly more complex by
1406 * the desire to cleanly ignore input lines of any length, while still
1407 * correctly detecting a line with just a "." on it.
1408 *
1409 * I18n note: It appears that none of the Solaris-supported locales
1410 * use 0x0a for any purpose other than newline, so looking for '\n'
1411 * seems safe.
1412 * All locales use high-bit-set leadin characters for their multi-byte
1413 * sequences, so a line consisting solely of ".\n" is what it appears
1414 * to be.
1415 */
1416 static void
readinitblock(void)1417 readinitblock(void)
1418 {
1419 char buf[100];
1420 boolean_t bol;
1421
1422 bol = B_TRUE;
1423 for (;;) {
1424 if (fgets(buf, sizeof (buf), stdin) == NULL)
1425 return;
1426 if (bol && strcmp(buf, ".\n") == 0)
1427 return;
1428 bol = (strchr(buf, '\n') != NULL);
1429 }
1430 }
1431 #else /* !DYNAMIC_SU */
1432 static void
update_audit(struct passwd * pwd)1433 update_audit(struct passwd *pwd)
1434 {
1435 adt_session_data_t *ah; /* audit session handle */
1436
1437 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
1438 message(ERR, gettext("Sorry"));
1439 if (dosyslog)
1440 syslog(LOG_CRIT, "'su %s' failed for %s "
1441 "cannot start audit session %m",
1442 pwd->pw_name, username);
1443 closelog();
1444 exit(2);
1445 }
1446 if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
1447 pwd->pw_gid, NULL, ADT_UPDATE) != 0) {
1448 if (dosyslog)
1449 syslog(LOG_CRIT, "'su %s' failed for %s "
1450 "cannot update audit session %m",
1451 pwd->pw_name, username);
1452 closelog();
1453 exit(2);
1454 }
1455 }
1456 #endif /* DYNAMIC_SU */
1457
1458 /*
1459 * Report an error, either a fatal one, a warning, or a usage message,
1460 * depending on the mode parameter.
1461 */
1462 /*ARGSUSED*/
1463 static void
message(enum messagemode mode,char * fmt,...)1464 message(enum messagemode mode, char *fmt, ...)
1465 {
1466 char *s;
1467 va_list v;
1468
1469 va_start(v, fmt);
1470 s = alloc_vsprintf(fmt, v);
1471 va_end(v);
1472
1473 #ifdef DYNAMIC_SU
1474 if (embedded) {
1475 if (mode == WARN) {
1476 (void) printf("CONV 1\n");
1477 (void) printf("PAM_ERROR_MSG\n");
1478 } else { /* ERR, USAGE */
1479 (void) printf("ERROR\n");
1480 }
1481 if (mode == USAGE) {
1482 quotemsg("%s", s);
1483 } else { /* ERR, WARN */
1484 quotemsg("%s: %s", myname, s);
1485 }
1486 } else {
1487 #endif /* DYNAMIC_SU */
1488 if (mode == USAGE) {
1489 (void) fprintf(stderr, "%s\n", s);
1490 } else { /* ERR, WARN */
1491 (void) fprintf(stderr, "%s: %s\n", myname, s);
1492 }
1493 #ifdef DYNAMIC_SU
1494 }
1495 #endif /* DYNAMIC_SU */
1496
1497 free(s);
1498 }
1499
1500 /*
1501 * Return a pointer to the last path component of a.
1502 */
1503 static char *
tail(char * a)1504 tail(char *a)
1505 {
1506 char *p;
1507
1508 p = strrchr(a, '/');
1509 if (p == NULL)
1510 p = a;
1511 else
1512 p++; /* step over the '/' */
1513
1514 return (p);
1515 }
1516
1517 static char *
alloc_vsprintf(const char * fmt,va_list ap1)1518 alloc_vsprintf(const char *fmt, va_list ap1)
1519 {
1520 va_list ap2;
1521 int n;
1522 char buf[1];
1523 char *s;
1524
1525 /*
1526 * We need to scan the argument list twice. Save off a copy
1527 * of the argument list pointer(s) for the second pass. Note that
1528 * we are responsible for va_end'ing our copy.
1529 */
1530 va_copy(ap2, ap1);
1531
1532 /*
1533 * vsnprintf into a dummy to get a length. One might
1534 * think that passing 0 as the length to snprintf would
1535 * do what we want, but it's defined not to.
1536 *
1537 * Perhaps we should sprintf into a 100 character buffer
1538 * or something like that, to avoid two calls to snprintf
1539 * in most cases.
1540 */
1541 n = vsnprintf(buf, sizeof (buf), fmt, ap2);
1542 va_end(ap2);
1543
1544 /*
1545 * Allocate an appropriately-sized buffer.
1546 */
1547 s = malloc(n + 1);
1548 if (s == NULL) {
1549 perror("malloc");
1550 exit(4);
1551 }
1552
1553 (void) vsnprintf(s, n+1, fmt, ap1);
1554
1555 return (s);
1556 }
1557