]> andersk Git - openssh.git/blobdiff - session.c
- djm@cvs.openbsd.org 2010/01/30 02:54:53
[openssh.git] / session.c
index 2a1a25ac4a202508b1f67c7944f51f9af1f501a4..fd7acbe03bb1751b89748da23b2d342bd6925c1a 100644 (file)
--- a/session.c
+++ b/session.c
@@ -1,3 +1,4 @@
+/* $OpenBSD: session.c,v 1.251 2010/01/12 08:33:17 dtucker Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: session.c,v 1.186 2005/07/25 11:59:40 markus Exp $");
 
+#include <sys/types.h>
+#include <sys/param.h>
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <grp.h>
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "openbsd-compat/sys-queue.h"
+#include "xmalloc.h"
 #include "ssh.h"
 #include "ssh1.h"
 #include "ssh2.h"
-#include "xmalloc.h"
 #include "sshpty.h"
 #include "packet.h"
 #include "buffer.h"
@@ -46,7 +71,12 @@ RCSID("$OpenBSD: session.c,v 1.186 2005/07/25 11:59:40 markus Exp $");
 #include "uidswap.h"
 #include "compat.h"
 #include "channels.h"
-#include "bufaux.h"
+#include "key.h"
+#include "cipher.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+#include "hostfile.h"
 #include "auth.h"
 #include "auth-options.h"
 #include "pathnames.h"
@@ -55,28 +85,32 @@ RCSID("$OpenBSD: session.c,v 1.186 2005/07/25 11:59:40 markus Exp $");
 #include "sshlogin.h"
 #include "serverloop.h"
 #include "canohost.h"
+#include "misc.h"
 #include "session.h"
 #include "kex.h"
 #include "monitor_wrap.h"
+#include "sftp.h"
 
 #if defined(KRB5) && defined(USE_AFS)
 #include <kafs.h>
 #endif
 
-#ifdef GSSAPI
-#include "ssh-gss.h"
-#endif
+#define IS_INTERNAL_SFTP(c) \
+       (!strncmp(c, INTERNAL_SFTP_NAME, sizeof(INTERNAL_SFTP_NAME) - 1) && \
+        (c[sizeof(INTERNAL_SFTP_NAME) - 1] == '\0' || \
+         c[sizeof(INTERNAL_SFTP_NAME) - 1] == ' ' || \
+         c[sizeof(INTERNAL_SFTP_NAME) - 1] == '\t'))
 
 /* func */
 
 Session *session_new(void);
-void   session_set_fds(Session *, int, int, int);
+void   session_set_fds(Session *, int, int, int, int);
 void   session_pty_cleanup(Session *);
 void   session_proctitle(Session *);
 int    session_setup_x11fwd(Session *);
-void   do_exec_pty(Session *, const char *);
-void   do_exec_no_pty(Session *, const char *);
-void   do_exec(Session *, const char *);
+int    do_exec_pty(Session *, const char *);
+int    do_exec_no_pty(Session *, const char *);
+int    do_exec(Session *, const char *);
 void   do_login(Session *, const char *);
 #ifdef LOGIN_NEEDS_UTMPX
 static void    do_pre_login(Session *s);
@@ -104,8 +138,14 @@ extern Buffer loginmsg;
 const char *original_command = NULL;
 
 /* data */
-#define MAX_SESSIONS 10
-Session        sessions[MAX_SESSIONS];
+static int sessions_first_unused = -1;
+static int sessions_nalloc = 0;
+static Session *sessions = NULL;
+
+#define SUBSYSTEM_NONE                 0
+#define SUBSYSTEM_EXT                  1
+#define SUBSYSTEM_INT_SFTP             2
+#define SUBSYSTEM_INT_SFTP_ERROR       3
 
 #ifdef HAVE_LOGIN_CAP
 login_cap_t *lc;
@@ -135,7 +175,7 @@ static int
 auth_input_request_forwarding(struct passwd * pw)
 {
        Channel *nc;
-       int sock;
+       int sock = -1;
        struct sockaddr_un sunaddr;
 
        if (auth_sock_name != NULL) {
@@ -147,51 +187,69 @@ auth_input_request_forwarding(struct passwd * pw)
        temporarily_use_uid(pw);
 
        /* Allocate a buffer for the socket name, and format the name. */
-       auth_sock_name = xmalloc(MAXPATHLEN);
-       auth_sock_dir = xmalloc(MAXPATHLEN);
-       strlcpy(auth_sock_dir, "/tmp/ssh-XXXXXXXXXX", MAXPATHLEN);
+       auth_sock_dir = xstrdup("/tmp/ssh-XXXXXXXXXX");
 
        /* Create private directory for socket */
        if (mkdtemp(auth_sock_dir) == NULL) {
                packet_send_debug("Agent forwarding disabled: "
                    "mkdtemp() failed: %.100s", strerror(errno));
                restore_uid();
-               xfree(auth_sock_name);
                xfree(auth_sock_dir);
-               auth_sock_name = NULL;
                auth_sock_dir = NULL;
-               return 0;
+               goto authsock_err;
        }
-       snprintf(auth_sock_name, MAXPATHLEN, "%s/agent.%ld",
-                auth_sock_dir, (long) getpid());
+
+       xasprintf(&auth_sock_name, "%s/agent.%ld",
+           auth_sock_dir, (long) getpid());
 
        /* Create the socket. */
        sock = socket(AF_UNIX, SOCK_STREAM, 0);
-       if (sock < 0)
-               packet_disconnect("socket: %.100s", strerror(errno));
+       if (sock < 0) {
+               error("socket: %.100s", strerror(errno));
+               restore_uid();
+               goto authsock_err;
+       }
 
        /* Bind it to the name. */
        memset(&sunaddr, 0, sizeof(sunaddr));
        sunaddr.sun_family = AF_UNIX;
        strlcpy(sunaddr.sun_path, auth_sock_name, sizeof(sunaddr.sun_path));
 
-       if (bind(sock, (struct sockaddr *) & sunaddr, sizeof(sunaddr)) < 0)
-               packet_disconnect("bind: %.100s", strerror(errno));
+       if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) < 0) {
+               error("bind: %.100s", strerror(errno));
+               restore_uid();
+               goto authsock_err;
+       }
 
        /* Restore the privileged uid. */
        restore_uid();
 
        /* Start listening on the socket. */
-       if (listen(sock, SSH_LISTEN_BACKLOG) < 0)
-               packet_disconnect("listen: %.100s", strerror(errno));
+       if (listen(sock, SSH_LISTEN_BACKLOG) < 0) {
+               error("listen: %.100s", strerror(errno));
+               goto authsock_err;
+       }
 
        /* Allocate a channel for the authentication agent socket. */
        nc = channel_new("auth socket",
            SSH_CHANNEL_AUTH_SOCKET, sock, sock, -1,
            CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT,
            0, "auth socket", 1);
