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