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