-       strlcpy(nc->path, auth_sock_name, sizeof(nc->path));
+       nc->path = xstrdup(auth_sock_name);
        return 1;
+
+ authsock_err:
+       if (auth_sock_name != NULL)
+               xfree(auth_sock_name);
+       if (auth_sock_dir != NULL) {
+               rmdir(auth_sock_dir);
+               xfree(auth_sock_dir);
+       }
+       if (sock != -1)
+               close(sock);
+       auth_sock_name = NULL;
+       auth_sock_dir = NULL;
+       return 0;
 }
 
 static void
@@ -209,15 +267,6 @@ do_authenticated(Authctxt *authctxt)
 {
        setproctitle("%s", authctxt->pw->pw_name);
 
-       /*
-        * Cancel the alarm we set to limit the time taken for
-        * authentication.
-        */
-       alarm(0);
-       if (startup_pipe != -1) {
-               close(startup_pipe);
-               startup_pipe = -1;
-       }
        /* setup the channel layer */
        if (!no_port_forwarding_flag && options.allow_tcp_forwarding)
                channel_permit_all_opens();
@@ -313,7 +362,8 @@ do_authenticated1(Authctxt *authctxt)
                        break;
 
                case SSH_CMSG_AGENT_REQUEST_FORWARDING:
-                       if (no_agent_forwarding_flag || compat13) {
+                       if (!options.allow_agent_forwarding ||
+                           no_agent_forwarding_flag || compat13) {
                                debug("Authentication agent forwarding not permitted for this authentication.");
                                break;
                        }
@@ -331,7 +381,11 @@ do_authenticated1(Authctxt *authctxt)
                                break;
                        }
                        debug("Received TCP/IP port forwarding request.");
-                       channel_input_port_forward_request(s->pw->pw_uid == 0, options.gateway_ports);
+                       if (channel_input_port_forward_request(s->pw->pw_uid == 0,
+                           options.gateway_ports) < 0) {
+                               debug("Port forwarding failed.");
+                               break;
+                       }
                        success = 1;
                        break;
 
@@ -345,10 +399,14 @@ do_authenticated1(Authctxt *authctxt)
                        if (type == SSH_CMSG_EXEC_CMD) {
                                command = packet_get_string(&dlen);
                                debug("Exec command '%.500s'", command);
-                               do_exec(s, command);
+                               if (do_exec(s, command) != 0)
+                                       packet_disconnect(
+                                           "command execution failed");
                                xfree(command);
                        } else {
-                               do_exec(s, NULL);
+                               if (do_exec(s, NULL) != 0)
+                                       packet_disconnect(
+                                           "shell execution failed");
                        }
                        packet_check_eom();
                        session_close(s);
@@ -373,46 +431,84 @@ do_authenticated1(Authctxt *authctxt)
        }
 }
 
+#define USE_PIPES
 /*
  * This is called to fork and execute a command when we have no tty.  This
  * will call do_child from the child, and server_loop from the parent after
  * setting up file descriptors and such.
  */
-void
+int
 do_exec_no_pty(Session *s, const char *command)
 {
        pid_t pid;
 
 #ifdef USE_PIPES
        int pin[2], pout[2], perr[2];
+
        /* Allocate pipes for communicating with the program. */
-       if (pipe(pin) < 0 || pipe(pout) < 0 || pipe(perr) < 0)
-               packet_disconnect("Could not create pipes: %.100s",
-                                 strerror(errno));
-#else /* USE_PIPES */
+       if (pipe(pin) < 0) {
+               error("%s: pipe in: %.100s", __func__, strerror(errno));
+               return -1;
+       }
+       if (pipe(pout) < 0) {
+               error("%s: pipe out: %.100s", __func__, strerror(errno));
+               close(pin[0]);
+               close(pin[1]);
+               return -1;
+       }
+       if (pipe(perr) < 0) {
+               error("%s: pipe err: %.100s", __func__, strerror(errno));
+               close(pin[0]);
+               close(pin[1]);
+               close(pout[0]);
+               close(pout[1]);
+               return -1;
+       }
+#else
        int inout[2], err[2];
+
        /* Uses socket pairs to communicate with the program. */
-       if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) < 0 ||
-           socketpair(AF_UNIX, SOCK_STREAM, 0, err) < 0)
-               packet_disconnect("Could not create socket pairs: %.100s",
-                                 strerror(errno));
-#endif /* USE_PIPES */
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) < 0) {
+               error("%s: socketpair #1: %.100s", __func__, strerror(errno));
+               return -1;
+       }
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, err) < 0) {
+               error("%s: socketpair #2: %.100s", __func__, strerror(errno));
+               close(inout[0]);
+               close(inout[1]);
+               return -1;
+       }
+#endif
+
        if (s == NULL)
                fatal("do_exec_no_pty: no session");
 
        session_proctitle(s);
 
-#if defined(USE_PAM)
-       if (options.use_pam && !use_privsep)
-               do_pam_setcred(1);
-#endif /* USE_PAM */
-
        /* Fork the child. */
-       if ((pid = fork()) == 0) {
+       switch ((pid = fork())) {
+       case -1:
+               error("%s: fork: %.100s", __func__, strerror(errno));
+#ifdef USE_PIPES
+               close(pin[0]);
+               close(pin[1]);
+               close(pout[0]);
+               close(pout[1]);
+               close(perr[0]);
+               close(perr[1]);
+#else
+               close(inout[0]);
+               close(inout[1]);
+               close(err[0]);
+               close(err[1]);
+#endif
+               return -1;
+       case 0:
                is_child = 1;
 
                /* 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);
 
                /*
                 * Create a new session and process group since the 4.4BSD
@@ -442,7 +538,7 @@ do_exec_no_pty(Session *s, const char *command)
                if (dup2(perr[1], 2) < 0)
                        perror("dup2 stderr");
                close(perr[1]);
-#else /* USE_PIPES */
+#else
                /*
                 * Redirect stdin, stdout, and stderr.  Stdin and stdout will
                 * use the same socket, as some programs (particularly rdist)
@@ -452,11 +548,14 @@ do_exec_no_pty(Session *s, const char *command)
                close(err[1]);
                if (dup2(inout[0], 0) < 0)      /* stdin */
                        perror("dup2 stdin");
-               if (dup2(inout[0], 1) < 0)      /* stdout.  Note: same socket as stdin. */
+               if (dup2(inout[0], 1) < 0)      /* stdout (same as stdin) */
                        perror("dup2 stdout");
+               close(inout[0]);
                if (dup2(err[0], 2) < 0)        /* stderr */
                        perror("dup2 stderr");
-#endif /* USE_PIPES */
+               close(err[0]);
+#endif
+
 
 #ifdef _UNICOS
                cray_init_job(s->pw); /* set up cray jid and tmpdir */
@@ -465,19 +564,28 @@ do_exec_no_pty(Session *s, const char *command)
                /* Do processing for the child (exec command etc). */
                do_child(s, command);
                /* NOTREACHED */
+       default:
+               break;
        }
+
 #ifdef _UNICOS
        signal(WJSIGNAL, cray_job_termination_handler);
 #endif /* _UNICOS */
 #ifdef HAVE_CYGWIN
-       if (is_winnt)
-               cygwin_set_impersonation_token(INVALID_HANDLE_VALUE);
+       cygwin_set_impersonation_token(INVALID_HANDLE_VALUE);
 #endif
-       if (pid < 0)
-               packet_disconnect("fork failed: %.100s", strerror(errno));
+
        s->pid = pid;
        /* Set interactive/non-interactive mode. */
        packet_set_interactive(s->display != NULL);
+
+       /*
+        * Clear loginmsg, since 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);
+
 #ifdef USE_PIPES
        /* We are the parent.  Close the child sides of the pipes. */
        close(pin[0]);
@@ -489,35 +597,32 @@ do_exec_no_pty(Session *s, const char *command)
                        close(perr[0]);
                        perr[0] = -1;
                }
