version 1.163.2.2, 2004/08/19 22:37:32 |
version 1.164, 2003/09/18 08:49:45 |
|
|
#include "sshpty.h" |
#include "sshpty.h" |
#include "packet.h" |
#include "packet.h" |
#include "buffer.h" |
#include "buffer.h" |
#include "match.h" |
#include "mpaux.h" |
#include "uidswap.h" |
#include "uidswap.h" |
#include "compat.h" |
#include "compat.h" |
#include "channels.h" |
#include "channels.h" |
|
|
#include "session.h" |
#include "session.h" |
#include "monitor_wrap.h" |
#include "monitor_wrap.h" |
|
|
#ifdef KRB5 |
|
#include <kafs.h> |
|
#endif |
|
|
|
#ifdef GSSAPI |
#ifdef GSSAPI |
#include "ssh-gss.h" |
#include "ssh-gss.h" |
#endif |
#endif |
|
|
|
|
Session *session_new(void); |
Session *session_new(void); |
void session_set_fds(Session *, int, int, int); |
void session_set_fds(Session *, int, int, int); |
void session_pty_cleanup(Session *); |
void session_pty_cleanup(void *); |
void session_proctitle(Session *); |
void session_proctitle(Session *); |
int session_setup_x11fwd(Session *); |
int session_setup_x11fwd(Session *); |
void do_exec_pty(Session *, const char *); |
void do_exec_pty(Session *, const char *); |
|
|
extern u_int utmp_len; |
extern u_int utmp_len; |
extern int startup_pipe; |
extern int startup_pipe; |
extern void destroy_sensitive_data(void); |
extern void destroy_sensitive_data(void); |
extern Buffer loginmsg; |
|
|
|
/* original command from peer. */ |
/* original command from peer. */ |
const char *original_command = NULL; |
const char *original_command = NULL; |
|
|
login_cap_t *lc; |
login_cap_t *lc; |
#endif |
#endif |
|
|
static int is_child = 0; |
|
|
|
/* Name and directory of socket for authentication agent forwarding. */ |
/* Name and directory of socket for authentication agent forwarding. */ |
static char *auth_sock_name = NULL; |
static char *auth_sock_name = NULL; |
static char *auth_sock_dir = NULL; |
static char *auth_sock_dir = NULL; |
|
|
/* removes the agent forwarding socket */ |
/* removes the agent forwarding socket */ |
|
|
static void |
static void |
auth_sock_cleanup_proc(struct passwd *pw) |
auth_sock_cleanup_proc(void *_pw) |
{ |
{ |
|
struct passwd *pw = _pw; |
|
|
if (auth_sock_name != NULL) { |
if (auth_sock_name != NULL) { |
temporarily_use_uid(pw); |
temporarily_use_uid(pw); |
unlink(auth_sock_name); |
unlink(auth_sock_name); |
|
|
/* Allocate a buffer for the socket name, and format the name. */ |
/* Allocate a buffer for the socket name, and format the name. */ |
auth_sock_name = xmalloc(MAXPATHLEN); |
auth_sock_name = xmalloc(MAXPATHLEN); |
auth_sock_dir = xmalloc(MAXPATHLEN); |
auth_sock_dir = xmalloc(MAXPATHLEN); |
strlcpy(auth_sock_dir, "/tmp/ssh-XXXXXXXXXX", MAXPATHLEN); |
strlcpy(auth_sock_dir, "/tmp/ssh-XXXXXXXX", MAXPATHLEN); |
|
|
/* Create private directory for socket */ |
/* Create private directory for socket */ |
if (mkdtemp(auth_sock_dir) == NULL) { |
if (mkdtemp(auth_sock_dir) == NULL) { |
|
|
snprintf(auth_sock_name, MAXPATHLEN, "%s/agent.%ld", |
snprintf(auth_sock_name, MAXPATHLEN, "%s/agent.%ld", |
auth_sock_dir, (long) getpid()); |
auth_sock_dir, (long) getpid()); |
|
|
|
/* delete agent socket on fatal() */ |
|
fatal_add_cleanup(auth_sock_cleanup_proc, pw); |
|
|
/* Create the socket. */ |
/* Create the socket. */ |
sock = socket(AF_UNIX, SOCK_STREAM, 0); |
sock = socket(AF_UNIX, SOCK_STREAM, 0); |
if (sock < 0) |
if (sock < 0) |
|
|
restore_uid(); |
restore_uid(); |
|
|
/* Start listening on the socket. */ |
/* Start listening on the socket. */ |
if (listen(sock, SSH_LISTEN_BACKLOG) < 0) |
if (listen(sock, 5) < 0) |
packet_disconnect("listen: %.100s", strerror(errno)); |
packet_disconnect("listen: %.100s", strerror(errno)); |
|
|
/* Allocate a channel for the authentication agent socket. */ |
/* Allocate a channel for the authentication agent socket. */ |
|
|
return 1; |
return 1; |
} |
} |
|
|
static void |
|
display_loginmsg(void) |
|
{ |
|
if (buffer_len(&loginmsg) > 0) { |
|
buffer_append(&loginmsg, "\0", 1); |
|
printf("%s", (char *)buffer_ptr(&loginmsg)); |
|
buffer_clear(&loginmsg); |
|
} |
|
} |
|
|
|
void |
void |
do_authenticated(Authctxt *authctxt) |
do_authenticated(Authctxt *authctxt) |
|
|
else |
else |
do_authenticated1(authctxt); |
do_authenticated1(authctxt); |
|
|
do_cleanup(authctxt); |
/* remove agent socket */ |
|
if (auth_sock_name != NULL) |
|
auth_sock_cleanup_proc(authctxt->pw); |
|
#ifdef KRB5 |
|
if (options.kerberos_ticket_cleanup) |
|
krb5_cleanup_proc(authctxt); |
|
#endif |
} |
} |
|
|
/* |
/* |
|
|
compression_level = packet_get_int(); |
compression_level = packet_get_int(); |
packet_check_eom(); |
packet_check_eom(); |
if (compression_level < 1 || compression_level > 9) { |
if (compression_level < 1 || compression_level > 9) { |
packet_send_debug("Received invalid compression level %d.", |
packet_send_debug("Received illegal compression level %d.", |
compression_level); |
compression_level); |
break; |
break; |
} |
} |
|
|
|
|
/* Fork the child. */ |
/* Fork the child. */ |
if ((pid = fork()) == 0) { |
if ((pid = fork()) == 0) { |
is_child = 1; |
fatal_remove_all_cleanups(); |
|
|
/* Child. Reinitialize the log since the pid has changed. */ |
/* Child. Reinitialize the log since the pid has changed. */ |
log_init(__progname, options.log_level, options.log_facility, log_stderr); |
log_init(__progname, options.log_level, options.log_facility, log_stderr); |
|
|
close(perr[1]); |
close(perr[1]); |
|
|
if (compat20) { |
if (compat20) { |
if (s->is_subsystem) { |
session_set_fds(s, pin[1], pout[0], s->is_subsystem ? -1 : perr[0]); |
close(perr[0]); |
|
perr[0] = -1; |
|
} |
|
session_set_fds(s, pin[1], pout[0], perr[0]); |
|
} else { |
} else { |
/* Enter the interactive session. */ |
/* Enter the interactive session. */ |
server_loop(pid, pin[1], pout[0], perr[0]); |
server_loop(pid, pin[1], pout[0], perr[0]); |
|
|
|
|
/* Fork the child. */ |
/* Fork the child. */ |
if ((pid = fork()) == 0) { |
if ((pid = fork()) == 0) { |
is_child = 1; |
fatal_remove_all_cleanups(); |
|
|
/* Child. Reinitialize the log because the pid has changed. */ |
/* Child. Reinitialize the log because the pid has changed. */ |
log_init(__progname, options.log_level, options.log_facility, log_stderr); |
log_init(__progname, options.log_level, options.log_facility, log_stderr); |
|
|
do_exec_no_pty(s, command); |
do_exec_no_pty(s, command); |
|
|
original_command = NULL; |
original_command = NULL; |
|
|
/* |
|
* Clear loginmsg: it's the child's responsibility to display |
|
* it to the user, otherwise multiple sessions may accumulate |
|
* multiple copies of the login messages. |
|
*/ |
|
buffer_clear(&loginmsg); |
|
} |
} |
|
|
|
|
|
|
void |
void |
do_login(Session *s, const char *command) |
do_login(Session *s, const char *command) |
{ |
{ |
|
char *time_string; |
socklen_t fromlen; |
socklen_t fromlen; |
struct sockaddr_storage from; |
struct sockaddr_storage from; |
struct passwd * pw = s->pw; |
struct passwd * pw = s->pw; |
|
|
if (getpeername(packet_get_connection_in(), |
if (getpeername(packet_get_connection_in(), |
(struct sockaddr *) & from, &fromlen) < 0) { |
(struct sockaddr *) & from, &fromlen) < 0) { |
debug("getpeername: %.100s", strerror(errno)); |
debug("getpeername: %.100s", strerror(errno)); |
cleanup_exit(255); |
fatal_cleanup(); |
} |
} |
} |
} |
|
|
|
|
if (check_quietlogin(s, command)) |
if (check_quietlogin(s, command)) |
return; |
return; |
|
|
display_loginmsg(); |
if (options.print_lastlog && s->last_login_time != 0) { |
|
time_string = ctime(&s->last_login_time); |
|
if (strchr(time_string, '\n')) |
|
*strchr(time_string, '\n') = 0; |
|
if (strcmp(s->hostname, "") == 0) |
|
printf("Last login: %s\r\n", time_string); |
|
else |
|
printf("Last login: %s from %s\r\n", time_string, |
|
s->hostname); |
|
} |
|
|
do_motd(); |
do_motd(); |
} |
} |
|
|
env[0] = NULL; |
env[0] = NULL; |
|
|
#ifdef GSSAPI |
#ifdef GSSAPI |
/* Allow any GSSAPI methods that we've used to alter |
/* Allow any GSSAPI methods that we've used to alter |
* the childs environment as they see fit |
* the childs environment as they see fit |
*/ |
*/ |
ssh_gssapi_do_child(&env, &envsize); |
ssh_gssapi_do_child(&env, &envsize); |
|
|
|
|
if (!options.use_login) { |
if (!options.use_login) { |
/* Set basic environment. */ |
/* Set basic environment. */ |
for (i = 0; i < s->num_env; i++) |
|
child_set_env(&env, &envsize, s->env[i].name, |
|
s->env[i].val); |
|
|
|
child_set_env(&env, &envsize, "USER", pw->pw_name); |
child_set_env(&env, &envsize, "USER", pw->pw_name); |
child_set_env(&env, &envsize, "LOGNAME", pw->pw_name); |
child_set_env(&env, &envsize, "LOGNAME", pw->pw_name); |
child_set_env(&env, &envsize, "HOME", pw->pw_dir); |
child_set_env(&env, &envsize, "HOME", pw->pw_dir); |
|
|
if (debug_flag) { |
if (debug_flag) { |
fprintf(stderr, |
fprintf(stderr, |
"Running %.500s remove %.100s\n", |
"Running %.500s remove %.100s\n", |
options.xauth_location, s->auth_display); |
options.xauth_location, s->auth_display); |
fprintf(stderr, |
fprintf(stderr, |
"%.500s add %.100s %.100s %.100s\n", |
"%.500s add %.100s %.100s %.100s\n", |
options.xauth_location, s->auth_display, |
options.xauth_location, s->auth_display, |
|
|
} |
} |
|
|
static void |
static void |
do_pwchange(Session *s) |
|
{ |
|
fflush(NULL); |
|
fprintf(stderr, "WARNING: Your password has expired.\n"); |
|
if (s->ttyfd != -1) { |
|
fprintf(stderr, |
|
"You must change your password now and login again!\n"); |
|
execl(_PATH_PASSWD_PROG, "passwd", (char *)NULL); |
|
perror("passwd"); |
|
} else { |
|
fprintf(stderr, |
|
"Password change required but no TTY available.\n"); |
|
} |
|
exit(1); |
|
} |
|
|
|
static void |
|
launch_login(struct passwd *pw, const char *hostname) |
launch_login(struct passwd *pw, const char *hostname) |
{ |
{ |
/* Launch login(1). */ |
/* Launch login(1). */ |
|
|
exit(1); |
exit(1); |
} |
} |
|
|
static void |
|
child_close_fds(void) |
|
{ |
|
int i; |
|
|
|
if (packet_get_connection_in() == packet_get_connection_out()) |
|
close(packet_get_connection_in()); |
|
else { |
|
close(packet_get_connection_in()); |
|
close(packet_get_connection_out()); |
|
} |
|
/* |
|
* Close all descriptors related to channels. They will still remain |
|
* open in the parent. |
|
*/ |
|
/* XXX better use close-on-exec? -markus */ |
|
channel_close_all(); |
|
|
|
/* |
|
* Close any extra file descriptors. Note that there may still be |
|
* descriptors left by system functions. They will be closed later. |
|
*/ |
|
endpwent(); |
|
|
|
/* |
|
* Close any extra open file descriptors so that we don\'t have them |
|
* hanging around in clients. Note that we want to do this after |
|
* initgroups, because at least on Solaris 2.3 it leaves file |
|
* descriptors open. |
|
*/ |
|
for (i = 3; i < 64; i++) |
|
close(i); |
|
} |
|
|
|
/* |
/* |
* Performs common processing for the child, such as setting up the |
* Performs common processing for the child, such as setting up the |
* environment, closing extra file descriptors, setting the user and group |
* environment, closing extra file descriptors, setting the user and group |
|
|
char *argv[10]; |
char *argv[10]; |
const char *shell, *shell0, *hostname = NULL; |
const char *shell, *shell0, *hostname = NULL; |
struct passwd *pw = s->pw; |
struct passwd *pw = s->pw; |
|
u_int i; |
|
|
/* remove hostkey from the child's memory */ |
/* remove hostkey from the child's memory */ |
destroy_sensitive_data(); |
destroy_sensitive_data(); |
|
|
/* Force a password change */ |
|
if (s->authctxt->force_pwchange) { |
|
do_setusercontext(pw); |
|
child_close_fds(); |
|
do_pwchange(s); |
|
exit(1); |
|
} |
|
|
|
/* login(1) is only called if we execute the login shell */ |
/* login(1) is only called if we execute the login shell */ |
if (options.use_login && command != NULL) |
if (options.use_login && command != NULL) |
options.use_login = 0; |
options.use_login = 0; |
|
|
* closed before building the environment, as we call |
* closed before building the environment, as we call |
* get_remote_ipaddr there. |
* get_remote_ipaddr there. |
*/ |
*/ |
child_close_fds(); |
if (packet_get_connection_in() == packet_get_connection_out()) |
|
close(packet_get_connection_in()); |
|
else { |
|
close(packet_get_connection_in()); |
|
close(packet_get_connection_out()); |
|
} |
|
/* |
|
* Close all descriptors related to channels. They will still remain |
|
* open in the parent. |
|
*/ |
|
/* XXX better use close-on-exec? -markus */ |
|
channel_close_all(); |
|
|
/* |
/* |
* Must take new environment into use so that .ssh/rc, |
* Close any extra file descriptors. Note that there may still be |
* /etc/ssh/sshrc and xauth are run in the proper environment. |
* descriptors left by system functions. They will be closed later. |
*/ |
*/ |
environ = env; |
endpwent(); |
|
|
#ifdef KRB5 |
|
/* |
/* |
* At this point, we check to see if AFS is active and if we have |
* Close any extra open file descriptors so that we don\'t have them |
* a valid Kerberos 5 TGT. If so, it seems like a good idea to see |
* hanging around in clients. Note that we want to do this after |
* if we can (and need to) extend the ticket into an AFS token. If |
* initgroups, because at least on Solaris 2.3 it leaves file |
* we don't do this, we run into potential problems if the user's |
* descriptors open. |
* home directory is in AFS and it's not world-readable. |
|
*/ |
*/ |
|
for (i = 3; i < 64; i++) |
|
close(i); |
|
|
if (options.kerberos_get_afs_token && k_hasafs() && |
/* |
(s->authctxt->krb5_ctx != NULL)) { |
* Must take new environment into use so that .ssh/rc, |
char cell[64]; |
* /etc/ssh/sshrc and xauth are run in the proper environment. |
|
*/ |
|
environ = env; |
|
|
debug("Getting AFS token"); |
|
|
|
k_setpag(); |
|
|
|
if (k_afs_cell_of_file(pw->pw_dir, cell, sizeof(cell)) == 0) |
|
krb5_afslog(s->authctxt->krb5_ctx, |
|
s->authctxt->krb5_fwd_ccache, cell, NULL); |
|
|
|
krb5_afslog_home(s->authctxt->krb5_ctx, |
|
s->authctxt->krb5_fwd_ccache, NULL, NULL, pw->pw_dir); |
|
} |
|
#endif |
|
|
|
/* Change current directory to the user\'s home directory. */ |
/* Change current directory to the user\'s home directory. */ |
if (chdir(pw->pw_dir) < 0) { |
if (chdir(pw->pw_dir) < 0) { |
fprintf(stderr, "Could not chdir to home directory %s: %s\n", |
fprintf(stderr, "Could not chdir to home directory %s: %s\n", |
|
|
} |
} |
s->authctxt = authctxt; |
s->authctxt = authctxt; |
s->pw = authctxt->pw; |
s->pw = authctxt->pw; |
if (s->pw == NULL || !authctxt->valid) |
if (s->pw == NULL) |
fatal("no user for session %d", s->self); |
fatal("no user for session %d", s->self); |
debug("session_open: session %d: link with channel %d", s->self, chanid); |
debug("session_open: session %d: link with channel %d", s->self, chanid); |
s->chanid = chanid; |
s->chanid = chanid; |
|
|
packet_disconnect("Protocol error: you already have a pty."); |
packet_disconnect("Protocol error: you already have a pty."); |
return 0; |
return 0; |
} |
} |
|
/* Get the time and hostname when the user last logged in. */ |
|
if (options.print_lastlog) { |
|
s->hostname[0] = '\0'; |
|
s->last_login_time = get_last_login_time(s->pw->pw_uid, |
|
s->pw->pw_name, s->hostname, sizeof(s->hostname)); |
|
} |
|
|
s->term = packet_get_string(&len); |
s->term = packet_get_string(&len); |
|
|
|
|
n_bytes = packet_remaining(); |
n_bytes = packet_remaining(); |
tty_parse_modes(s->ttyfd, &n_bytes); |
tty_parse_modes(s->ttyfd, &n_bytes); |
|
|
|
/* |
|
* Add a cleanup function to clear the utmp entry and record logout |
|
* time in case we call fatal() (e.g., the connection gets closed). |
|
*/ |
|
fatal_add_cleanup(session_pty_cleanup, (void *)s); |
if (!use_privsep) |
if (!use_privsep) |
pty_setowner(s->pw, s->tty); |
pty_setowner(s->pw, s->tty); |
|
|
|
|
static int |
static int |
session_break_req(Session *s) |
session_break_req(Session *s) |
{ |
{ |
|
u_int break_length; |
|
|
packet_get_int(); /* ignored */ |
break_length = packet_get_int(); /* ignored */ |
packet_check_eom(); |
packet_check_eom(); |
|
|
if (s->ttyfd == -1 || |
if (s->ttyfd == -1 || |
|
|
} |
} |
|
|
static int |
static int |
session_env_req(Session *s) |
|
{ |
|
char *name, *val; |
|
u_int name_len, val_len, i; |
|
|
|
name = packet_get_string(&name_len); |
|
val = packet_get_string(&val_len); |
|
packet_check_eom(); |
|
|
|
/* Don't set too many environment variables */ |
|
if (s->num_env > 128) { |
|
debug2("Ignoring env request %s: too many env vars", name); |
|
goto fail; |
|
} |
|
|
|
for (i = 0; i < options.num_accept_env; i++) { |
|
if (match_pattern(name, options.accept_env[i])) { |
|
debug2("Setting env %d: %s=%s", s->num_env, name, val); |
|
s->env = xrealloc(s->env, sizeof(*s->env) * |
|
(s->num_env + 1)); |
|
s->env[s->num_env].name = name; |
|
s->env[s->num_env].val = val; |
|
s->num_env++; |
|
return (1); |
|
} |
|
} |
|
debug2("Ignoring env request %s: disallowed name", name); |
|
|
|
fail: |
|
xfree(name); |
|
xfree(val); |
|
return (0); |
|
} |
|
|
|
static int |
|
session_auth_agent_req(Session *s) |
session_auth_agent_req(Session *s) |
{ |
{ |
static int called = 0; |
static int called = 0; |
|
|
success = session_auth_agent_req(s); |
success = session_auth_agent_req(s); |
} else if (strcmp(rtype, "subsystem") == 0) { |
} else if (strcmp(rtype, "subsystem") == 0) { |
success = session_subsystem_req(s); |
success = session_subsystem_req(s); |
} else if (strcmp(rtype, "env") == 0) { |
} else if (strcmp(rtype, "break") == 0) { |
success = session_env_req(s); |
success = session_break_req(s); |
} |
} |
} |
} |
if (strcmp(rtype, "window-change") == 0) { |
if (strcmp(rtype, "window-change") == 0) { |
success = session_window_change_req(s); |
success = session_window_change_req(s); |
} else if (strcmp(rtype, "break") == 0) { |
|
success = session_break_req(s); |
|
} |
} |
|
|
return success; |
return success; |
} |
} |
|
|
|
|
* (e.g., due to a dropped connection). |
* (e.g., due to a dropped connection). |
*/ |
*/ |
void |
void |
session_pty_cleanup2(Session *s) |
session_pty_cleanup2(void *session) |
{ |
{ |
|
Session *s = session; |
|
|
if (s == NULL) { |
if (s == NULL) { |
error("session_pty_cleanup: no session"); |
error("session_pty_cleanup: no session"); |
return; |
return; |
|
|
} |
} |
|
|
void |
void |
session_pty_cleanup(Session *s) |
session_pty_cleanup(void *session) |
{ |
{ |
PRIVSEP(session_pty_cleanup2(s)); |
PRIVSEP(session_pty_cleanup2(session)); |
} |
} |
|
|
static char * |
static char * |
|
|
void |
void |
session_close(Session *s) |
session_close(Session *s) |
{ |
{ |
int i; |
|
|
|
debug("session_close: session %d pid %ld", s->self, (long)s->pid); |
debug("session_close: session %d pid %ld", s->self, (long)s->pid); |
if (s->ttyfd != -1) |
if (s->ttyfd != -1) { |
|
fatal_remove_cleanup(session_pty_cleanup, (void *)s); |
session_pty_cleanup(s); |
session_pty_cleanup(s); |
|
} |
if (s->term) |
if (s->term) |
xfree(s->term); |
xfree(s->term); |
if (s->display) |
if (s->display) |
|
|
if (s->auth_proto) |
if (s->auth_proto) |
xfree(s->auth_proto); |
xfree(s->auth_proto); |
s->used = 0; |
s->used = 0; |
for (i = 0; i < s->num_env; i++) { |
|
xfree(s->env[i].name); |
|
xfree(s->env[i].val); |
|
} |
|
if (s->env != NULL) |
|
xfree(s->env); |
|
session_proctitle(s); |
session_proctitle(s); |
} |
} |
|
|
|
|
* delay detach of session, but release pty, since |
* delay detach of session, but release pty, since |
* the fd's to the child are already closed |
* the fd's to the child are already closed |
*/ |
*/ |
if (s->ttyfd != -1) |
if (s->ttyfd != -1) { |
|
fatal_remove_cleanup(session_pty_cleanup, (void *)s); |
session_pty_cleanup(s); |
session_pty_cleanup(s); |
|
} |
return; |
return; |
} |
} |
/* detach by removing callback */ |
/* detach by removing callback */ |
|
|
do_authenticated2(Authctxt *authctxt) |
do_authenticated2(Authctxt *authctxt) |
{ |
{ |
server_loop2(authctxt); |
server_loop2(authctxt); |
} |
#if defined(GSSAPI) |
|
if (options.gss_cleanup_creds) |
void |
ssh_gssapi_cleanup_creds(NULL); |
do_cleanup(Authctxt *authctxt) |
|
{ |
|
static int called = 0; |
|
|
|
debug("do_cleanup"); |
|
|
|
/* no cleanup if we're in the child for login shell */ |
|
if (is_child) |
|
return; |
|
|
|
/* avoid double cleanup */ |
|
if (called) |
|
return; |
|
called = 1; |
|
|
|
if (authctxt == NULL) |
|
return; |
|
#ifdef KRB5 |
|
if (options.kerberos_ticket_cleanup && |
|
authctxt->krb5_ctx) |
|
krb5_cleanup_proc(authctxt); |
|
#endif |
#endif |
|
|
#ifdef GSSAPI |
|
if (compat20 && options.gss_cleanup_creds) |
|
ssh_gssapi_cleanup_creds(); |
|
#endif |
|
|
|
/* remove agent socket */ |
|
auth_sock_cleanup_proc(authctxt->pw); |
|
|
|
/* |
|
* Cleanup ptys/utmp only if privsep is disabled, |
|
* or if running in monitor. |
|
*/ |
|
if (!use_privsep || mm_is_monitor()) |
|
session_destroy_all(session_pty_cleanup2); |
|
} |
} |