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