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.
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.
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]
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.
26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
27 /* All Rights Reserved */
29 /* Copyright (c) 1987, 1988 Microsoft Corporation */
30 /* All Rights Reserved */
33 * su [-] [name [arg ...]] change userid, `-' changes environment.
34 * If SULOG is defined, all attempts to su to another user are
36 * If CONSOLE is defined, all successful attempts to su to uid 0
37 * are also logged there.
39 * If su cannot create, open, or write entries into SULOG,
40 * (or on the CONSOLE, if defined), the entry will not
41 * be logged -- thus losing a record of the su's attempted
46 #include <sys/types.h>
48 #include <sys/param.h>
60 #include <sys/utsname.h>
67 #include <user_attr.h>
71 #include <bsm/adt_event.h>
73 #include <security/pam_appl.h>
75 #define PATH "/usr/bin:" /* path for users other than root */
76 #define SUPATH "/usr/sbin:/usr/bin" /* path for root */
77 #define SUPRMT "PS1=# " /* primary prompt for root */
81 #define EMBEDDED_NAME "embedded_su"
82 #define DEF_ATTEMPTS 3 /* attempts to change password */
83 #endif /* DYNAMIC_SU */
85 #define PW_FALSE 1 /* no password change */
86 #define PW_TRUE 2 /* successful password change */
87 #define PW_FAILED 3 /* failed password change */
90 * Intervals to sleep after failed su
96 #define DEFAULT_LOGIN "/etc/default/login"
97 #define DEFFILE "/etc/default/su"
100 char *Sulog
, *Console
;
104 * Locale variables to be propagated to "su -" environment
106 static char *initvar
;
107 static char *initenv
[] = {
108 "TZ", "LANG", "LC_CTYPE",
109 "LC_NUMERIC", "LC_TIME", "LC_COLLATE",
110 "LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0};
111 static char mail
[30] = { "MAIL=/var/mail/" };
113 static void envalt(void);
114 static void log(char *, char *, int);
117 enum messagemode
{ USAGE
, ERR
, WARN
};
118 static void message(enum messagemode
, char *, ...);
120 static char *alloc_vsprintf(const char *, va_list);
121 static char *tail(char *);
123 static void audit_success(int, struct passwd
*);
124 static void audit_logout(adt_session_data_t
*, au_event_t
);
125 static void audit_failure(int, struct passwd
*, char *, int);
128 static void validate(char *, int *);
129 static int legalenvvar(char *);
130 static int su_conv(int, struct pam_message
**, struct pam_response
**, void *);
131 static int emb_su_conv(int, struct pam_message
**, struct pam_response
**,
133 static void freeresponse(int, struct pam_response
**response
);
134 static struct pam_conv pam_conv
= {su_conv
, NULL
};
135 static struct pam_conv emb_pam_conv
= {emb_su_conv
, NULL
};
136 static void quotemsg(char *, ...);
137 static void readinitblock(void);
138 #else /* !DYNAMIC_SU */
139 static void update_audit(struct passwd
*pwd
);
140 #endif /* DYNAMIC_SU */
142 static pam_handle_t
*pamh
= NULL
; /* Authentication handle */
144 char pwdbuf
[1024]; /* buffer for getpwnam_r() */
145 char shell
[] = "/usr/bin/sh"; /* default shell */
146 char safe_shell
[] = "/sbin/sh"; /* "fallback" shell */
147 char su
[PATH_MAX
] = "su"; /* arg0 for exec of shprog */
148 char homedir
[PATH_MAX
] = "HOME=";
149 char logname
[20] = "LOGNAME=";
150 char *suprmt
= SUPRMT
;
151 char termtyp
[PATH_MAX
] = "TERM=";
153 char shelltyp
[PATH_MAX
] = "SHELL=";
155 char tznam
[PATH_MAX
];
156 char hzname
[10] = "HZ=";
157 char path
[PATH_MAX
] = "PATH=";
158 char supath
[PATH_MAX
] = "PATH=";
160 extern char **environ
;
162 char *username
; /* the invoker */
163 static int dosyslog
= 0; /* use syslog? */
167 boolean_t embedded
= B_FALSE
;
168 #endif /* DYNAMIC_SU */
171 main(int argc
, char **argv
)
175 char spbuf
[1024]; /* buffer for getspnam_r() */
177 #endif /* !DYNAMIC_SU */
184 char *dir
, *shprog
, *name
;
186 char *prog
= argv
[0];
188 int sleeptime
= SLEEPTIME
;
193 #endif /* DYNAMIC_SU */
194 int pw_change
= PW_FALSE
;
196 (void) setlocale(LC_ALL
, "");
197 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
198 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it wasn't */
200 (void) textdomain(TEXT_DOMAIN
);
202 myname
= tail(argv
[0]);
205 if (strcmp(myname
, EMBEDDED_NAME
) == 0) {
208 setbuf(stdout
, NULL
);
211 #endif /* DYNAMIC_SU */
213 if (argc
> 1 && *argv
[1] == '-') {
214 /* Explicitly check for just `-' (no trailing chars) */
215 if (strlen(argv
[1]) == 1) {
216 eflag
++; /* set eflag if `-' is specified */
221 gettext("Usage: %s [-] [ username [ arg ... ] ]"),
228 * Determine specified userid, get their password file entry,
229 * and set variables to values in password file entry fields.
233 * Usernames can't start with a `-', so we check for that to
234 * catch bad usage (like "su - -c ls").
236 if (*argv
[1] == '-') {
238 gettext("Usage: %s [-] [ username [ arg ... ] ]"),
242 nptr
= argv
[1]; /* use valid command-line username */
244 nptr
= "root"; /* use default "root" username */
246 if (defopen(DEFFILE
) == 0) {
248 if (Sulog
= defread("SULOG="))
249 Sulog
= strdup(Sulog
);
250 if (Console
= defread("CONSOLE="))
251 Console
= strdup(Console
);
252 if (Path
= defread("PATH="))
254 if (Supath
= defread("SUPATH="))
255 Supath
= strdup(Supath
);
256 if ((ptr
= defread("SYSLOG=")) != NULL
)
257 dosyslog
= strcmp(ptr
, "YES") == 0;
259 (void) defopen(NULL
);
261 (void) strlcat(path
, (Path
) ? Path
: PATH
, sizeof (path
));
262 (void) strlcat(supath
, (Supath
) ? Supath
: SUPATH
, sizeof (supath
));
264 if ((ttyn
= ttyname(0)) == NULL
)
265 if ((ttyn
= ttyname(1)) == NULL
)
266 if ((ttyn
= ttyname(2)) == NULL
)
268 if ((username
= cuserid(NULL
)) == NULL
)
272 * if Sulog defined, create SULOG, if it does not exist, with
273 * mode read/write user. Change owner and group to root
276 (void) close(open(Sulog
, O_WRONLY
| O_APPEND
| O_CREAT
,
278 (void) chown(Sulog
, (uid_t
)ROOT
, (gid_t
)ROOT
);
282 if (pam_start(embedded
? EMBEDDED_NAME
: "su", nptr
,
283 embedded
? &emb_pam_conv
: &pam_conv
, &pamh
) != PAM_SUCCESS
)
285 if (pam_set_item(pamh
, PAM_TTY
, ttyn
) != PAM_SUCCESS
)
287 if (getpwuid_r(getuid(), &pwd
, pwdbuf
, sizeof (pwdbuf
)) == NULL
||
288 pam_set_item(pamh
, PAM_AUSER
, pwd
.pw_name
) != PAM_SUCCESS
)
290 #endif /* DYNAMIC_SU */
292 openlog("su", LOG_CONS
, LOG_AUTH
);
297 * Use the same value of sleeptime and password required that
299 * This is obtained by reading the file /etc/default/login
300 * using the def*() functions
302 if (defopen(DEFAULT_LOGIN
) == 0) {
303 if ((ptr
= defread("SLEEPTIME=")) != NULL
) {
304 sleeptime
= atoi(ptr
);
305 if (sleeptime
< 0 || sleeptime
> 5)
306 sleeptime
= SLEEPTIME
;
309 if ((ptr
= defread("PASSREQ=")) != NULL
&&
310 strcasecmp("YES", ptr
) == 0)
311 pam_flags
|= PAM_DISALLOW_NULL_AUTHTOK
;
313 (void) defopen((char *)NULL
);
316 * Ignore SIGQUIT and SIGINT
318 (void) signal(SIGQUIT
, SIG_IGN
);
319 (void) signal(SIGINT
, SIG_IGN
);
321 /* call pam_authenticate() to authenticate the user through PAM */
322 if (getpwnam_r(nptr
, &pwd
, pwdbuf
, sizeof (pwdbuf
)) == NULL
)
323 retcode
= PAM_USER_UNKNOWN
;
324 else if ((flags
= (getuid() != (uid_t
)ROOT
)) != 0) {
325 retcode
= pam_authenticate(pamh
, pam_flags
);
326 } else /* root user does not need to authenticate */
327 retcode
= PAM_SUCCESS
;
329 if (retcode
!= PAM_SUCCESS
) {
331 * 1st step: audit and log the error.
333 * 3rd step: print out message to user.
335 /* don't let audit_failure distinguish a role here */
336 audit_failure(PW_FALSE
, NULL
, nptr
, retcode
);
338 case PAM_USER_UNKNOWN
:
340 (void) sleep(sleeptime
);
341 message(ERR
, gettext("Unknown id: %s"), nptr
);
346 log(Sulog
, nptr
, 0); /* log entry */
348 syslog(LOG_CRIT
, "'su %s' failed for %s on %s",
349 pwd
.pw_name
, username
, ttyn
);
351 (void) sleep(sleeptime
);
352 message(ERR
, gettext("Sorry"));
358 syslog(LOG_CRIT
, "'su %s' failed for %s on %s",
359 pwd
.pw_name
, username
, ttyn
);
361 (void) sleep(sleeptime
);
362 message(ERR
, gettext("Sorry"));
366 (void) signal(SIGQUIT
, SIG_DFL
);
367 (void) signal(SIGINT
, SIG_DFL
);
371 validate(username
, &pw_change
);
372 if (pam_setcred(pamh
, PAM_REINITIALIZE_CRED
) != PAM_SUCCESS
) {
373 message(ERR
, gettext("unable to set credentials"));
377 syslog(pwd
.pw_uid
== 0 ? LOG_NOTICE
: LOG_INFO
,
378 "'su %s' succeeded for %s on %s",
379 pwd
.pw_name
, username
, ttyn
);
381 (void) signal(SIGQUIT
, SIG_DFL
);
382 (void) signal(SIGINT
, SIG_DFL
);
383 #else /* !DYNAMIC_SU */
384 if ((getpwnam_r(nptr
, &pwd
, pwdbuf
, sizeof (pwdbuf
)) == NULL
) ||
385 (getspnam_r(nptr
, &sp
, spbuf
, sizeof (spbuf
)) == NULL
)) {
386 message(ERR
, gettext("Unknown id: %s"), nptr
);
387 audit_failure(PW_FALSE
, NULL
, nptr
, PAM_USER_UNKNOWN
);
393 * Prompt for password if invoking user is not root or
394 * if specified(new) user requires a password
396 if (sp
.sp_pwdp
[0] == '\0' || getuid() == (uid_t
)ROOT
)
398 password
= getpass(gettext("Password:"));
400 if ((strcmp(sp
.sp_pwdp
, crypt(password
, sp
.sp_pwdp
)) != 0)) {
401 /* clear password file entry */
402 (void) memset((void *)spbuf
, 0, sizeof (spbuf
));
404 log(Sulog
, nptr
, 0); /* log entry */
405 message(ERR
, gettext("Sorry"));
406 audit_failure(PW_FALSE
, NULL
, nptr
, PAM_AUTH_ERR
);
408 syslog(LOG_CRIT
, "'su %s' failed for %s on %s",
409 pwd
.pw_name
, username
, ttyn
);
413 /* clear password file entry */
414 (void) memset((void *)spbuf
, 0, sizeof (spbuf
));
416 /* update audit session in a non-pam environment */
419 syslog(pwd
.pw_uid
== 0 ? LOG_NOTICE
: LOG_INFO
,
420 "'su %s' succeeded for %s on %s",
421 pwd
.pw_name
, username
, ttyn
);
422 #endif /* DYNAMIC_SU */
424 audit_success(pw_change
, &pwd
);
427 dir
= strdup(pwd
.pw_dir
);
428 shprog
= strdup(pwd
.pw_shell
);
429 name
= strdup(pwd
.pw_name
);
432 log(Sulog
, nptr
, 1); /* log entry */
434 /* set user and group ids to specified user */
436 /* set the real (and effective) GID */
437 if (setgid(gid
) == -1) {
438 message(ERR
, gettext("Invalid GID"));
441 /* Initialize the supplementary group access list. */
444 if (initgroups(nptr
, gid
) == -1) {
447 /* set the real (and effective) UID */
448 if (setuid(uid
) == -1) {
449 message(ERR
, gettext("Invalid UID"));
454 * If new user's shell field is neither NULL nor equal to /usr/bin/sh,
457 * pshell = their shell
458 * su = [-]last component of shell's pathname
460 * Otherwise, set the shell to /usr/bin/sh and set argv[0] to '[-]su'.
462 if (shprog
[0] != '\0' && strcmp(shell
, shprog
) != 0) {
466 (void) strcpy(su
, eflag
? "-" : "");
468 if ((p
= strrchr(pshell
, '/')) != NULL
)
469 (void) strlcat(su
, p
+ 1, sizeof (su
));
471 (void) strlcat(su
, pshell
, sizeof (su
));
474 (void) strcpy(su
, eflag
? "-su" : "su");
478 * set environment variables for new user;
479 * arg0 for exec of shprog must now contain `-'
480 * so that environment of new user is given
486 if (strlen(dir
) == 0) {
487 (void) strcpy(dir
, "/");
488 message(WARN
, gettext("No directory! Using home=/"));
490 (void) strlcat(homedir
, dir
, sizeof (homedir
));
491 (void) strlcat(logname
, name
, sizeof (logname
));
492 if (hz
= getenv("HZ"))
493 (void) strlcat(hzname
, hz
, sizeof (hzname
));
495 (void) strlcat(shelltyp
, pshell
, sizeof (shelltyp
));
497 if (chdir(dir
) < 0) {
498 message(ERR
, gettext("No directory!"));
501 envinit
[envidx
= 0] = homedir
;
502 envinit
[++envidx
] = ((uid
== (uid_t
)ROOT
) ? supath
: path
);
503 envinit
[++envidx
] = logname
;
504 envinit
[++envidx
] = hzname
;
505 if ((term
= getenv("TERM")) != NULL
) {
506 (void) strlcat(termtyp
, term
, sizeof (termtyp
));
507 envinit
[++envidx
] = termtyp
;
509 envinit
[++envidx
] = shelltyp
;
511 (void) strlcat(mail
, name
, sizeof (mail
));
512 envinit
[++envidx
] = mail
;
515 * Fetch the relevant locale/TZ environment variables from
516 * the inherited environment.
518 * We have a priority here for setting TZ. If TZ is set in
519 * in the inherited environment, that value remains top
520 * priority. If the file /etc/default/login has TIMEZONE set,
521 * that has second highest priority.
524 for (j
= 0; initenv
[j
] != 0; j
++) {
525 if (initvar
= getenv(initenv
[j
])) {
528 * Skip over values beginning with '/' for
531 if (initvar
[0] == '/') continue;
533 if (strcmp(initenv
[j
], "TZ") == 0) {
534 (void) strcpy(tznam
, "TZ=");
535 (void) strlcat(tznam
, initvar
,
540 malloc(strlen(initenv
[j
])
547 (void) strcpy(var
, initenv
[j
]);
548 (void) strcat(var
, "=");
549 (void) strcat(var
, initvar
);
550 envinit
[++envidx
] = var
;
556 * Check if TZ was found. If not then try to read it from
557 * /etc/default/login.
559 if (tznam
[0] == '\0') {
560 if (defopen(DEFAULT_LOGIN
) == 0) {
561 if (initvar
= defread("TIMEZONE=")) {
562 (void) strcpy(tznam
, "TZ=");
563 (void) strlcat(tznam
, initvar
,
566 (void) defopen(NULL
);
570 if (tznam
[0] != '\0')
571 envinit
[++envidx
] = tznam
;
575 * set the PAM environment variables -
576 * check for legal environment variables
578 if ((pam_env
= pam_getenvlist(pamh
)) != 0) {
579 while (pam_env
[idx
] != 0) {
580 if (envidx
+ 2 < ELIM
&&
581 legalenvvar(pam_env
[idx
])) {
582 envinit
[++envidx
] = pam_env
[idx
];
587 #endif /* DYNAMIC_SU */
588 envinit
[++envidx
] = NULL
;
591 char **pp
= environ
, **qq
, *p
;
593 while ((p
= *pp
) != NULL
) {
594 if (*p
== 'L' && p
[1] == 'D' && p
[2] == '_') {
595 for (qq
= pp
; (*qq
= qq
[1]) != NULL
; qq
++)
597 /* pp is not advanced */
606 (void) pam_end(pamh
, PAM_SUCCESS
);
607 #endif /* DYNAMIC_SU */
610 * if new user is root:
611 * if CONSOLE defined, log entry there;
612 * if eflag not set, change environment to that of root.
614 if (uid
== (uid_t
)ROOT
) {
616 if (strcmp(ttyn
, Console
) != 0) {
617 (void) signal(SIGALRM
, to
);
619 log(Console
, nptr
, 1);
627 * Default for SIGCPU and SIGXFSZ. Shells inherit
628 * signal disposition from parent. And the
629 * shells should have default dispositions for these
632 (void) signal(SIGXCPU
, SIG_DFL
);
633 (void) signal(SIGXFSZ
, SIG_DFL
);
637 (void) puts("SUCCESS");
639 * After this point, we're no longer talking the
640 * embedded_su protocol, so turn it off.
644 #endif /* DYNAMIC_SU */
647 * if additional arguments, exec shell program with array
648 * of pointers to arguments:
649 * -> if shell = default, then su = [-]su
650 * -> if shell != default, then su = [-]last component of
653 * if no additional arguments, exec shell with arg0 of su
655 * -> if shell = default, then su = [-]su
656 * -> if shell != default, then su = [-]last component of
661 (void) execv(pshell
, &argv
[1]);
663 (void) execl(pshell
, su
, 0);
667 * Try to clean up after an administrator who has made a mistake
668 * configuring root's shell; if root's shell is other than /sbin/sh,
669 * try exec'ing /sbin/sh instead.
671 if ((uid
== (uid_t
)ROOT
) && (strcmp(name
, "root") == 0) &&
672 (strcmp(safe_shell
, pshell
) != 0)) {
674 gettext("No shell %s. Trying fallback shell %s."),
678 (void) strcpy(su
, "-sh");
679 (void) strlcpy(shelltyp
+ strlen("SHELL="),
680 safe_shell
, sizeof (shelltyp
) - strlen("SHELL="));
682 (void) strcpy(su
, "sh");
687 (void) execv(safe_shell
, &argv
[1]);
689 (void) execl(safe_shell
, su
, 0);
691 message(ERR
, gettext("Couldn't exec fallback shell %s: %s"),
692 safe_shell
, strerror(errno
));
694 message(ERR
, gettext("No shell"));
700 * Environment altering routine -
701 * This routine is called when a user is su'ing to root
702 * without specifying the - flag.
703 * The user's PATH and PS1 variables are reset
704 * to the correct value for root.
705 * All of the user's other environment variables retain
706 * their current values after the su (if they are exported).
712 * If user has PATH variable in their environment, change its value
713 * to /bin:/etc:/usr/bin ;
714 * if user does not have PATH variable, add it to the user's
716 * if either of the above fail, an error message is printed.
718 if (putenv(supath
) != 0) {
720 gettext("unable to obtain memory to expand environment"));
725 * If user has PROMPT variable in their environment, change its value
727 * if user does not have PROMPT variable, add it to the user's
729 * if either of the above fail, an error message is printed.
731 if (putenv(suprmt
) != 0) {
733 gettext("unable to obtain memory to expand environment"));
740 * where = SULOG or CONSOLE
741 * towho = specified user ( user being su'ed to )
742 * how = 0 if su attempt failed; 1 if su attempt succeeded
745 log(char *where
, char *towho
, int how
)
752 * open SULOG or CONSOLE - if open fails, return
754 if ((logf
= fopen(where
, "a")) == NULL
)
758 tmp
= localtime(&now
);
761 * write entry into SULOG or onto CONSOLE - if write fails, return
763 (void) fprintf(logf
, "SU %.2d/%.2d %.2d:%.2d %c %s %s-%s\n",
764 tmp
->tm_mon
+ 1, tmp
->tm_mday
, tmp
->tm_hour
, tmp
->tm_min
,
765 how
? '+' : '-', ttyn
+ sizeof ("/dev/") - 1, username
, towho
);
767 (void) fclose(logf
); /* close SULOG or CONSOLE */
776 * audit_success - audit successful su
778 * Entry process audit context established -- i.e., pam_setcred()
779 * or equivalent called.
780 * pw_change = PW_TRUE, if successful password change audit
782 * pwd = passwd entry for new user.
786 audit_success(int pw_change
, struct passwd
*pwd
)
788 adt_session_data_t
*ah
= NULL
;
789 adt_event_data_t
*event
;
790 au_event_t event_id
= ADT_su
;
791 userattr_t
*user_entry
;
794 if (adt_start_session(&ah
, NULL
, ADT_USE_PROC_DATA
) != 0) {
795 syslog(LOG_AUTH
| LOG_ALERT
,
796 "adt_start_session(ADT_su): %m");
799 if (((user_entry
= getusernam(pwd
->pw_name
)) != NULL
) &&
800 ((kva_value
= kva_match((kva_t
*)user_entry
->attr
,
801 USERATTR_TYPE_KW
)) != NULL
) &&
802 ((strcmp(kva_value
, USERATTR_TYPE_NONADMIN_KW
) == 0) ||
803 (strcmp(kva_value
, USERATTR_TYPE_ADMIN_KW
) == 0))) {
804 event_id
= ADT_role_login
;
806 free_userattr(user_entry
); /* OK to use, checks for NULL */
808 /* since proc uid/gid not yet updated */
809 if (adt_set_user(ah
, pwd
->pw_uid
, pwd
->pw_gid
, pwd
->pw_uid
,
810 pwd
->pw_gid
, NULL
, ADT_USER
) != 0) {
811 syslog(LOG_AUTH
| LOG_ERR
,
812 "adt_set_user(ADT_su, ADT_FAILURE): %m");
814 if ((event
= adt_alloc_event(ah
, event_id
)) == NULL
) {
815 syslog(LOG_AUTH
| LOG_ALERT
, "adt_alloc_event(ADT_su): %m");
816 } else if (adt_put_event(event
, ADT_SUCCESS
, ADT_SUCCESS
) != 0) {
817 syslog(LOG_AUTH
| LOG_ALERT
,
818 "adt_put_event(ADT_su, ADT_SUCCESS): %m");
821 if (pw_change
== PW_TRUE
) {
822 /* Also audit password change */
823 adt_free_event(event
);
824 if ((event
= adt_alloc_event(ah
, ADT_passwd
)) == NULL
) {
825 syslog(LOG_AUTH
| LOG_ALERT
,
826 "adt_alloc_event(ADT_passwd): %m");
827 } else if (adt_put_event(event
, ADT_SUCCESS
,
829 syslog(LOG_AUTH
| LOG_ALERT
,
830 "adt_put_event(ADT_passwd, ADT_SUCCESS): %m");
833 adt_free_event(event
);
835 * The preceeding code is a noop if audit isn't enabled,
836 * but, let's not make a new process when it's not necessary.
838 if (adt_audit_state(AUC_AUDITING
)) {
839 audit_logout(ah
, event_id
); /* fork to catch logout */
841 (void) adt_end_session(ah
);
846 * audit_logout - audit successful su logout
848 * Entry ah = Successful su audit handle
849 * event_id = su event ID: ADT_su, ADT_role_login
851 * Exit Errors are just ignored and we go on.
852 * su logout event written.
855 audit_logout(adt_session_data_t
*ah
, au_event_t event_id
)
857 adt_event_data_t
*event
;
858 int status
; /* wait status */
860 priv_set_t
*priv
; /* waiting process privs */
862 if (event_id
== ADT_su
) {
863 event_id
= ADT_su_logout
;
865 event_id
= ADT_role_logout
;
867 if ((event
= adt_alloc_event(ah
, event_id
)) == NULL
) {
868 syslog(LOG_AUTH
| LOG_ALERT
,
869 "adt_alloc_event(ADT_su_logout): %m");
872 if ((priv
= priv_allocset()) == NULL
) {
873 syslog(LOG_AUTH
| LOG_ALERT
,
874 "su audit_logout: could not alloc basic privs: %m");
875 adt_free_event(event
);
880 * The child returns and continues su processing.
881 * The parent's sole job is to wait for child exit, write the
882 * logout audit record, and replay the child's exit code.
884 if ((pid
= fork()) == 0) {
887 adt_free_event(event
);
894 syslog(LOG_AUTH
| LOG_ALERT
,
895 "su audit_logout: could not fork: %m");
896 adt_free_event(event
);
904 * When this routine is called, the current working
905 * directory is the unknown and there are unknown open
906 * files. For the waiting process, change the current
907 * directory to root and close open files so that
908 * directories can be unmounted if necessary.
910 if (chdir("/") != 0) {
911 syslog(LOG_AUTH
| LOG_ALERT
,
912 "su audit_logout: could not chdir /: %m");
915 * Reduce privileges to just those needed.
918 (void) priv_delset(priv
, PRIV_PROC_EXEC
);
919 (void) priv_delset(priv
, PRIV_PROC_FORK
);
920 (void) priv_delset(priv
, PRIV_PROC_INFO
);
921 (void) priv_delset(priv
, PRIV_PROC_SESSION
);
922 (void) priv_delset(priv
, PRIV_FILE_LINK_ANY
);
923 if ((priv_addset(priv
, PRIV_PROC_AUDIT
) != 0) ||
924 (setppriv(PRIV_SET
, PRIV_PERMITTED
, priv
) != 0)) {
925 syslog(LOG_AUTH
| LOG_ALERT
,
926 "su audit_logout: could not reduce privs: %m");
932 if (pid
!= waitpid(pid
, &status
, WUNTRACED
)) {
933 if (errno
== ECHILD
) {
935 * No existing child with the given pid. Lets
943 if (WIFEXITED(status
) || WIFSIGNALED(status
)) {
945 * The child shell exited or was terminated by
946 * a signal. Lets audit logout.
949 } else if (WIFSTOPPED(status
)) {
952 void (*sg_handler
)();
954 * The child shell has been stopped/suspended.
955 * We need to suspend here as well and pass down
956 * the control to the parent process.
958 sg_handler
= signal(WSTOPSIG(status
), SIG_DFL
);
959 (void) sigsend(P_PGID
, getpgrp(), WSTOPSIG(status
));
961 * We stop here. When resumed, mark the child
962 * shell group as foreground process group
963 * which gives the child shell a control over
964 * the controlling terminal.
966 (void) signal(WSTOPSIG(status
), sg_handler
);
969 if ((fd
= open("/dev/tty", O_RDWR
)) != -1) {
971 * Pass down the control over the controlling
972 * terminal iff we are in a foreground process
973 * group. Otherwise, we are in a background
974 * process group and the kernel will send
975 * SIGTTOU signal to stop us (by default).
977 if (tcgetpgrp(fd
) == getpgrp()) {
978 (void) tcsetpgrp(fd
, pgid
);
982 /* Wake up the child shell */
983 (void) sigsend(P_PGID
, pgid
, SIGCONT
);
987 (void) adt_put_event(event
, ADT_SUCCESS
, ADT_SUCCESS
);
988 adt_free_event(event
);
989 (void) adt_end_session(ah
);
990 exit(WEXITSTATUS(status
));
995 * audit_failure - audit failed su
997 * Entry New audit context not set.
998 * pw_change == PW_FALSE, if no password change requested.
999 * PW_FAILED, if failed password change audit
1001 * pwd = NULL, or password entry to use.
1002 * user = username entered. Add to record if pwd == NULL.
1003 * pamerr = PAM error code; reason for failure.
1007 audit_failure(int pw_change
, struct passwd
*pwd
, char *user
, int pamerr
)
1009 adt_session_data_t
*ah
; /* audit session handle */
1010 adt_event_data_t
*event
; /* event to generate */
1011 au_event_t event_id
= ADT_su
;
1012 userattr_t
*user_entry
;
1015 if (adt_start_session(&ah
, NULL
, ADT_USE_PROC_DATA
) != 0) {
1016 syslog(LOG_AUTH
| LOG_ALERT
,
1017 "adt_start_session(ADT_su, ADT_FAILURE): %m");
1022 /* target user authenticated, merge audit state */
1023 if (adt_set_user(ah
, pwd
->pw_uid
, pwd
->pw_gid
, pwd
->pw_uid
,
1024 pwd
->pw_gid
, NULL
, ADT_UPDATE
) != 0) {
1025 syslog(LOG_AUTH
| LOG_ERR
,
1026 "adt_set_user(ADT_su, ADT_FAILURE): %m");
1028 if (((user_entry
= getusernam(pwd
->pw_name
)) != NULL
) &&
1029 ((kva_value
= kva_match((kva_t
*)user_entry
->attr
,
1030 USERATTR_TYPE_KW
)) != NULL
) &&
1031 ((strcmp(kva_value
, USERATTR_TYPE_NONADMIN_KW
) == 0) ||
1032 (strcmp(kva_value
, USERATTR_TYPE_ADMIN_KW
) == 0))) {
1033 event_id
= ADT_role_login
;
1035 free_userattr(user_entry
); /* OK to use, checks for NULL */
1037 if ((event
= adt_alloc_event(ah
, event_id
)) == NULL
) {
1038 syslog(LOG_AUTH
| LOG_ALERT
,
1039 "adt_alloc_event(ADT_su, ADT_FAILURE): %m");
1043 * can't tell if user not found is a role, so always use su
1044 * If we do pass in pwd when the JNI is fixed, then can
1045 * distinguish and set name in both su and role_login
1049 * this should be "fail_user" rather than "message"
1050 * see adt_xml. The JNI breaks, so for now we leave
1053 event
->adt_su
.message
= user
;
1055 if (adt_put_event(event
, ADT_FAILURE
,
1056 ADT_FAIL_PAM
+ pamerr
) != 0) {
1057 syslog(LOG_AUTH
| LOG_ALERT
,
1058 "adt_put_event(ADT_su(ADT_FAIL, %s): %m",
1059 pam_strerror(pamh
, pamerr
));
1061 if (pw_change
!= PW_FALSE
) {
1062 /* Also audit password change failed */
1063 adt_free_event(event
);
1064 if ((event
= adt_alloc_event(ah
, ADT_passwd
)) == NULL
) {
1065 syslog(LOG_AUTH
| LOG_ALERT
,
1066 "su: adt_alloc_event(ADT_passwd): %m");
1067 } else if (adt_put_event(event
, ADT_FAILURE
,
1068 ADT_FAIL_PAM
+ pamerr
) != 0) {
1069 syslog(LOG_AUTH
| LOG_ALERT
,
1070 "su: adt_put_event(ADT_passwd, ADT_FAILURE): %m");
1073 adt_free_event(event
);
1074 (void) adt_end_session(ah
);
1080 * This is the conv (conversation) function called from
1081 * a PAM authentication module to print error messages
1082 * or garner information from the user.
1086 su_conv(int num_msg
, struct pam_message
**msg
, struct pam_response
**response
,
1089 struct pam_message
*m
;
1090 struct pam_response
*r
;
1093 char respbuf
[PAM_MAX_RESP_SIZE
];
1096 return (PAM_CONV_ERR
);
1098 *response
= (struct pam_response
*)calloc(num_msg
,
1099 sizeof (struct pam_response
));
1100 if (*response
== NULL
)
1101 return (PAM_BUF_ERR
);
1108 switch (m
->msg_style
) {
1110 case PAM_PROMPT_ECHO_OFF
:
1112 temp
= getpassphrase(m
->msg
);
1114 return (PAM_CONV_ERR
);
1116 r
->resp
= strdup(temp
);
1117 if (r
->resp
== NULL
) {
1118 freeresponse(num_msg
, response
);
1119 return (PAM_BUF_ERR
);
1124 case PAM_PROMPT_ECHO_ON
:
1125 if (m
->msg
!= NULL
) {
1126 (void) fputs(m
->msg
, stdout
);
1129 (void) fgets(respbuf
, sizeof (respbuf
), stdin
);
1130 temp
= strchr(respbuf
, '\n');
1134 r
->resp
= strdup(respbuf
);
1135 if (r
->resp
== NULL
) {
1136 freeresponse(num_msg
, response
);
1137 return (PAM_BUF_ERR
);
1142 if (m
->msg
!= NULL
) {
1143 (void) fputs(m
->msg
, stderr
);
1144 (void) fputs("\n", stderr
);
1149 if (m
->msg
!= NULL
) {
1150 (void) fputs(m
->msg
, stdout
);
1151 (void) fputs("\n", stdout
);
1161 return (PAM_SUCCESS
);
1166 * This is the conv (conversation) function called from
1167 * a PAM authentication module to print error messages
1168 * or garner information from the user.
1169 * This version is used for embedded_su.
1173 emb_su_conv(int num_msg
, struct pam_message
**msg
,
1174 struct pam_response
**response
, void *appdata_ptr
)
1176 struct pam_message
*m
;
1177 struct pam_response
*r
;
1180 char respbuf
[PAM_MAX_RESP_SIZE
];
1183 return (PAM_CONV_ERR
);
1185 *response
= (struct pam_response
*)calloc(num_msg
,
1186 sizeof (struct pam_response
));
1187 if (*response
== NULL
)
1188 return (PAM_BUF_ERR
);
1190 /* First, send the prompts */
1191 (void) printf("CONV %d\n", num_msg
);
1195 switch (m
->msg_style
) {
1197 case PAM_PROMPT_ECHO_OFF
:
1198 (void) puts("PAM_PROMPT_ECHO_OFF");
1201 case PAM_PROMPT_ECHO_ON
:
1202 (void) puts("PAM_PROMPT_ECHO_ON");
1206 (void) puts("PAM_ERROR_MSG");
1210 (void) puts("PAM_TEXT_INFO");
1211 /* fall through to msg_common */
1216 quotemsg("%s", m
->msg
);
1225 /* Next, collect the responses */
1231 switch (m
->msg_style
) {
1233 case PAM_PROMPT_ECHO_OFF
:
1234 case PAM_PROMPT_ECHO_ON
:
1235 (void) fgets(respbuf
, sizeof (respbuf
), stdin
);
1237 temp
= strchr(respbuf
, '\n');
1241 r
->resp
= strdup(respbuf
);
1242 if (r
->resp
== NULL
) {
1243 freeresponse(num_msg
, response
);
1244 return (PAM_BUF_ERR
);
1259 return (PAM_SUCCESS
);
1263 freeresponse(int num_msg
, struct pam_response
**response
)
1265 struct pam_response
*r
;
1268 /* free responses */
1270 for (i
= 0; i
< num_msg
; i
++, r
++) {
1271 if (r
->resp
!= NULL
) {
1272 /* Zap it in case it's a password */
1273 (void) memset(r
->resp
, '\0', strlen(r
->resp
));
1282 * Print a message, applying quoting for lines starting with '.'.
1284 * I18n note: \n is "safe" in all locales, and all locales use
1285 * a high-bit-set character to start multibyte sequences, so
1286 * scanning for a \n followed by a '.' is safe.
1289 quotemsg(char *fmt
, ...)
1298 msg
= alloc_vsprintf(fmt
, v
);
1302 for (p
= msg
; *p
!= '\0'; p
++) {
1305 (void) putchar('.');
1312 (void) putchar('\n');
1315 (void) putchar('.');
1316 (void) putchar('\n');
1320 * validate - Check that the account is valid for switching to.
1323 validate(char *usernam
, int *pw_change
)
1328 if ((error
= pam_acct_mgmt(pamh
, pam_flags
)) != PAM_SUCCESS
) {
1330 log(Sulog
, pwd
.pw_name
, 0); /* log entry */
1331 if (error
== PAM_NEW_AUTHTOK_REQD
) {
1333 message(ERR
, gettext("Password for user "
1334 "'%s' has expired"), pwd
.pw_name
);
1335 while ((error
= pam_chauthtok(pamh
,
1336 PAM_CHANGE_EXPIRED_AUTHTOK
)) != PAM_SUCCESS
) {
1337 if ((error
== PAM_AUTHTOK_ERR
||
1338 error
== PAM_TRY_AGAIN
) &&
1339 (tries
++ < DEF_ATTEMPTS
)) {
1342 message(ERR
, gettext("Sorry"));
1343 audit_failure(PW_FAILED
, &pwd
, NULL
, error
);
1346 "'su %s' failed for %s on %s",
1347 pwd
.pw_name
, usernam
, ttyn
);
1351 *pw_change
= PW_TRUE
;
1354 message(ERR
, gettext("Sorry"));
1355 audit_failure(PW_FALSE
, &pwd
, NULL
, error
);
1357 syslog(LOG_CRIT
, "'su %s' failed for %s on %s",
1358 pwd
.pw_name
, usernam
, ttyn
);
1365 static char *illegal
[] = {
1382 * legalenvvar - can PAM modules insert this environmental variable?
1386 legalenvvar(char *s
)
1390 for (p
= illegal
; *p
; p
++)
1391 if (strncmp(s
, *p
, strlen(*p
)) == 0)
1394 if (s
[0] == 'L' && s
[1] == 'D' && s
[2] == '_')
1401 * The embedded_su protocol allows the client application to supply
1402 * an initialization block terminated by a line with just a "." on it.
1404 * This initialization block is currently unused, reserved for future
1405 * expansion. Ignore it. This is made very slightly more complex by
1406 * the desire to cleanly ignore input lines of any length, while still
1407 * correctly detecting a line with just a "." on it.
1409 * I18n note: It appears that none of the Solaris-supported locales
1410 * use 0x0a for any purpose other than newline, so looking for '\n'
1412 * All locales use high-bit-set leadin characters for their multi-byte
1413 * sequences, so a line consisting solely of ".\n" is what it appears
1424 if (fgets(buf
, sizeof (buf
), stdin
) == NULL
)
1426 if (bol
&& strcmp(buf
, ".\n") == 0)
1428 bol
= (strchr(buf
, '\n') != NULL
);
1431 #else /* !DYNAMIC_SU */
1433 update_audit(struct passwd
*pwd
)
1435 adt_session_data_t
*ah
; /* audit session handle */
1437 if (adt_start_session(&ah
, NULL
, ADT_USE_PROC_DATA
) != 0) {
1438 message(ERR
, gettext("Sorry"));
1440 syslog(LOG_CRIT
, "'su %s' failed for %s "
1441 "cannot start audit session %m",
1442 pwd
->pw_name
, username
);
1446 if (adt_set_user(ah
, pwd
->pw_uid
, pwd
->pw_gid
, pwd
->pw_uid
,
1447 pwd
->pw_gid
, NULL
, ADT_UPDATE
) != 0) {
1449 syslog(LOG_CRIT
, "'su %s' failed for %s "
1450 "cannot update audit session %m",
1451 pwd
->pw_name
, username
);
1456 #endif /* DYNAMIC_SU */
1459 * Report an error, either a fatal one, a warning, or a usage message,
1460 * depending on the mode parameter.
1464 message(enum messagemode mode
, char *fmt
, ...)
1470 s
= alloc_vsprintf(fmt
, v
);
1476 (void) printf("CONV 1\n");
1477 (void) printf("PAM_ERROR_MSG\n");
1478 } else { /* ERR, USAGE */
1479 (void) printf("ERROR\n");
1481 if (mode
== USAGE
) {
1483 } else { /* ERR, WARN */
1484 quotemsg("%s: %s", myname
, s
);
1487 #endif /* DYNAMIC_SU */
1488 if (mode
== USAGE
) {
1489 (void) fprintf(stderr
, "%s\n", s
);
1490 } else { /* ERR, WARN */
1491 (void) fprintf(stderr
, "%s: %s\n", myname
, s
);
1495 #endif /* DYNAMIC_SU */
1501 * Return a pointer to the last path component of a.
1508 p
= strrchr(a
, '/');
1512 p
++; /* step over the '/' */
1518 alloc_vsprintf(const char *fmt
, va_list ap1
)
1526 * We need to scan the argument list twice. Save off a copy
1527 * of the argument list pointer(s) for the second pass. Note that
1528 * we are responsible for va_end'ing our copy.
1533 * vsnprintf into a dummy to get a length. One might
1534 * think that passing 0 as the length to snprintf would
1535 * do what we want, but it's defined not to.
1537 * Perhaps we should sprintf into a 100 character buffer
1538 * or something like that, to avoid two calls to snprintf
1541 n
= vsnprintf(buf
, sizeof (buf
), fmt
, ap2
);
1545 * Allocate an appropriately-sized buffer.
1553 (void) vsnprintf(s
, n
+1, fmt
, ap1
);