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