-               session_set_fds(s, pin[1], pout[0], perr[0]);
+               session_set_fds(s, pin[1], pout[0], perr[0], 0);
        } else {
                /* Enter the interactive session. */
                server_loop(pid, pin[1], pout[0], perr[0]);
                /* server_loop has closed pin[1], pout[0], and perr[0]. */
        }
-#else /* USE_PIPES */
+#else
        /* We are the parent.  Close the child sides of the socket pairs. */
        close(inout[0]);
        close(err[0]);
 
-       /*
-        * Clear loginmsg, since 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);
-
        /*
         * Enter the interactive session.  Note: server_loop must be able to
         * handle the case that fdin and fdout are the same.
         */
        if (compat20) {
-               session_set_fds(s, inout[1], inout[1], s->is_subsystem ? -1 : err[1]);
+               session_set_fds(s, inout[1], inout[1],
+                   s->is_subsystem ? -1 : err[1], 0);
+               if (s->is_subsystem)
+                       close(err[1]);
        } else {
                server_loop(pid, inout[1], inout[1], err[1]);
                /* server_loop has closed inout[1] and err[1]. */
        }
-#endif /* USE_PIPES */
+#endif
+       return 0;
 }
 
 /*
@@ -526,7 +631,7 @@ do_exec_no_pty(Session *s, const char *command)
  * setting up file descriptors, controlling tty, updating wtmp, utmp,
  * lastlog, and other such operations.
  */
