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
;
195 struct passwd
*result
;
197 (void) setlocale(LC_ALL
, "");
198 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
199 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it wasn't */
201 (void) textdomain(TEXT_DOMAIN
);
203 myname
= tail(argv
[0]);
206 if (strcmp(myname
, EMBEDDED_NAME
) == 0) {
209 setbuf(stdout
, NULL
);
212 #endif /* DYNAMIC_SU */
214 if (argc
> 1 && *argv
[1] == '-') {
215 /* Explicitly check for just `-' (no trailing chars) */
216 if (strlen(argv
[1]) == 1) {
217 eflag
++; /* set eflag if `-' is specified */
222 gettext("Usage: %s [-] [ username [ arg ... ] ]"),
229 * Determine specified userid, get their password file entry,
230 * and set variables to values in password file entry fields.
234 * Usernames can't start with a `-', so we check for that to
235 * catch bad usage (like "su - -c ls").
237 if (*argv
[1] == '-') {
239 gettext("Usage: %s [-] [ username [ arg ... ] ]"),
243 nptr
= argv
[1]; /* use valid command-line username */
245 nptr
= "root"; /* use default "root" username */
247 if (defopen(DEFFILE
) == 0) {
249 if (Sulog
= defread("SULOG="))
250 Sulog
= strdup(Sulog
);
251 if (Console
= defread("CONSOLE="))
252 Console
= strdup(Console
);
253 if (Path
= defread("PATH="))
255 if (Supath
= defread("SUPATH="))
256 Supath
= strdup(Supath
);
257 if ((ptr
= defread("SYSLOG=")) != NULL
)
258 dosyslog
= strcmp(ptr
, "YES") == 0;
260 (void) defopen(NULL
);
262 (void) strlcat(path
, (Path
) ? Path
: PATH
, sizeof (path
));
263 (void) strlcat(supath
, (Supath
) ? Supath
: SUPATH
, sizeof (supath
));
265 if ((ttyn
= ttyname(0)) == NULL
)
266 if ((ttyn
= ttyname(1)) == NULL
)
267 if ((ttyn
= ttyname(2)) == NULL
)
269 if ((username
= cuserid(NULL
)) == NULL
)
273 * if Sulog defined, create SULOG, if it does not exist, with
274 * mode read/write user. Change owner and group to root
277 (void) close(open(Sulog
, O_WRONLY
| O_APPEND
| O_CREAT
,
279 (void) chown(Sulog
, (uid_t
)ROOT
, (gid_t
)ROOT
);
283 if (pam_start(embedded
? EMBEDDED_NAME
: "su", nptr
,
284 embedded
? &emb_pam_conv
: &pam_conv
, &pamh
) != PAM_SUCCESS
)
286 if (pam_set_item(pamh
, PAM_TTY
, ttyn
) != PAM_SUCCESS
)
288 getpwuid_r(getuid(), &pwd
, pwdbuf
, sizeof (pwdbuf
), &result
);
290 pam_set_item(pamh
, PAM_AUSER
, pwd
.pw_name
) != PAM_SUCCESS
)
292 #endif /* DYNAMIC_SU */
294 openlog("su", LOG_CONS
, LOG_AUTH
);
299 * Use the same value of sleeptime and password required that
301 * This is obtained by reading the file /etc/default/login
302 * using the def*() functions
304 if (defopen(DEFAULT_LOGIN
) == 0) {
305 if ((ptr
= defread("SLEEPTIME=")) != NULL
) {
306 sleeptime
= atoi(ptr
);
307 if (sleeptime
< 0 || sleeptime
> 5)
308 sleeptime
= SLEEPTIME
;
311 if ((ptr
= defread("PASSREQ=")) != NULL
&&
312 strcasecmp("YES", ptr
) == 0)
313 pam_flags
|= PAM_DISALLOW_NULL_AUTHTOK
;
315 (void) defopen(NULL
);
318 * Ignore SIGQUIT and SIGINT
320 (void) signal(SIGQUIT
, SIG_IGN
);
321 (void) signal(SIGINT
, SIG_IGN
);
323 /* call pam authenticate() to authenticate the user through PAM */
324 getpwnam_r(nptr
, &pwd
, pwdbuf
, sizeof (pwdbuf
), &result
);
326 retcode
= PAM_USER_UNKNOWN
;
327 else if ((flags
= (getuid() != (uid_t
)ROOT
)) != 0) {
328 retcode
= pam_authenticate(pamh
, pam_flags
);
329 } else /* root user does not need to authenticate */
330 retcode
= PAM_SUCCESS
;
332 if (retcode
!= PAM_SUCCESS
) {
334 * 1st step: audit and log the error.
336 * 3rd step: print out message to user.
338 /* don't let audit_failure distinguish a role here */
339 audit_failure(PW_FALSE
, NULL
, nptr
, retcode
);
341 case PAM_USER_UNKNOWN
:
343 (void) sleep(sleeptime
);
344 message(ERR
, gettext("Unknown id: %s"), nptr
);
349 log(Sulog
, nptr
, 0); /* log entry */
351 syslog(LOG_CRIT
, "'su %s' failed for %s on %s",
352 pwd
.pw_name
, username
, ttyn
);
354 (void) sleep(sleeptime
);
355 message(ERR
, gettext("Sorry"));
361 syslog(LOG_CRIT
, "'su %s' failed for %s on %s",
362 pwd
.pw_name
, username
, ttyn
);
364 (void) sleep(sleeptime
);
365 message(ERR
, gettext("Sorry"));
369 (void) signal(SIGQUIT
, SIG_DFL
);
370 (void) signal(SIGINT
, SIG_DFL
);
374 validate(username
, &pw_change
);
375 if (pam_setcred(pamh
, PAM_REINITIALIZE_CRED
) != PAM_SUCCESS
) {
376 message(ERR
, gettext("unable to set credentials"));
380 syslog(pwd
.pw_uid
== 0 ? LOG_NOTICE
: LOG_INFO
,
381 "'su %s' succeeded for %s on %s",
382 pwd
.pw_name
, username
, ttyn
);
384 (void) signal(SIGQUIT
, SIG_DFL
);
385 (void) signal(SIGINT
, SIG_DFL
);
386 #else /* !DYNAMIC_SU */
387 getpwnam_r(nptr
, &pwd
, pwdbuf
, sizeof (pwdbuf
), &result
);
389 (getspnam_r(nptr
, &sp
, spbuf
, sizeof (spbuf
)) == NULL
)) {
390 message(ERR
, gettext("Unknown id: %s"), nptr
);
391 audit_failure(PW_FALSE
, NULL
, nptr
, PAM_USER_UNKNOWN
);
397 * Prompt for password if invoking user is not root or
398 * if specified(new) user requires a password
400 if (sp
.sp_pwdp
[0] == '\0' || getuid() == (uid_t
)ROOT
)
402 password
= getpass(gettext("Password:"));
404 if ((strcmp(sp
.sp_pwdp
, crypt(password
, sp
.sp_pwdp
)) != 0)) {
405 /* clear password file entry */
406 (void) memset(spbuf
, 0, sizeof (spbuf
));
408 log(Sulog
, nptr
, 0); /* log entry */
409 message(ERR
, gettext("Sorry"));
410 audit_failure(PW_FALSE
, NULL
, nptr
, PAM_AUTH_ERR
);
412 syslog(LOG_CRIT
, "'su %s' failed for %s on %s",
413 pwd
.pw_name
, username
, ttyn
);
417 /* clear password file entry */
418 (void) memset(spbuf
, 0, sizeof (spbuf
));
420 /* update audit session in a non-pam environment */
423 syslog(pwd
.pw_uid
== 0 ? LOG_NOTICE
: LOG_INFO
,
424 "'su %s' succeeded for %s on %s",
425 pwd
.pw_name
, username
, ttyn
);
426 #endif /* DYNAMIC_SU */
428 audit_success(pw_change
, &pwd
);
431 dir
= strdup(pwd
.pw_dir
);
432 shprog
= strdup(pwd
.pw_shell
);
433 name
= strdup(pwd
.pw_name
);
436 log(Sulog
, nptr
, 1); /* log entry */
438 /* set user and group ids to specified user */
440 /* set the real (and effective) GID */
441 if (setgid(gid
) == -1) {
442 message(ERR
, gettext("Invalid GID"));
445 /* Initialize the supplementary group access list. */
448 if (initgroups(nptr
, gid
) == -1) {
451 /* set the real (and effective) UID */
452 if (setuid(uid
) == -1) {
453 message(ERR
, gettext("Invalid UID"));
458 * If new user's shell field is neither NULL nor equal to /usr/bin/sh,
461 * pshell = their shell
462 * su = [-]last component of shell's pathname
464 * Otherwise, set the shell to /usr/bin/sh and set argv[0] to '[-]su'.
466 if (shprog
[0] != '\0' && strcmp(shell
, shprog
) != 0) {
470 (void) strcpy(su
, eflag
? "-" : "");
472 if ((p
= strrchr(pshell
, '/')) != NULL
)
473 (void) strlcat(su
, p
+ 1, sizeof (su
));
475 (void) strlcat(su
, pshell
, sizeof (su
));
478 (void) strcpy(su
, eflag
? "-su" : "su");
482 * set environment variables for new user;
483 * arg0 for exec of shprog must now contain `-'
484 * so that environment of new user is given
490 if (strlen(dir
) == 0) {
491 (void) strcpy(dir
, "/");
492 message(WARN
, gettext("No directory! Using home=/"));
494 (void) strlcat(homedir
, dir
, sizeof (homedir
));
495 (void) strlcat(logname
, name
, sizeof (logname
));
496 if (hz
= getenv("HZ"))
497 (void) strlcat(hzname
, hz
, sizeof (hzname
));
499 (void) strlcat(shelltyp
, pshell
, sizeof (shelltyp
));
501 if (chdir(dir
) < 0) {
502 message(ERR
, gettext("No directory!"));
505 envinit
[envidx
= 0] = homedir
;
506 envinit
[++envidx
] = ((uid
== (uid_t
)ROOT
) ? supath
: path
);
507 envinit
[++envidx
] = logname
;
508 envinit
[++envidx
] = hzname
;
509 if ((term
= getenv("TERM")) != NULL
) {
510 (void) strlcat(termtyp
, term
, sizeof (termtyp
));
511 envinit
[++envidx
] = termtyp
;
513 envinit
[++envidx
] = shelltyp
;
515 (void) strlcat(mail
, name
, sizeof (mail
));
516 envinit
[++envidx
] = mail
;
519 * Fetch the relevant locale/TZ environment variables from
520 * the inherited environment.
522 * We have a priority here for setting TZ. If TZ is set in
523 * in the inherited environment, that value remains top
524 * priority. If the file /etc/default/login has TIMEZONE set,
525 * that has second highest priority.
528 for (j
= 0; initenv
[j
] != 0; j
++) {
529 if (initvar
= getenv(initenv
[j
])) {
532 * Skip over values beginning with '/' for
535 if (initvar
[0] == '/') continue;
537 if (strcmp(initenv
[j
], "TZ") == 0) {
538 (void) strcpy(tznam
, "TZ=");
539 (void) strlcat(tznam
, initvar
,
544 malloc(strlen(initenv
[j
])
551 (void) strcpy(var
, initenv
[j
]);
552 (void) strcat(var
, "=");
553 (void) strcat(var
, initvar
);
554 envinit
[++envidx
] = var
;
560 * Check if TZ was found. If not then try to read it from
561 * /etc/default/login.
563 if (tznam
[0] == '\0') {
564 if (defopen(DEFAULT_LOGIN
) == 0) {
565 if (initvar
= defread("TIMEZONE=")) {
566 (void) strcpy(tznam
, "TZ=");
567 (void) strlcat(tznam
, initvar
,
570 (void) defopen(NULL
);
574 if (tznam
[0] != '\0')
575 envinit
[++envidx
] = tznam
;
579 * set the PAM environment variables -
580 * check for legal environment variables
582 if ((pam_env
= pam_getenvlist(pamh
)) != 0) {
583 while (pam_env
[idx
] != 0) {
584 if (envidx
+ 2 < ELIM
&&
585 legalenvvar(pam_env
[idx
])) {
586 envinit
[++envidx
] = pam_env
[idx
];
591 #endif /* DYNAMIC_SU */
592 envinit
[++envidx
] = NULL
;
595 char **pp
= environ
, **qq
, *p
;
597 while ((p
= *pp
) != NULL
) {
598 if (*p
== 'L' && p
[1] == 'D' && p
[2] == '_') {
599 for (qq
= pp
; (*qq
= qq
[1]) != NULL
; qq
++)
601 /* pp is not advanced */
610 (void) pam_end(pamh
, PAM_SUCCESS
);
611 #endif /* DYNAMIC_SU */
614 * if new user is root:
615 * if CONSOLE defined, log entry there;
616 * if eflag not set, change environment to that of root.
618 if (uid
== (uid_t
)ROOT
) {
620 if (strcmp(ttyn
, Console
) != 0) {
621 (void) signal(SIGALRM
, to
);
623 log(Console
, nptr
, 1);
631 * Default for SIGCPU and SIGXFSZ. Shells inherit
632 * signal disposition from parent. And the
633 * shells should have default dispositions for these
636 (void) signal(SIGXCPU
, SIG_DFL
);
637 (void) signal(SIGXFSZ
, SIG_DFL
);
641 (void) puts("SUCCESS");
643 * After this point, we're no longer talking the
644 * embedded_su protocol, so turn it off.
648 #endif /* DYNAMIC_SU */
651 * if additional arguments, exec shell program with array
652 * of pointers to arguments:
653 * -> if shell = default, then su = [-]su
654 * -> if shell != default, then su = [-]last component of
657 * if no additional arguments, exec shell with arg0 of su
659 * -> if shell = default, then su = [-]su
660 * -> if shell != default, then su = [-]last component of
665 (void) execv(pshell
, &argv
[1]);
667 (void) execl(pshell
, su
, 0);
671 * Try to clean up after an administrator who has made a mistake
672 * configuring root's shell; if root's shell is other than /sbin/sh,
673 * try exec'ing /sbin/sh instead.
675 if ((uid
== (uid_t
)ROOT
) && (strcmp(name
, "root") == 0) &&
676 (strcmp(safe_shell
, pshell
) != 0)) {
678 gettext("No shell %s. Trying fallback shell %s."),
682 (void) strcpy(su
, "-sh");
683 (void) strlcpy(shelltyp
+ strlen("SHELL="),
684 safe_shell
, sizeof (shelltyp
) - strlen("SHELL="));
686 (void) strcpy(su
, "sh");
691 (void) execv(safe_shell
, &argv
[1]);
693 (void) execl(safe_shell
, su
, 0);
695 message(ERR
, gettext("Couldn't exec fallback shell %s: %s"),
696 safe_shell
, strerror(errno
));
698 message(ERR
, gettext("No shell"));
704 * Environment altering routine -
705 * This routine is called when a user is su'ing to root
706 * without specifying the - flag.
707 * The user's PATH and PS1 variables are reset
708 * to the correct value for root.
709 * All of the user's other environment variables retain
710 * their current values after the su (if they are exported).
716 * If user has PATH variable in their environment, change its value
717 * to /bin:/etc:/usr/bin ;
718 * if user does not have PATH variable, add it to the user's
720 * if either of the above fail, an error message is printed.
722 if (putenv(supath
) != 0) {
724 gettext("unable to obtain memory to expand environment"));
729 * If user has PROMPT variable in their environment, change its value
731 * if user does not have PROMPT variable, add it to the user's
733 * if either of the above fail, an error message is printed.
735 if (putenv(suprmt
) != 0) {
737 gettext("unable to obtain memory to expand environment"));
744 * where = SULOG or CONSOLE
745 * towho = specified user ( user being su'ed to )
746 * how = 0 if su attempt failed; 1 if su attempt succeeded
749 log(char *where
, char *towho
, int how
)
756 * open SULOG or CONSOLE - if open fails, return
758 if ((logf
= fopen(where
, "a")) == NULL
)
762 tmp
= localtime(&now
);
765 * write entry into SULOG or onto CONSOLE - if write fails, return
767 (void) fprintf(logf
, "SU %.2d/%.2d %.2d:%.2d %c %s %s-%s\n",
768 tmp
->tm_mon
+ 1, tmp
->tm_mday
, tmp
->tm_hour
, tmp
->tm_min
,
769 how
? '+' : '-', ttyn
+ sizeof ("/dev/") - 1, username
, towho
);
771 (void) fclose(logf
); /* close SULOG or CONSOLE */
780 * audit_success - audit successful su
782 * Entry process audit context established -- i.e., pam_setcred()
783 * or equivalent called.
784 * pw_change = PW_TRUE, if successful password change audit
786 * pwd = passwd entry for new user.
790 audit_success(int pw_change
, struct passwd
*pwd
)
792 adt_session_data_t
*ah
= NULL
;
793 adt_event_data_t
*event
;
794 au_event_t event_id
= ADT_su
;
795 userattr_t
*user_entry
;
798 if (adt_start_session(&ah
, NULL
, ADT_USE_PROC_DATA
) != 0) {
799 syslog(LOG_AUTH
| LOG_ALERT
,
800 "adt_start_session(ADT_su): %m");
803 if (((user_entry
= getusernam(pwd
->pw_name
)) != NULL
) &&
804 ((kva_value
= kva_match((kva_t
*)user_entry
->attr
,
805 USERATTR_TYPE_KW
)) != NULL
) &&
806 ((strcmp(kva_value
, USERATTR_TYPE_NONADMIN_KW
) == 0) ||
807 (strcmp(kva_value
, USERATTR_TYPE_ADMIN_KW
) == 0))) {
808 event_id
= ADT_role_login
;
810 free_userattr(user_entry
); /* OK to use, checks for NULL */
812 /* since proc uid/gid not yet updated */
813 if (adt_set_user(ah
, pwd
->pw_uid
, pwd
->pw_gid
, pwd
->pw_uid
,
814 pwd
->pw_gid
, NULL
, ADT_USER
) != 0) {
815 syslog(LOG_AUTH
| LOG_ERR
,
816 "adt_set_user(ADT_su, ADT_FAILURE): %m");
818 if ((event
= adt_alloc_event(ah
, event_id
)) == NULL
) {
819 syslog(LOG_AUTH
| LOG_ALERT
, "adt_alloc_event(ADT_su): %m");
820 } else if (adt_put_event(event
, ADT_SUCCESS
, ADT_SUCCESS
) != 0) {
821 syslog(LOG_AUTH
| LOG_ALERT
,
822 "adt_put_event(ADT_su, ADT_SUCCESS): %m");
825 if (pw_change
== PW_TRUE
) {
826 /* Also audit password change */
827 adt_free_event(event
);
828 if ((event
= adt_alloc_event(ah
, ADT_passwd
)) == NULL
) {
829 syslog(LOG_AUTH
| LOG_ALERT
,
830 "adt_alloc_event(ADT_passwd): %m");
831 } else if (adt_put_event(event
, ADT_SUCCESS
,
833 syslog(LOG_AUTH
| LOG_ALERT
,
834 "adt_put_event(ADT_passwd, ADT_SUCCESS): %m");
837 adt_free_event(event
);
839 * The preceeding code is a noop if audit isn't enabled,
840 * but, let's not make a new process when it's not necessary.
842 if (adt_audit_state(AUC_AUDITING
)) {
843 audit_logout(ah
, event_id
); /* fork to catch logout */
845 (void) adt_end_session(ah
);
850 * audit_logout - audit successful su logout
852 * Entry ah = Successful su audit handle
853 * event_id = su event ID: ADT_su, ADT_role_login
855 * Exit Errors are just ignored and we go on.
856 * su logout event written.
859 audit_logout(adt_session_data_t
*ah
, au_event_t event_id
)
861 adt_event_data_t
*event
;
862 int status
; /* wait status */
864 priv_set_t
*priv
; /* waiting process privs */
866 if (event_id
== ADT_su
) {
867 event_id
= ADT_su_logout
;
869 event_id
= ADT_role_logout
;
871 if ((event
= adt_alloc_event(ah
, event_id
)) == NULL
) {
872 syslog(LOG_AUTH
| LOG_ALERT
,
873 "adt_alloc_event(ADT_su_logout): %m");
876 if ((priv
= priv_allocset()) == NULL
) {
877 syslog(LOG_AUTH
| LOG_ALERT
,
878 "su audit_logout: could not alloc basic privs: %m");
879 adt_free_event(event
);
884 * The child returns and continues su processing.
885 * The parent's sole job is to wait for child exit, write the
886 * logout audit record, and replay the child's exit code.
888 if ((pid
= fork()) == 0) {
891 adt_free_event(event
);
898 syslog(LOG_AUTH
| LOG_ALERT
,
899 "su audit_logout: could not fork: %m");
900 adt_free_event(event
);
908 * When this routine is called, the current working
909 * directory is the unknown and there are unknown open
910 * files. For the waiting process, change the current
911 * directory to root and close open files so that
912 * directories can be unmounted if necessary.
914 if (chdir("/") != 0) {
915 syslog(LOG_AUTH
| LOG_ALERT
,
916 "su audit_logout: could not chdir /: %m");
919 * Reduce privileges to just those needed.
922 (void) priv_delset(priv
, PRIV_PROC_EXEC
);
923 (void) priv_delset(priv
, PRIV_PROC_FORK
);
924 (void) priv_delset(priv
, PRIV_PROC_INFO
);
925 (void) priv_delset(priv
, PRIV_PROC_SESSION
);
926 (void) priv_delset(priv
, PRIV_FILE_LINK_ANY
);
927 if ((priv_addset(priv
, PRIV_PROC_AUDIT
) != 0) ||
928 (setppriv(PRIV_SET
, PRIV_PERMITTED
, priv
) != 0)) {
929 syslog(LOG_AUTH
| LOG_ALERT
,
930 "su audit_logout: could not reduce privs: %m");
936 if (pid
!= waitpid(pid
, &status
, WUNTRACED
)) {
937 if (errno
== ECHILD
) {
939 * No existing child with the given pid. Lets
947 if (WIFEXITED(status
) || WIFSIGNALED(status
)) {
949 * The child shell exited or was terminated by
950 * a signal. Lets audit logout.
953 } else if (WIFSTOPPED(status
)) {
956 void (*sg_handler
)();
958 * The child shell has been stopped/suspended.
959 * We need to suspend here as well and pass down
960 * the control to the parent process.
962 sg_handler
= signal(WSTOPSIG(status
), SIG_DFL
);
963 (void) sigsend(P_PGID
, getpgrp(), WSTOPSIG(status
));
965 * We stop here. When resumed, mark the child
966 * shell group as foreground process group
967 * which gives the child shell a control over
968 * the controlling terminal.
970 (void) signal(WSTOPSIG(status
), sg_handler
);
973 if ((fd
= open("/dev/tty", O_RDWR
)) != -1) {
975 * Pass down the control over the controlling
976 * terminal iff we are in a foreground process
977 * group. Otherwise, we are in a background
978 * process group and the kernel will send
979 * SIGTTOU signal to stop us (by default).
981 if (tcgetpgrp(fd
) == getpgrp()) {
982 (void) tcsetpgrp(fd
, pgid
);
986 /* Wake up the child shell */
987 (void) sigsend(P_PGID
, pgid
, SIGCONT
);
991 (void) adt_put_event(event
, ADT_SUCCESS
, ADT_SUCCESS
);
992 adt_free_event(event
);
993 (void) adt_end_session(ah
);
994 exit(WEXITSTATUS(status
));
999 * audit_failure - audit failed su
1001 * Entry New audit context not set.
1002 * pw_change == PW_FALSE, if no password change requested.
1003 * PW_FAILED, if failed password change audit
1005 * pwd = NULL, or password entry to use.
1006 * user = username entered. Add to record if pwd == NULL.
1007 * pamerr = PAM error code; reason for failure.
1011 audit_failure(int pw_change
, struct passwd
*pwd
, char *user
, int pamerr
)
1013 adt_session_data_t
*ah
; /* audit session handle */
1014 adt_event_data_t
*event
; /* event to generate */
1015 au_event_t event_id
= ADT_su
;
1016 userattr_t
*user_entry
;
1019 if (adt_start_session(&ah
, NULL
, ADT_USE_PROC_DATA
) != 0) {
1020 syslog(LOG_AUTH
| LOG_ALERT
,
1021 "adt_start_session(ADT_su, ADT_FAILURE): %m");
1026 /* target user authenticated, merge audit state */
1027 if (adt_set_user(ah
, pwd
->pw_uid
, pwd
->pw_gid
, pwd
->pw_uid
,
1028 pwd
->pw_gid
, NULL
, ADT_UPDATE
) != 0) {
1029 syslog(LOG_AUTH
| LOG_ERR
,
1030 "adt_set_user(ADT_su, ADT_FAILURE): %m");
1032 if (((user_entry
= getusernam(pwd
->pw_name
)) != NULL
) &&
1033 ((kva_value
= kva_match((kva_t
*)user_entry
->attr
,
1034 USERATTR_TYPE_KW
)) != NULL
) &&
1035 ((strcmp(kva_value
, USERATTR_TYPE_NONADMIN_KW
) == 0) ||
1036 (strcmp(kva_value
, USERATTR_TYPE_ADMIN_KW
) == 0))) {
1037 event_id
= ADT_role_login
;
1039 free_userattr(user_entry
); /* OK to use, checks for NULL */
1041 if ((event
= adt_alloc_event(ah
, event_id
)) == NULL
) {
1042 syslog(LOG_AUTH
| LOG_ALERT
,
1043 "adt_alloc_event(ADT_su, ADT_FAILURE): %m");
1047 * can't tell if user not found is a role, so always use su
1048 * If we do pass in pwd when the JNI is fixed, then can
1049 * distinguish and set name in both su and role_login
1053 * this should be "fail_user" rather than "message"
1054 * see adt_xml. The JNI breaks, so for now we leave
1057 event
->adt_su
.message
= user
;
1059 if (adt_put_event(event
, ADT_FAILURE
,
1060 ADT_FAIL_PAM
+ pamerr
) != 0) {
1061 syslog(LOG_AUTH
| LOG_ALERT
,
1062 "adt_put_event(ADT_su(ADT_FAIL, %s): %m",
1063 pam_strerror(pamh
, pamerr
));
1065 if (pw_change
!= PW_FALSE
) {
1066 /* Also audit password change failed */
1067 adt_free_event(event
);
1068 if ((event
= adt_alloc_event(ah
, ADT_passwd
)) == NULL
) {
1069 syslog(LOG_AUTH
| LOG_ALERT
,
1070 "su: adt_alloc_event(ADT_passwd): %m");
1071 } else if (adt_put_event(event
, ADT_FAILURE
,
1072 ADT_FAIL_PAM
+ pamerr
) != 0) {
1073 syslog(LOG_AUTH
| LOG_ALERT
,
1074 "su: adt_put_event(ADT_passwd, ADT_FAILURE): %m");
1077 adt_free_event(event
);
1078 (void) adt_end_session(ah
);
1084 * This is the conv (conversation) function called from
1085 * a PAM authentication module to print error messages
1086 * or garner information from the user.
1090 su_conv(int num_msg
, struct pam_message
**msg
, struct pam_response
**response
,
1093 struct pam_message
*m
;
1094 struct pam_response
*r
;
1097 char respbuf
[PAM_MAX_RESP_SIZE
];
1100 return (PAM_CONV_ERR
);
1102 *response
= (struct pam_response
*)calloc(num_msg
,
1103 sizeof (struct pam_response
));
1104 if (*response
== NULL
)
1105 return (PAM_BUF_ERR
);
1112 switch (m
->msg_style
) {
1114 case PAM_PROMPT_ECHO_OFF
:
1116 temp
= getpassphrase(m
->msg
);
1118 return (PAM_CONV_ERR
);
1120 r
->resp
= strdup(temp
);
1121 if (r
->resp
== NULL
) {
1122 freeresponse(num_msg
, response
);
1123 return (PAM_BUF_ERR
);
1128 case PAM_PROMPT_ECHO_ON
:
1129 if (m
->msg
!= NULL
) {
1130 (void) fputs(m
->msg
, stdout
);
1133 (void) fgets(respbuf
, sizeof (respbuf
), stdin
);
1134 temp
= strchr(respbuf
, '\n');
1138 r
->resp
= strdup(respbuf
);
1139 if (r
->resp
== NULL
) {
1140 freeresponse(num_msg
, response
);
1141 return (PAM_BUF_ERR
);
1146 if (m
->msg
!= NULL
) {
1147 (void) fputs(m
->msg
, stderr
);
1148 (void) fputs("\n", stderr
);
1153 if (m
->msg
!= NULL
) {
1154 (void) fputs(m
->msg
, stdout
);
1155 (void) fputs("\n", stdout
);
1165 return (PAM_SUCCESS
);
1170 * This is the conv (conversation) function called from
1171 * a PAM authentication module to print error messages
1172 * or garner information from the user.
1173 * This version is used for embedded_su.
1177 emb_su_conv(int num_msg
, struct pam_message
**msg
,
1178 struct pam_response
**response
, void *appdata_ptr
)
1180 struct pam_message
*m
;
1181 struct pam_response
*r
;
1184 char respbuf
[PAM_MAX_RESP_SIZE
];
1187 return (PAM_CONV_ERR
);
1189 *response
= (struct pam_response
*)calloc(num_msg
,
1190 sizeof (struct pam_response
));
1191 if (*response
== NULL
)
1192 return (PAM_BUF_ERR
);
1194 /* First, send the prompts */
1195 (void) printf("CONV %d\n", num_msg
);
1199 switch (m
->msg_style
) {
1201 case PAM_PROMPT_ECHO_OFF
:
1202 (void) puts("PAM_PROMPT_ECHO_OFF");
1205 case PAM_PROMPT_ECHO_ON
:
1206 (void) puts("PAM_PROMPT_ECHO_ON");
1210 (void) puts("PAM_ERROR_MSG");
1214 (void) puts("PAM_TEXT_INFO");
1215 /* fall through to msg_common */
1220 quotemsg("%s", m
->msg
);
1229 /* Next, collect the responses */
1235 switch (m
->msg_style
) {
1237 case PAM_PROMPT_ECHO_OFF
:
1238 case PAM_PROMPT_ECHO_ON
:
1239 (void) fgets(respbuf
, sizeof (respbuf
), stdin
);
1241 temp
= strchr(respbuf
, '\n');
1245 r
->resp
= strdup(respbuf
);
1246 if (r
->resp
== NULL
) {
1247 freeresponse(num_msg
, response
);
1248 return (PAM_BUF_ERR
);
1263 return (PAM_SUCCESS
);
1267 freeresponse(int num_msg
, struct pam_response
**response
)
1269 struct pam_response
*r
;
1272 /* free responses */
1274 for (i
= 0; i
< num_msg
; i
++, r
++) {
1275 if (r
->resp
!= NULL
) {
1276 /* Zap it in case it's a password */
1277 (void) memset(r
->resp
, '\0', strlen(r
->resp
));
1286 * Print a message, applying quoting for lines starting with '.'.
1288 * I18n note: \n is "safe" in all locales, and all locales use
1289 * a high-bit-set character to start multibyte sequences, so
1290 * scanning for a \n followed by a '.' is safe.
1293 quotemsg(char *fmt
, ...)
1302 msg
= alloc_vsprintf(fmt
, v
);
1306 for (p
= msg
; *p
!= '\0'; p
++) {
1309 (void) putchar('.');
1316 (void) putchar('\n');
1319 (void) putchar('.');
1320 (void) putchar('\n');
1324 * validate - Check that the account is valid for switching to.
1327 validate(char *usernam
, int *pw_change
)
1332 if ((error
= pam_acct_mgmt(pamh
, pam_flags
)) != PAM_SUCCESS
) {
1334 log(Sulog
, pwd
.pw_name
, 0); /* log entry */
1335 if (error
== PAM_NEW_AUTHTOK_REQD
) {
1337 message(ERR
, gettext("Password for user "
1338 "'%s' has expired"), pwd
.pw_name
);
1339 while ((error
= pam_chauthtok(pamh
,
1340 PAM_CHANGE_EXPIRED_AUTHTOK
)) != PAM_SUCCESS
) {
1341 if ((error
== PAM_AUTHTOK_ERR
||
1342 error
== PAM_TRY_AGAIN
) &&
1343 (tries
++ < DEF_ATTEMPTS
)) {
1346 message(ERR
, gettext("Sorry"));
1347 audit_failure(PW_FAILED
, &pwd
, NULL
, error
);
1350 "'su %s' failed for %s on %s",
1351 pwd
.pw_name
, usernam
, ttyn
);
1355 *pw_change
= PW_TRUE
;
1358 message(ERR
, gettext("Sorry"));
1359 audit_failure(PW_FALSE
, &pwd
, NULL
, error
);
1361 syslog(LOG_CRIT
, "'su %s' failed for %s on %s",
1362 pwd
.pw_name
, usernam
, ttyn
);
1369 static char *illegal
[] = {
1386 * legalenvvar - can PAM modules insert this environmental variable?
1390 legalenvvar(char *s
)
1394 for (p
= illegal
; *p
; p
++)
1395 if (strncmp(s
, *p
, strlen(*p
)) == 0)
1398 if (s
[0] == 'L' && s
[1] == 'D' && s
[2] == '_')
1405 * The embedded_su protocol allows the client application to supply
1406 * an initialization block terminated by a line with just a "." on it.
1408 * This initialization block is currently unused, reserved for future
1409 * expansion. Ignore it. This is made very slightly more complex by
1410 * the desire to cleanly ignore input lines of any length, while still
1411 * correctly detecting a line with just a "." on it.
1413 * I18n note: It appears that none of the Solaris-supported locales
1414 * use 0x0a for any purpose other than newline, so looking for '\n'
1416 * All locales use high-bit-set leadin characters for their multi-byte
1417 * sequences, so a line consisting solely of ".\n" is what it appears
1428 if (fgets(buf
, sizeof (buf
), stdin
) == NULL
)
1430 if (bol
&& strcmp(buf
, ".\n") == 0)
1432 bol
= (strchr(buf
, '\n') != NULL
);
1435 #else /* !DYNAMIC_SU */
1437 update_audit(struct passwd
*pwd
)
1439 adt_session_data_t
*ah
; /* audit session handle */
1441 if (adt_start_session(&ah
, NULL
, ADT_USE_PROC_DATA
) != 0) {
1442 message(ERR
, gettext("Sorry"));
1444 syslog(LOG_CRIT
, "'su %s' failed for %s "
1445 "cannot start audit session %m",
1446 pwd
->pw_name
, username
);
1450 if (adt_set_user(ah
, pwd
->pw_uid
, pwd
->pw_gid
, pwd
->pw_uid
,
1451 pwd
->pw_gid
, NULL
, ADT_UPDATE
) != 0) {
1453 syslog(LOG_CRIT
, "'su %s' failed for %s "
1454 "cannot update audit session %m",
1455 pwd
->pw_name
, username
);
1460 #endif /* DYNAMIC_SU */
1463 * Report an error, either a fatal one, a warning, or a usage message,
1464 * depending on the mode parameter.
1468 message(enum messagemode mode
, char *fmt
, ...)
1474 s
= alloc_vsprintf(fmt
, v
);
1480 (void) printf("CONV 1\n");
1481 (void) printf("PAM_ERROR_MSG\n");
1482 } else { /* ERR, USAGE */
1483 (void) printf("ERROR\n");
1485 if (mode
== USAGE
) {
1487 } else { /* ERR, WARN */
1488 quotemsg("%s: %s", myname
, s
);
1491 #endif /* DYNAMIC_SU */
1492 if (mode
== USAGE
) {
1493 (void) fprintf(stderr
, "%s\n", s
);
1494 } else { /* ERR, WARN */
1495 (void) fprintf(stderr
, "%s: %s\n", myname
, s
);
1499 #endif /* DYNAMIC_SU */
1505 * Return a pointer to the last path component of a.
1512 p
= strrchr(a
, '/');
1516 p
++; /* step over the '/' */
1522 alloc_vsprintf(const char *fmt
, va_list ap1
)
1530 * We need to scan the argument list twice. Save off a copy
1531 * of the argument list pointer(s) for the second pass. Note that
1532 * we are responsible for va_end'ing our copy.
1537 * vsnprintf into a dummy to get a length. One might
1538 * think that passing 0 as the length to snprintf would
1539 * do what we want, but it's defined not to.
1541 * Perhaps we should sprintf into a 100 character buffer
1542 * or something like that, to avoid two calls to snprintf
1545 n
= vsnprintf(buf
, sizeof (buf
), fmt
, ap2
);
1549 * Allocate an appropriately-sized buffer.
1557 (void) vsnprintf(s
, n
+1, fmt
, ap1
);