-void
+int
 do_exec_pty(Session *s, const char *command)
 {
        int fdout, ptyfd, ttyfd, ptymaster;
@@ -537,20 +642,46 @@ do_exec_pty(Session *s, const char *command)
        ptyfd = s->ptyfd;
        ttyfd = s->ttyfd;
 
-#if defined(USE_PAM)
-       if (options.use_pam) {
-               do_pam_set_tty(s->tty);
-               if (!use_privsep)
-                       do_pam_setcred(1);
+       /*
+        * Create another descriptor of the pty master side for use as the
+        * standard input.  We could use the original descriptor, but this
+        * simplifies code in server_loop.  The descriptor is bidirectional.
+        * Do this before forking (and cleanup in the child) so as to
+        * detect and gracefully fail out-of-fd conditions.
+        */
+       if ((fdout = dup(ptyfd)) < 0) {
+               error("%s: dup #1: %s", __func__, strerror(errno));
+               close(ttyfd);
+               close(ptyfd);
+               return -1;
+       }
+       /* we keep a reference to the pty master */
+       if ((ptymaster = dup(ptyfd)) < 0) {
+               error("%s: dup #2: %s", __func__, strerror(errno));
+               close(ttyfd);
+               close(ptyfd);
+               close(fdout);
+               return -1;
        }
-#endif
 
        /* Fork the child. */
-       if ((pid = fork()) == 0) {
+       switch ((pid = fork())) {
+       case -1:
+               error("%s: fork: %.100s", __func__, strerror(errno));
+               close(fdout);
+               close(ptymaster);
+               close(ttyfd);
+               close(ptyfd);
+               return -1;
+       case 0:
                is_child = 1;
 
+               close(fdout);
+               close(ptymaster);
+
                /* 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);
                /* Close the master side of the pseudo tty. */
                close(ptyfd);
 
@@ -581,48 +712,38 @@ do_exec_pty(Session *s, const char *command)
                        do_pre_login(s);
 # endif
 #endif
-
-               /* Do common processing for the child, such as execing the command. */
+               /*
+                * Do common processing for the child, such as execing
+                * the command.
+                */
                do_child(s, command);
                /* NOTREACHED */
+       default:
+               break;
        }
+
 #ifdef _UNICOS
        signal(WJSIGNAL, cray_job_termination_handler);
 #endif /* _UNICOS */
 #ifdef HAVE_CYGWIN
-       if (is_winnt)
-               cygwin_set_impersonation_token(INVALID_HANDLE_VALUE);
+       cygwin_set_impersonation_token(INVALID_HANDLE_VALUE);
 #endif
-       if (pid < 0)
-               packet_disconnect("fork failed: %.100s", strerror(errno));
+
        s->pid = pid;
 
        /* Parent.  Close the slave side of the pseudo tty. */
        close(ttyfd);
 
-       /*
-        * Create another descriptor of the pty master side for use as the
-        * standard input.  We could use the original descriptor, but this
-        * simplifies code in server_loop.  The descriptor is bidirectional.
-        */
-       fdout = dup(ptyfd);
-       if (fdout < 0)
-               packet_disconnect("dup #1 failed: %.100s", strerror(errno));
-
-       /* we keep a reference to the pty master */
-       ptymaster = dup(ptyfd);
-       if (ptymaster < 0)
-               packet_disconnect("dup #2 failed: %.100s", strerror(errno));
-       s->ptymaster = ptymaster;
-
        /* Enter interactive session. */
+       s->ptymaster = ptymaster;
        packet_set_interactive(1);
        if (compat20) {
-               session_set_fds(s, ptyfd, fdout, -1);
+               session_set_fds(s, ptyfd, fdout, -1, 1);
        } else {
                server_loop(pid, ptyfd, fdout, -1);
                /* server_loop _has_ closed ptyfd and fdout. */
        }
+       return 0;
 }
 
 #ifdef LOGIN_NEEDS_UTMPX
@@ -641,7 +762,7 @@ do_pre_login(Session *s)
        fromlen = sizeof(from);
        if (packet_connection_is_on_socket()) {
                if (getpeername(packet_get_connection_in(),
-                   (struct sockaddr *) & from, &fromlen) < 0) {
+                   (struct sockaddr *)&from, &fromlen) < 0) {
                        debug("getpeername: %.100s", strerror(errno));
                        cleanup_exit(255);
                }
@@ -657,13 +778,29 @@ do_pre_login(Session *s)
  * This is called to fork and execute a command.  If another command is
  * to be forced, execute that instead.
  */
-void
+int
 do_exec(Session *s, const char *command)
 {
-       if (forced_command) {
+       int ret;
+
+       if (options.adm_forced_command) {
+               original_command = command;
+               command = options.adm_forced_command;
+               if (IS_INTERNAL_SFTP(command)) {
+                       s->is_subsystem = s->is_subsystem ?
+                           SUBSYSTEM_INT_SFTP : SUBSYSTEM_INT_SFTP_ERROR;
+               } else if (s->is_subsystem)
+                       s->is_subsystem = SUBSYSTEM_EXT;
+               debug("Forced command (config) '%.900s'", command);
+       } else if (forced_command) {
                original_command = command;
                command = forced_command;
-               debug("Forced command '%.900s'", command);
+               if (IS_INTERNAL_SFTP(command)) {
+                       s->is_subsystem = s->is_subsystem ?
+                           SUBSYSTEM_INT_SFTP : SUBSYSTEM_INT_SFTP_ERROR;
+               } else if (s->is_subsystem)
+                       s->is_subsystem = SUBSYSTEM_EXT;
+               debug("Forced command (key option) '%.900s'", command);
        }
 
 #ifdef SSH_AUDIT_EVENTS
@@ -677,11 +814,10 @@ do_exec(Session *s, const char *command)
                PRIVSEP(audit_run_command(shell));
        }
 #endif
-
        if (s->ttyfd != -1)
-               do_exec_pty(s, command);
+               ret = do_exec_pty(s, command);
        else
-               do_exec_no_pty(s, command);
+               ret = do_exec_no_pty(s, command);
 
        original_command = NULL;
 
@@ -691,6 +827,8 @@ do_exec(Session *s, const char *command)
         * multiple copies of the login messages.
         */
        buffer_clear(&loginmsg);
+
+       return ret;
 }
 
 /* administrative, login(1)-like work */
@@ -710,7 +848,7 @@ do_login(Session *s, const char *command)
        fromlen = sizeof(from);
        if (packet_connection_is_on_socket()) {
                if (getpeername(packet_get_connection_in(),
-                   (struct sockaddr *) & from, &fromlen) < 0) {
+                   (struct sockaddr *)&from, &fromlen) < 0) {
                        debug("getpeername: %.100s", strerror(errno));
                        cleanup_exit(255);
                }
@@ -795,7 +933,7 @@ check_quietlogin(Session *s, const char *command)
 
 /*
  * Sets the value of the given variable in the environment.  If the variable
- * already exists, its value is overriden.
+ * already exists, its value is overridden.
  */
 void
 child_set_env(char ***envp, u_int *envsizep, const char *name,
@@ -835,7 +973,7 @@ child_set_env(char ***envp, u_int *envsizep, const char *name,
                        if (envsize >= 1000)
                                fatal("child_set_env: too many env vars");
                        envsize += 50;
-                       env = (*envp) = xrealloc(env, envsize * sizeof(char *));
+                       env = (*envp) = xrealloc(env, envsize, sizeof(char *));
                        *envsizep = envsize;
                }
                /* Need to set the NULL pointer at end of array beyond the new slot. */
@@ -873,8 +1011,9 @@ read_environment_file(char ***env, u_int *envsize,
                        ;
                if (!*cp || *cp == '#' || *cp == '\n')
                        continue;
-               if (strchr(cp, '\n'))
-                       *strchr(cp, '\n') = '\0';
+
+               cp[strcspn(cp, "\n")] = '\0';
+
                value = strchr(cp, '=');
                if (value == NULL) {
                        fprintf(stderr, "Bad line %u in %.100s\n", lineno,
@@ -976,12 +1115,15 @@ do_setup_env(Session *s, const char *shell)
 {
        char buf[256];
        u_int i, envsize;
-       char **env, *laddr, *path = NULL;
+       char **env, *laddr;
        struct passwd *pw = s->pw;
+#if !defined (HAVE_LOGIN_CAP) && !defined (HAVE_CYGWIN)
+       char *path = NULL;
+#endif
 
        /* Initialize the environment. */
        envsize = 100;
-       env = xmalloc(envsize * sizeof(char *));
+       env = xcalloc(envsize, sizeof(char *));
        env[0] = NULL;
 
 #ifdef HAVE_CYGWIN
@@ -1174,8 +1316,9 @@ do_rc_files(Session *s, const char *shell)
        do_xauth =
            s->display != NULL && s->auth_proto != NULL && s->auth_data != NULL;
 
-       /* ignore _PATH_SSH_USER_RC for subsystems */
-       if (!s->is_subsystem && (stat(_PATH_SSH_USER_RC, &st) >= 0)) {
+       /* ignore _PATH_SSH_USER_RC for subsystems and admin forced commands */
+       if (!s->is_subsystem && options.adm_forced_command == NULL &&
+           !no_user_rc && stat(_PATH_SSH_USER_RC, &st) >= 0) {
                snprintf(cmd, sizeof cmd, "%s -c '%s %s'",
                    shell, _PATH_BSHELL, _PATH_SSH_USER_RC);
                if (debug_flag)
@@ -1234,60 +1377,115 @@ static void
 do_nologin(struct passwd *pw)
 {
        FILE *f = NULL;
-       char buf[1024];
+       char buf[1024], *nl, *def_nl = _PATH_NOLOGIN;
+       struct stat sb;
 
 #ifdef HAVE_LOGIN_CAP
-       if (!login_getcapbool(lc, "ignorenologin", 0) && pw->pw_uid)
-               f = fopen(login_getcapstr(lc, "nologin", _PATH_NOLOGIN,
-                   _PATH_NOLOGIN), "r");
+       if (login_getcapbool(lc, "ignorenologin", 0) && pw->pw_uid)
+               return;
+       nl = login_getcapstr(lc, "nologin", def_nl, def_nl);
 #else
-       if (pw->pw_uid)
-               f = fopen(_PATH_NOLOGIN, "r");
+       if (pw->pw_uid == 0)
+               return;
+       nl = def_nl;
 #endif
-       if (f) {
-               /* /etc/nologin exists.  Print its contents and exit. */
-               logit("User %.100s not allowed because %s exists",
-                   pw->pw_name, _PATH_NOLOGIN);
-               while (fgets(buf, sizeof(buf), f))
-                       fputs(buf, stderr);
-               fclose(f);
-               fflush(NULL);
-               exit(254);
+       if (stat(nl, &sb) == -1) {
+               if (nl != def_nl)
+                       xfree(nl);
+               return;
        }
+
+       /* /etc/nologin exists.  Print its contents if we can and exit. */
+       logit("User %.100s not allowed because %s exists", pw->pw_name, nl);
+       if ((f = fopen(nl, "r")) != NULL) {
+               while (fgets(buf, sizeof(buf), f))
+                       fputs(buf, stderr);
+               fclose(f);
+       }
+       exit(254);
+}
+
+/*
+ * Chroot into a directory after checking it for safety: all path components
+ * must be root-owned directories with strict permissions.
+ */
+static void
+safely_chroot(const char *path, uid_t uid)
+{
+       const char *cp;
+       char component[MAXPATHLEN];
+       struct stat st;
+
+       if (*path != '/')
+               fatal("chroot path does not begin at root");
+       if (strlen(path) >= sizeof(component))
+               fatal("chroot path too long");
+
+       /*
+        * Descend the path, checking that each component is a
+        * root-owned directory with strict permissions.
+        */
+       for (cp = path; cp != NULL;) {
+               if ((cp = strchr(cp, '/')) == NULL)
+                       strlcpy(component, path, sizeof(component));
+               else {
+                       cp++;
+                       memcpy(component, path, cp - path);
+                       component[cp - path] = '\0';
+               }
+       
+               debug3("%s: checking '%s'", __func__, component);
+
+               if (stat(component, &st) != 0)
+                       fatal("%s: stat(\"%s\"): %s", __func__,
+                           component, strerror(errno));
+               if (st.st_uid != 0 || (st.st_mode & 022) != 0)
+                       fatal("bad ownership or modes for chroot "
+                           "directory %s\"%s\"", 
+                           cp == NULL ? "" : "component ", component);
+               if (!S_ISDIR(st.st_mode))
+                       fatal("chroot path %s\"%s\" is not a directory",
+                           cp == NULL ? "" : "component ", component);
+
+       }
+
+       if (chdir(path) == -1)
+               fatal("Unable to chdir to chroot path \"%s\": "
+                   "%s", path, strerror(errno));
+       if (chroot(path) == -1)
+               fatal("chroot(\"%s\"): %s", path, strerror(errno));
+       if (chdir("/") == -1)
+               fatal("%s: chdir(/) after chroot: %s",
+                   __func__, strerror(errno));
+       verbose("Changed root directory to \"%s\"", path);
 }
 
 /* Set login name, uid, gid, and groups. */
 void
 do_setusercontext(struct passwd *pw)
 {
+       char *chroot_path, *tmp;
+
+#ifdef WITH_SELINUX
+       /* Cache selinux status for later use */
+       (void)ssh_selinux_enabled();
+#endif
+
 #ifndef HAVE_CYGWIN
        if (getuid() == 0 || geteuid() == 0)
 #endif /* HAVE_CYGWIN */
        {
-
-#ifdef HAVE_SETPCRED
-               if (setpcred(pw->pw_name, (char **)NULL) == -1)
-                       fatal("Failed to set process credentials");
-#endif /* HAVE_SETPCRED */
 #ifdef HAVE_LOGIN_CAP
 # ifdef __bsdi__
                setpgid(0, 0);
 # endif
-#ifdef GSSAPI
-               if (options.gss_authentication) {
-                       temporarily_use_uid(pw);
-                       ssh_gssapi_storecreds();
-                       restore_uid();
-               }
-#endif
 # ifdef USE_PAM
                if (options.use_pam) {
-                       do_pam_session();
-                       do_pam_setcred(0);
+                       do_pam_setcred(use_privsep);
                }
 # endif /* USE_PAM */
                if (setusercontext(lc, pw, pw->pw_uid,
-                   (LOGIN_SETALL & ~LOGIN_SETPATH)) < 0) {
+                   (LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETUSER))) < 0) {
                        perror("unable to set user context");
                        exit(1);
                }
@@ -1310,13 +1508,6 @@ do_setusercontext(struct passwd *pw)
                        exit(1);
                }
                endgrent();
-#ifdef GSSAPI
-               if (options.gss_authentication) {
-                       temporarily_use_uid(pw);
-                       ssh_gssapi_storecreds();
-                       restore_uid();
-               }
-#endif
 # ifdef USE_PAM
                /*
                 * PAM credentials may take the form of supplementary groups.
@@ -1324,31 +1515,54 @@ do_setusercontext(struct passwd *pw)
                 * Reestablish them here.
                 */
                if (options.use_pam) {
-                       do_pam_session();
-                       do_pam_setcred(0);
+                       do_pam_setcred(use_privsep);
                }
 # endif /* USE_PAM */
 # if defined(WITH_IRIX_PROJECT) || defined(WITH_IRIX_JOBS) || defined(WITH_IRIX_ARRAY)
                irix_setusercontext(pw);
-#  endif /* defined(WITH_IRIX_PROJECT) || defined(WITH_IRIX_JOBS) || defined(WITH_IRIX_ARRAY) */
+# endif /* defined(WITH_IRIX_PROJECT) || defined(WITH_IRIX_JOBS) || defined(WITH_IRIX_ARRAY) */
 # ifdef _AIX
                aix_usrinfo(pw);
 # endif /* _AIX */
-#if defined(HAVE_LIBIAF)  &&  !defined(BROKEN_LIBIAF)
+# ifdef USE_LIBIAF
                if (set_id(pw->pw_name) != 0) {
                        exit(1);
                }
-#endif /* HAVE_LIBIAF  && !BROKEN_LIBIAF */
+# endif /* USE_LIBIAF */
+#endif
+
+               if (options.chroot_directory != NULL &&
+                   strcasecmp(options.chroot_directory, "none") != 0) {
+                        tmp = tilde_expand_filename(options.chroot_directory,
+                           pw->pw_uid);
+                       chroot_path = percent_expand(tmp, "h", pw->pw_dir,
+                           "u", pw->pw_name, (char *)NULL);
+                       safely_chroot(chroot_path, pw->pw_uid);
+                       free(tmp);
+                       free(chroot_path);
+               }
+
+#ifdef HAVE_SETPCRED
+               if (setpcred(pw->pw_name, (char **)NULL) == -1)
+                       fatal("Failed to set process credentials");
+#endif /* HAVE_SETPCRED */
+#ifdef HAVE_LOGIN_CAP
+               if (setusercontext(lc, pw, pw->pw_uid, LOGIN_SETUSER) < 0) {
+                       perror("unable to set user context (setuser)");
+                       exit(1);
+               }
+#else
                /* Permanently switch to the desired uid. */
                permanently_set_uid(pw);
 #endif
        }
 
-#ifdef HAVE_CYGWIN
-       if (is_winnt)
-#endif
        if (getuid() != pw->pw_uid || geteuid() != pw->pw_uid)
                fatal("Failed to set uids to %u.", (u_int) pw->pw_uid);
+
+#ifdef WITH_SELINUX
+       ssh_selinux_setup_exec_context(pw->pw_name);
+#endif
 }
 
 static void
@@ -1419,7 +1633,7 @@ child_close_fds(void)
        endpwent();
 
        /*
-        * Close any extra open file descriptors so that we don\'t have them
+        * 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.
@@ -1433,14 +1647,16 @@ child_close_fds(void)
  * environment, closing extra file descriptors, setting the user and group
  * ids, and executing the command or shell.
  */
+#define ARGV_MAX 10
 void
 do_child(Session *s, const char *command)
 {
        extern char **environ;
        char **env;
-       char *argv[10];
+       char *argv[ARGV_MAX];
        const char *shell, *shell0, *hostname = NULL;
        struct passwd *pw = s->pw;
+       int r = 0;
 
        /* remove hostkey from the child's memory */
        destroy_sensitive_data();
@@ -1554,21 +1770,52 @@ do_child(Session *s, const char *command)
        }
 #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) {
-               fprintf(stderr, "Could not chdir to home directory %s: %s\n",
-                   pw->pw_dir, strerror(errno));
+               /* Suppress missing homedir warning for chroot case */
 #ifdef HAVE_LOGIN_CAP
-               if (login_getcapbool(lc, "requirehome", 0))
-                       exit(1);
+               r = login_getcapbool(lc, "requirehome", 0);
 #endif
+               if (r || options.chroot_directory == NULL)
+                       fprintf(stderr, "Could not chdir to home "
+                           "directory %s: %s\n", pw->pw_dir,
+                           strerror(errno));
+               if (r)
+                       exit(1);
        }
 
+       closefrom(STDERR_FILENO + 1);
+
        if (!options.use_login)
                do_rc_files(s, shell);
 
        /* restore SIGPIPE for child */
-       signal(SIGPIPE,  SIG_DFL);
+       signal(SIGPIPE, SIG_DFL);
+
+       if (s->is_subsystem == SUBSYSTEM_INT_SFTP_ERROR) {
+               printf("This service allows sftp connections only.\n");
+               fflush(NULL);
+               exit(1);
+       } else if (s->is_subsystem == SUBSYSTEM_INT_SFTP) {
+               extern int optind, optreset;
+               int i;
+               char *p, *args;
+
+               setproctitle("%s@%s", s->pw->pw_name, INTERNAL_SFTP_NAME);
+               args = xstrdup(command ? command : "sftp-server");
+               for (i = 0, (p = strtok(args, " ")); p; (p = strtok(NULL, " ")))
+                       if (i < ARGV_MAX - 1)
+                               argv[i++] = p;
+               argv[i] = NULL;
+               optind = optreset = 1;
+               __progname = argv[0];
+#ifdef WITH_SELINUX
+               ssh_selinux_change_context("sftpd_t");
+#endif
+               exit(sftp_server_main(i, argv, s->pw));
+       }
+
+       fflush(NULL);
 
        if (options.use_login) {
                launch_login(pw, hostname);
@@ -1621,43 +1868,79 @@ do_child(Session *s, const char *command)
        exit(1);
 }
 
+void
+session_unused(int id)
+{
+       debug3("%s: session id %d unused", __func__, id);
+       if (id >= options.max_sessions ||
+           id >= sessions_nalloc) {
+               fatal("%s: insane session id %d (max %d nalloc %d)",
+                   __func__, id, options.max_sessions, sessions_nalloc);
+       }
+       bzero(&sessions[id], sizeof(*sessions));
+       sessions[id].self = id;
+       sessions[id].used = 0;
+       sessions[id].chanid = -1;
+       sessions[id].ptyfd = -1;
+       sessions[id].ttyfd = -1;
+       sessions[id].ptymaster = -1;
+       sessions[id].x11_chanids = NULL;
+       sessions[id].next_unused = sessions_first_unused;
+       sessions_first_unused = id;
+}
+
 Session *
 session_new(void)
 {
-       int i;
-       static int did_init = 0;
-       if (!did_init) {
-               debug("session_new: init");
-               for (i = 0; i < MAX_SESSIONS; i++) {
-                       sessions[i].used = 0;
+       Session *s, *tmp;
+
+       if (sessions_first_unused == -1) {
+               if (sessions_nalloc >= options.max_sessions)
+                       return NULL;
+               debug2("%s: allocate (allocated %d max %d)",
+                   __func__, sessions_nalloc, options.max_sessions);
+               tmp = xrealloc(sessions, sessions_nalloc + 1,
+                   sizeof(*sessions));
+               if (tmp == NULL) {
+                       error("%s: cannot allocate %d sessions",
+                           __func__, sessions_nalloc + 1);
+                       return NULL;
                }
-               did_init = 1;
+               sessions = tmp;
+               session_unused(sessions_nalloc++);
        }
-       for (i = 0; i < MAX_SESSIONS; i++) {
-               Session *s = &sessions[i];
-               if (! s->used) {
-                       memset(s, 0, sizeof(*s));
-                       s->chanid = -1;
-                       s->ptyfd = -1;
-                       s->ttyfd = -1;
-                       s->used = 1;
-                       s->self = i;
-                       s->x11_chanids = NULL;
-                       debug("session_new: session %d", i);
-                       return s;
-               }
+
+       if (sessions_first_unused >= sessions_nalloc ||
+           sessions_first_unused < 0) {
+               fatal("%s: insane first_unused %d max %d nalloc %d",
+                   __func__, sessions_first_unused, options.max_sessions,
+                   sessions_nalloc);
        }
-       return NULL;
+
+       s = &sessions[sessions_first_unused];
+       if (s->used) {
+               fatal("%s: session %d already used",
+                   __func__, sessions_first_unused);
+       }
+       sessions_first_unused = s->next_unused;
+       s->used = 1;
+       s->next_unused = -1;
+       debug("session_new: session %d", s->self);
+
+       return s;
 }
 
 static void
 session_dump(void)
 {
        int i;
-       for (i = 0; i < MAX_SESSIONS; i++) {
+       for (i = 0; i < sessions_nalloc; i++) {
                Session *s = &sessions[i];
-               debug("dump: used %d session %d %p channel %d pid %ld",
+
+               debug("dump: used %d next_unused %d session %d %p "
+                   "channel %d pid %ld",
                    s->used,
+                   s->next_unused,
                    s->self,
                    s,
                    s->chanid,
@@ -1687,7 +1970,7 @@ Session *
 session_by_tty(char *tty)
 {
        int i;
-       for (i = 0; i < MAX_SESSIONS; i++) {
+       for (i = 0; i < sessions_nalloc; i++) {
                Session *s = &sessions[i];
                if (s->used && s->ttyfd != -1 && strcmp(s->tty, tty) == 0) {
                        debug("session_by_tty: session %d tty %s", i, tty);
@@ -1703,10 +1986,11 @@ static Session *
 session_by_channel(int id)
 {
        int i;
-       for (i = 0; i < MAX_SESSIONS; i++) {
+       for (i = 0; i < sessions_nalloc; i++) {
                Session *s = &sessions[i];
                if (s->used && s->chanid == id) {
-                       debug("session_by_channel: session %d channel %d", i, id);
+                       debug("session_by_channel: session %d channel %d",
+                           i, id);
                        return s;
                }
        }
@@ -1720,7 +2004,7 @@ session_by_x11_channel(int id)
 {
        int i, j;
 
-       for (i = 0; i < MAX_SESSIONS; i++) {
+       for (i = 0; i < sessions_nalloc; i++) {
                Session *s = &sessions[i];
 
                if (s->x11_chanids == NULL || !s->used)
@@ -1743,7 +2027,7 @@ session_by_pid(pid_t pid)
 {
        int i;
        debug("session_by_pid: pid %ld", (long)pid);
-       for (i = 0; i < MAX_SESSIONS; i++) {
+       for (i = 0; i < sessions_nalloc; i++) {
                Session *s = &sessions[i];
                if (s->used && s->pid == pid)
                        return s;
@@ -1799,7 +2083,8 @@ session_pty_req(Session *s)
 
        /* Allocate a pty and open it. */
        debug("Allocating pty.");
-       if (!PRIVSEP(pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, sizeof(s->tty)))) {
+       if (!PRIVSEP(pty_allocate(&s->ptyfd, &s->ttyfd, s->tty,
+           sizeof(s->tty)))) {
                if (s->term)
                        xfree(s->term);
                s->term = NULL;
@@ -1832,7 +2117,7 @@ session_subsystem_req(Session *s)
        struct stat st;
        u_int len;
        int success = 0;
-       char *cmd, *subsys = packet_get_string(&len);
+       char *prog, *cmd, *subsys = packet_get_string(&len);
        u_int i;
 
        packet_check_eom();
@@ -1840,16 +2125,19 @@ session_subsystem_req(Session *s)
 
        for (i = 0; i < options.num_subsystems; i++) {
                if (strcmp(subsys, options.subsystem_name[i]) == 0) {
-                       cmd = options.subsystem_command[i];
-                       if (stat(cmd, &st) < 0) {
-                               error("subsystem: cannot stat %s: %s", cmd,
-                                   strerror(errno));
-                               break;
+                       prog = options.subsystem_command[i];
+                       cmd = options.subsystem_args[i];
+                       if (strcmp(INTERNAL_SFTP_NAME, prog) == 0) {
+                               s->is_subsystem = SUBSYSTEM_INT_SFTP;
+                               debug("subsystem: %s", prog);
+                       } else {
+                               if (stat(prog, &st) < 0)
+                                       debug("subsystem: cannot stat %s: %s",
+                                           prog, strerror(errno));
+                               s->is_subsystem = SUBSYSTEM_EXT;
+                               debug("subsystem: exec() %s", cmd);
                        }
-                       debug("subsystem: exec() %s", cmd);
-                       s->is_subsystem = 1;
-                       do_exec(s, cmd);
-                       success = 1;
+                       success = do_exec(s, cmd) == 0;
                        break;
                }
        }
@@ -1869,7 +2157,7 @@ session_x11_req(Session *s)
 
        if (s->auth_proto != NULL || s->auth_data != NULL) {
                error("session_x11_req: session %d: "
-                   "x11 fowarding already active", s->self);
+                   "x11 forwarding already active", s->self);
                return 0;
        }
        s->single_connection = packet_get_char();
@@ -1892,19 +2180,19 @@ static int
 session_shell_req(Session *s)
 {
        packet_check_eom();
-       do_exec(s, NULL);
-       return 1;
+       return do_exec(s, NULL) == 0;
 }
 
 static int
 session_exec_req(Session *s)
 {
-       u_int len;
+       u_int len, success;
+
        char *command = packet_get_string(&len);
        packet_check_eom();
-       do_exec(s, command);
+       success = do_exec(s, command) == 0;
        xfree(command);
-       return 1;
+       return success;
 }
 
 static int
@@ -1914,8 +2202,7 @@ session_break_req(Session *s)
        packet_get_int();       /* ignored */
        packet_check_eom();
 
-       if (s->ttyfd == -1 ||
-           tcsendbreak(s->ttyfd, 0) < 0)
+       if (s->ttyfd == -1 || tcsendbreak(s->ttyfd, 0) < 0)
                return 0;
        return 1;
 }
@@ -1939,8 +2226,8 @@ session_env_req(Session *s)
        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 = xrealloc(s->env, s->num_env + 1,
+                           sizeof(*s->env));
                        s->env[s->num_env].name = name;
                        s->env[s->num_env].val = val;
                        s->num_env++;
@@ -1960,7 +2247,7 @@ session_auth_agent_req(Session *s)
 {
        static int called = 0;
        packet_check_eom();
-       if (no_agent_forwarding_flag) {
+       if (no_agent_forwarding_flag || !options.allow_agent_forwarding) {
                debug("session_auth_agent_req: no_agent_forwarding_flag");
                return 0;
        }
@@ -1995,7 +2282,7 @@ session_input_channel_req(Channel *c, const char *rtype)
                } else if (strcmp(rtype, "exec") == 0) {
                        success = session_exec_req(s);
                } else if (strcmp(rtype, "pty-req") == 0) {
-                       success =  session_pty_req(s);
+                       success = session_pty_req(s);
                } else if (strcmp(rtype, "x11-req") == 0) {
                        success = session_x11_req(s);
                } else if (strcmp(rtype, "auth-agent-req@openssh.com") == 0) {
@@ -2016,7 +2303,7 @@ session_input_channel_req(Channel *c, const char *rtype)
 }
 
 void
-session_set_fds(Session *s, int fdin, int fdout, int fderr)
+session_set_fds(Session *s, int fdin, int fdout, int fderr, int is_tty)
 {
        if (!compat20)
                fatal("session_set_fds: called for proto != 2.0");
@@ -2029,8 +2316,7 @@ session_set_fds(Session *s, int fdin, int fdout, int fderr)
        channel_set_fds(s->chanid,
            fdout, fdin, fderr,
            fderr == -1 ? CHAN_EXTENDED_IGNORE : CHAN_EXTENDED_READ,
-           1,
-           CHAN_SES_WINDOW_DEFAULT);
+           1, is_tty, CHAN_SES_WINDOW_DEFAULT);
 }
 
 /*
@@ -2062,8 +2348,9 @@ session_pty_cleanup2(Session *s)
         * the pty cleanup, so that another process doesn't get this pty
         * while we're still cleaning up.
         */
-       if (close(s->ptymaster) < 0)
-               error("close(s->ptymaster/%d): %s", s->ptymaster, strerror(errno));
+       if (s->ptymaster != -1 && close(s->ptymaster) < 0)
+               error("close(s->ptymaster/%d): %s",
+                   s->ptymaster, strerror(errno));
 
        /* unlink pty from session */
        s->ttyfd = -1;
@@ -2101,7 +2388,7 @@ session_close_x11(int id)
 {
        Channel *c;
 
-       if ((c = channel_lookup(id)) == NULL) {
+       if ((c = channel_by_id(id)) == NULL) {
                debug("session_close_x11: x11 channel %d missing", id);
        } else {
                /* Detach X11 listener */
@@ -2120,7 +2407,7 @@ session_close_single_x11(int id, void *arg)
 
        debug3("session_close_single_x11: channel %d", id);
        channel_cancel_cleanup(id);
-       if ((s  = session_by_x11_channel(id)) == NULL)
+       if ((s = session_by_x11_channel(id)) == NULL)
                fatal("session_close_single_x11: no x11 channel %d", id);
        for (i = 0; s->x11_chanids[i] != -1; i++) {
                debug("session_close_single_x11: session %d: "
@@ -2156,7 +2443,6 @@ static void
 session_exit_message(Session *s, int status)
 {
        Channel *c;
-       u_int i;
 
        if ((c = channel_lookup(s->chanid)) == NULL)
                fatal("session_exit_message: session %d: no channel %d",
@@ -2172,7 +2458,7 @@ session_exit_message(Session *s, int status)
                channel_request_start(s->chanid, "exit-signal", 0);
                packet_put_cstring(sig2name(WTERMSIG(status)));
 #ifdef WCOREDUMP
-               packet_put_char(WCOREDUMP(status));
+               packet_put_char(WCOREDUMP(status)? 1 : 0);
 #else /* WCOREDUMP */
                packet_put_char(0);
 #endif /* WCOREDUMP */
@@ -2186,7 +2472,14 @@ session_exit_message(Session *s, int status)
 
        /* disconnect channel */
        debug("session_exit_message: release channel %d", s->chanid);
-       channel_cancel_cleanup(s->chanid);
+
+       /*
+        * Adjust cleanup callback attachment to send close messages when
+        * the channel gets EOF. The session will be then be closed
+        * by session_close_by_channel when the childs close their fds.
+        */
+       channel_register_cleanup(c->self, session_close_by_channel, 1);
+
        /*
         * emulate a write failure with 'chan_write_failed', nobody will be
         * interested in data we write.
@@ -2195,15 +2488,6 @@ session_exit_message(Session *s, int status)
         */
        if (c->ostate != CHAN_OUTPUT_CLOSED)
                chan_write_failed(c);
-       s->chanid = -1;
-
-       /* Close any X11 listeners associated with this session */
-       if (s->x11_chanids != NULL) {
-               for (i = 0; s->x11_chanids[i] != -1; i++) {
-                       session_close_x11(s->x11_chanids[i]);
-                       s->x11_chanids[i] = -1;
-               }
-       }
 }
 
 void
@@ -2226,14 +2510,15 @@ session_close(Session *s)
                xfree(s->auth_data);
        if (s->auth_proto)
                xfree(s->auth_proto);
-       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)
+       if (s->env != NULL) {
+               for (i = 0; i < s->num_env; i++) {
+                       xfree(s->env[i].name);
+                       xfree(s->env[i].val);
+               }
                xfree(s->env);
+       }
        session_proctitle(s);
+       session_unused(s->self);
 }
 
 void
@@ -2247,7 +2532,9 @@ session_close_by_pid(pid_t pid, int status)
        }
        if (s->chanid != -1)
                session_exit_message(s, status);
-       session_close(s);
+       if (s->ttyfd != -1)
+               session_pty_cleanup(s);
+       s->pid = 0;
 }
 
 /*
@@ -2258,6 +2545,7 @@ void
 session_close_by_channel(int id, void *arg)
 {
        Session *s = session_by_channel(id);
+       u_int i;
 
        if (s == NULL) {
                debug("session_close_by_channel: no session for id %d", id);
@@ -2277,6 +2565,15 @@ session_close_by_channel(int id, void *arg)
        }
        /* detach by removing callback */
        channel_cancel_cleanup(s->chanid);
+
+       /* Close any X11 listeners associated with this session */
+       if (s->x11_chanids != NULL) {
+               for (i = 0; s->x11_chanids[i] != -1; i++) {
+                       session_close_x11(s->x11_chanids[i]);
+                       s->x11_chanids[i] = -1;
+               }
+       }
+
        s->chanid = -1;
        session_close(s);
 }
@@ -2285,7 +2582,7 @@ void
 session_destroy_all(void (*closefunc)(Session *))
 {
        int i;
-       for (i = 0; i < MAX_SESSIONS; i++) {
+       for (i = 0; i < sessions_nalloc; i++) {
                Session *s = &sessions[i];
                if (s->used) {
                        if (closefunc != NULL)
@@ -2304,7 +2601,7 @@ session_tty_list(void)
        char *cp;
 
        buf[0] = '\0';
-       for (i = 0; i < MAX_SESSIONS; i++) {
+       for (i = 0; i < sessions_nalloc; i++) {
                Session *s = &sessions[i];
                if (s->used && s->ttyfd != -1) {
 
@@ -2371,7 +2668,7 @@ session_setup_x11fwd(Session *s)
        }
        for (i = 0; s->x11_chanids[i] != -1; i++) {
                channel_register_cleanup(s->x11_chanids[i],
-                   session_close_single_x11);
+                   session_close_single_x11, 0);
        }
 
        /* Set up a suitable value for the DISPLAY variable. */
@@ -2438,6 +2735,17 @@ do_cleanup(Authctxt *authctxt)
 
        if (authctxt == NULL)
                return;
+
+#ifdef USE_PAM
+       if (options.use_pam) {
+               sshpam_cleanup();
+               sshpam_thread_cleanup();
+       }
+#endif
+
+       if (!authctxt->authenticated)
+               return;
+
 #ifdef KRB5
        if (options.kerberos_ticket_cleanup &&
            authctxt->krb5_ctx)
@@ -2449,13 +2757,6 @@ do_cleanup(Authctxt *authctxt)
                ssh_gssapi_cleanup_creds();
 #endif
 
-#ifdef USE_PAM
-       if (options.use_pam) {
-               sshpam_cleanup();
-               sshpam_thread_cleanup();
-       }
-#endif
-
        /* remove agent socket */
        auth_sock_cleanup_proc(authctxt->pw);
 
This page took 0.12778 seconds and 4 git commands to generate.