]> andersk Git - openssh.git/blobdiff - serverloop.c
- djm@cvs.openbsd.org 2010/01/30 02:54:53
[openssh.git] / serverloop.c
index 21656cf8746d0ac39b43a93ac40d11cd95f47227..8be01c5c37c4250aab5c855f28f3ac7d51c00b51 100644 (file)
@@ -1,3 +1,4 @@
+/* $OpenBSD: serverloop.c,v 1.159 2009/05/28 16:50:16 andreas Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: serverloop.c,v 1.111 2003/09/23 20:17:11 markus Exp $");
 
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include "openbsd-compat/sys-queue.h"
 #include "xmalloc.h"
 #include "packet.h"
 #include "buffer.h"
@@ -48,19 +68,24 @@ RCSID("$OpenBSD: serverloop.c,v 1.111 2003/09/23 20:17:11 markus Exp $");
 #include "compat.h"
 #include "ssh1.h"
 #include "ssh2.h"
+#include "key.h"
+#include "cipher.h"
+#include "kex.h"
+#include "hostfile.h"
 #include "auth.h"
 #include "session.h"
 #include "dispatch.h"
 #include "auth-options.h"
 #include "serverloop.h"
 #include "misc.h"
-#include "kex.h"
+#include "roaming.h"
 
 extern ServerOptions options;
 
 /* XXX */
 extern Kex *xxx_kex;
 extern Authctxt *the_authctxt;
+extern int use_privsep;
 
 static Buffer stdin_buffer;    /* Buffer for stdin data. */
 static Buffer stdout_buffer;   /* Buffer for stdout data. */
@@ -81,7 +106,7 @@ static int connection_in;    /* Connection to client (input). */
 static int connection_out;     /* Connection to client (output). */
 static int connection_closed = 0;      /* Connection to client closed. */
 static u_int buffer_high;      /* "Soft" max buffer size. */
-static int client_alive_timeouts = 0;
+static int no_more_sessions = 0; /* Disallow further sessions. */
 
 /*
  * This SIGCHLD kludge is used to detect when the child exits.  The server
@@ -90,6 +115,9 @@ static int client_alive_timeouts = 0;
 
 static volatile sig_atomic_t child_terminated = 0;     /* The child has terminated. */
 
+/* Cleanup on signals (!use_privsep case only) */
+static volatile sig_atomic_t received_sigterm = 0;
+
 /* prototypes */
 static void server_init_dispatch(void);
 
@@ -138,11 +166,11 @@ notify_done(fd_set *readset)
                        debug2("notify_done: reading");
 }
 
+/*ARGSUSED*/
 static void
 sigchld_handler(int sig)
 {
        int save_errno = errno;
-       debug("Received SIGCHLD.");
        child_terminated = 1;
 #ifndef _UNICOS
        mysignal(SIGCHLD, sigchld_handler);
@@ -151,6 +179,13 @@ sigchld_handler(int sig)
        errno = save_errno;
 }
 
+/*ARGSUSED*/
+static void
+sigterm_handler(int sig)
+{
+       received_sigterm = sig;
+}
+
 /*
  * Make packets from buffered stderr data, and buffer it for sending
  * to the client.
@@ -212,26 +247,25 @@ make_packets_from_stdout_data(void)
 static void
 client_alive_check(void)
 {
-       static int had_channel = 0;
-       int id;
-
-       id = channel_find_open();
-       if (id == -1) {
-               if (!had_channel)
-                       return;
-               packet_disconnect("No open channels after timeout!");
-       }
-       had_channel = 1;
+       int channel_id;
 
        /* timeout, check to see how many we have had */
-       if (++client_alive_timeouts > options.client_alive_count_max)
-               packet_disconnect("Timeout, your session not responding.");
+       if (packet_inc_alive_timeouts() > options.client_alive_count_max) {
+               logit("Timeout, client not responding.");
+               cleanup_exit(255);
+       }
 
        /*
-        * send a bogus channel request with "wantreply",
+        * send a bogus global/channel request with "wantreply",
         * we should get back a failure
         */
-       channel_request_start(id, "keepalive@openssh.com", 1);
+       if ((channel_id = channel_find_open()) == -1) {
+               packet_start(SSH2_MSG_GLOBAL_REQUEST);
+               packet_put_cstring("keepalive@openssh.com");
+               packet_put_char(1);     /* boolean: want reply */
+       } else {
+               channel_request_start(channel_id, "keepalive@openssh.com", 1);
+       }
        packet_send();
 }
 
@@ -243,11 +277,12 @@ client_alive_check(void)
  */
 static void
 wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, int *maxfdp,
-    int *nallocp, u_int max_time_milliseconds)
+    u_int *nallocp, u_int max_time_milliseconds)
 {
        struct timeval tv, *tvp;
        int ret;
        int client_alive_scheduled = 0;
+       int program_alive_scheduled = 0;
 
        /*
         * if using client_alive, set the max timeout accordingly,
@@ -285,6 +320,7 @@ wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, int *maxfdp,
                 * the client, try to get some more data from the program.
                 */
                if (packet_not_very_much_data_to_write()) {
+                       program_alive_scheduled = child_terminated;
                        if (!fdout_eof)
                                FD_SET(fdout, *readsetp);
                        if (!fderr_eof)
@@ -330,8 +366,16 @@ wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, int *maxfdp,
                memset(*writesetp, 0, *nallocp);
                if (errno != EINTR)
                        error("select: %.100s", strerror(errno));
-       } else if (ret == 0 && client_alive_scheduled)
-               client_alive_check();
+       } else {
+               if (ret == 0 && client_alive_scheduled)
+                       client_alive_check();
+               if (!compat20 && program_alive_scheduled && fdin_is_tty) {
+                       if (!fdout_eof)
+                               FD_SET(fdout, *readsetp);
+                       if (!fderr_eof)
+                               FD_SET(fderr, *readsetp);
+               }
+       }
 
        notify_done(*readsetp);
 }
@@ -341,15 +385,18 @@ wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, int *maxfdp,
  * in buffers and processed later.
  */
 static void
-process_input(fd_set * readset)
+process_input(fd_set *readset)
 {
        int len;
        char buf[16384];
 
        /* Read and buffer any input data from the client. */
        if (FD_ISSET(connection_in, readset)) {
-               len = read(connection_in, buf, sizeof(buf));
+               int cont = 0;
+               len = roaming_read(connection_in, buf, sizeof(buf), &cont);
                if (len == 0) {
+                       if (cont)
+                               return;
                        verbose("Connection closed by %.100s",
                            get_remote_ipaddr());
                        connection_closed = 1;
@@ -357,7 +404,8 @@ process_input(fd_set * readset)
                                return;
                        cleanup_exit(255);
                } else if (len < 0) {
-                       if (errno != EINTR && errno != EAGAIN) {
+                       if (errno != EINTR && errno != EAGAIN &&
+                           errno != EWOULDBLOCK) {
                                verbose("Read error from remote host "
                                    "%.100s: %.100s",
                                    get_remote_ipaddr(), strerror(errno));
@@ -373,10 +421,17 @@ process_input(fd_set * readset)
 
        /* Read and buffer any available stdout data from the program. */
        if (!fdout_eof && FD_ISSET(fdout, readset)) {
+               errno = 0;
                len = read(fdout, buf, sizeof(buf));
-               if (len < 0 && (errno == EINTR || errno == EAGAIN)) {
+               if (len < 0 && (errno == EINTR || ((errno == EAGAIN ||
+                   errno == EWOULDBLOCK) && !child_terminated))) {
                        /* do nothing */
+#ifndef PTY_ZEROREAD
                } else if (len <= 0) {
+#else
+               } else if ((!isatty(fdout) && len <= 0) ||
+                   (isatty(fdout) && (len < 0 || (len == 0 && errno != 0)))) {
+#endif
                        fdout_eof = 1;
                } else {
                        buffer_append(&stdout_buffer, buf, len);
@@ -385,10 +440,17 @@ process_input(fd_set * readset)
        }
        /* Read and buffer any available stderr data from the program. */
        if (!fderr_eof && FD_ISSET(fderr, readset)) {
+               errno = 0;
                len = read(fderr, buf, sizeof(buf));
-               if (len < 0 && (errno == EINTR || errno == EAGAIN)) {
+               if (len < 0 && (errno == EINTR || ((errno == EAGAIN ||
+                   errno == EWOULDBLOCK) && !child_terminated))) {
                        /* do nothing */
+#ifndef PTY_ZEROREAD
                } else if (len <= 0) {
+#else
+               } else if ((!isatty(fderr) && len <= 0) ||
+                   (isatty(fderr) && (len < 0 || (len == 0 && errno != 0)))) {
+#endif
                        fderr_eof = 1;
                } else {
                        buffer_append(&stderr_buffer, buf, len);
@@ -400,7 +462,7 @@ process_input(fd_set * readset)
  * Sends data from internal buffers to client program stdin.
  */
 static void
-process_output(fd_set * writeset)
+process_output(fd_set *writeset)
 {
        struct termios tio;
        u_char *data;
@@ -412,7 +474,8 @@ process_output(fd_set * writeset)
                data = buffer_ptr(&stdin_buffer);
                dlen = buffer_len(&stdin_buffer);
                len = write(fdin, data, dlen);
-               if (len < 0 && (errno == EINTR || errno == EAGAIN)) {
+               if (len < 0 &&
+                   (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) {
                        /* do nothing */
                } else if (len <= 0) {
                        if (fdin != fdout)
@@ -489,7 +552,8 @@ void
 server_loop(pid_t pid, int fdin_arg, int fdout_arg, int fderr_arg)
 {
        fd_set *readset = NULL, *writeset = NULL;
-       int max_fd = 0, nalloc = 0;
+       int max_fd = 0;
+       u_int nalloc = 0;
        int wait_status;        /* Status returned by wait(). */
        pid_t wait_pid;         /* pid returned by wait(). */
        int waiting_termination = 0;    /* Have displayed waiting close message. */
@@ -504,6 +568,12 @@ server_loop(pid_t pid, int fdin_arg, int fdout_arg, int fderr_arg)
        child_terminated = 0;
        mysignal(SIGCHLD, sigchld_handler);
 
+       if (!use_privsep) {
+               signal(SIGTERM, sigterm_handler);
+               signal(SIGINT, sigterm_handler);
+               signal(SIGQUIT, sigterm_handler);
+       }
+
        /* Initialize our global variables. */
        fdin = fdin_arg;
        fdout = fdout_arg;
@@ -550,7 +620,7 @@ server_loop(pid_t pid, int fdin_arg, int fdout_arg, int fderr_arg)
         * If we have no separate fderr (which is the case when we have a pty
         * - there we cannot make difference between data sent to stdout and
         * stderr), indicate that we have seen an EOF from stderr.  This way
-        * we don\'t need to check the descriptor everywhere.
+        * we don't need to check the descriptor everywhere.
         */
        if (fderr == -1)
                fderr_eof = 1;
@@ -631,6 +701,12 @@ server_loop(pid_t pid, int fdin_arg, int fdout_arg, int fderr_arg)
                wait_until_can_do_something(&readset, &writeset, &max_fd,
                    &nalloc, max_time_milliseconds);
 
+               if (received_sigterm) {
+                       logit("Exiting on signal %d", received_sigterm);
+                       /* Clean up sessions, utmp, etc. */
+                       cleanup_exit(255);
+               }
+
                /* Process any channel events. */
                channel_after_select(readset, writeset);
 
@@ -729,6 +805,7 @@ collect_children(void)
        sigaddset(&nset, SIGCHLD);
        sigprocmask(SIG_BLOCK, &nset, &oset);
        if (child_terminated) {
+               debug("Received SIGCHLD.");
                while ((pid = waitpid(-1, &status, WNOHANG)) > 0 ||
                    (pid < 0 && errno == EINTR))
                        if (pid > 0)
@@ -751,6 +828,12 @@ server_loop2(Authctxt *authctxt)
        connection_in = packet_get_connection_in();
        connection_out = packet_get_connection_out();
 
+       if (!use_privsep) {
+               signal(SIGTERM, sigterm_handler);
+               signal(SIGINT, sigterm_handler);
+               signal(SIGQUIT, sigterm_handler);
+       }
+
        notify_setup();
 
        max_fd = MAX(connection_in, connection_out);
@@ -768,6 +851,12 @@ server_loop2(Authctxt *authctxt)
                wait_until_can_do_something(&readset, &writeset, &max_fd,
                    &nalloc, 0);
 
+               if (received_sigterm) {
+                       logit("Exiting on signal %d", received_sigterm);
+                       /* Clean up sessions, utmp, etc. */
+                       cleanup_exit(255);
+               }
+
                collect_children();
                if (!rekeying) {
                        channel_after_select(readset, writeset);
@@ -797,18 +886,17 @@ server_loop2(Authctxt *authctxt)
 }
 
 static void
-server_input_channel_failure(int type, u_int32_t seq, void *ctxt)
+server_input_keep_alive(int type, u_int32_t seq, void *ctxt)
 {
-       debug("Got CHANNEL_FAILURE for keepalive");
+       debug("Got %d/%u for keepalive", type, seq);
        /*
         * reset timeout, since we got a sane answer from the client.
         * even if this was generated by something other than
         * the bogus CHANNEL_REQUEST we send for keepalives.
         */
-       client_alive_timeouts = 0;
+       packet_set_alive_timeouts(0);
 }
 
-
 static void
 server_input_stdin_data(int type, u_int32_t seq, void *ctxt)
 {
@@ -842,10 +930,10 @@ server_input_eof(int type, u_int32_t seq, void *ctxt)
 static void
 server_input_window_size(int type, u_int32_t seq, void *ctxt)
 {
-       int row = packet_get_int();
-       int col = packet_get_int();
-       int xpixel = packet_get_int();
-       int ypixel = packet_get_int();
+       u_int row = packet_get_int();
+       u_int col = packet_get_int();
+       u_int xpixel = packet_get_int();
+       u_int ypixel = packet_get_int();
 
        debug("Window change received.");
        packet_check_eom();
@@ -854,12 +942,11 @@ server_input_window_size(int type, u_int32_t seq, void *ctxt)
 }
 
 static Channel *
-server_request_direct_tcpip(char *ctype)
+server_request_direct_tcpip(void)
 {
        Channel *c;
-       int sock;
        char *target, *originator;
-       int target_port, originator_port;
+       u_short target_port, originator_port;
 
        target = packet_get_string(NULL);
        target_port = packet_get_int();
@@ -867,35 +954,85 @@ server_request_direct_tcpip(char *ctype)
        originator_port = packet_get_int();
        packet_check_eom();
 
-       debug("server_request_direct_tcpip: originator %s port %d, target %s port %d",
-          originator, originator_port, target, target_port);
+       debug("server_request_direct_tcpip: originator %s port %d, target %s "
+           "port %d", originator, originator_port, target, target_port);
 
        /* XXX check permission */
-       sock = channel_connect_to(target, target_port);
-       xfree(target);
+       c = channel_connect_to(target, target_port,
+           "direct-tcpip", "direct-tcpip");
+
        xfree(originator);
-       if (sock < 0)
+       xfree(target);
+
+       return c;
+}
+
+static Channel *
+server_request_tun(void)
+{
+       Channel *c = NULL;
+       int mode, tun;
+       int sock;
+
+       mode = packet_get_int();
+       switch (mode) {
+       case SSH_TUNMODE_POINTOPOINT:
+       case SSH_TUNMODE_ETHERNET:
+               break;
+       default:
+               packet_send_debug("Unsupported tunnel device mode.");
+               return NULL;
+       }
+       if ((options.permit_tun & mode) == 0) {
+               packet_send_debug("Server has rejected tunnel device "
+                   "forwarding");
                return NULL;
-       c = channel_new(ctype, SSH_CHANNEL_CONNECTING,
-           sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT,
-           CHAN_TCP_PACKET_DEFAULT, 0, "direct-tcpip", 1);
+       }
+
+       tun = packet_get_int();
+       if (forced_tun_device != -1) {
+               if (tun != SSH_TUNID_ANY && forced_tun_device != tun)
+                       goto done;
+               tun = forced_tun_device;
+       }
+       sock = tun_open(tun, mode);
+       if (sock < 0)
+               goto done;
+       c = channel_new("tun", SSH_CHANNEL_OPEN, sock, sock, -1,
+           CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1);
+       c->datagram = 1;
+#if defined(SSH_TUN_FILTER)
+       if (mode == SSH_TUNMODE_POINTOPOINT)
+               channel_register_filter(c->self, sys_tun_infilter,
+                   sys_tun_outfilter, NULL, NULL);
+#endif
+
+ done:
+       if (c == NULL)
+               packet_send_debug("Failed to open the tunnel device.");
        return c;
 }
 
 static Channel *
-server_request_session(char *ctype)
+server_request_session(void)
 {
        Channel *c;
 
        debug("input_session_request");
        packet_check_eom();
+
+       if (no_more_sessions) {
+               packet_disconnect("Possible attack: attempt to open a session "
+                   "after additional sessions disabled");
+       }
+
        /*
         * A server session has no fd to read or write until a
         * CHANNEL_REQUEST for a shell is made, so we set the type to
         * SSH_CHANNEL_LARVAL.  Additionally, a callback for handling all
         * CHANNEL_REQUEST messages is registered.
         */
-       c = channel_new(ctype, SSH_CHANNEL_LARVAL,
+       c = channel_new("session", SSH_CHANNEL_LARVAL,
            -1, -1, -1, /*window size*/0, CHAN_SES_PACKET_DEFAULT,
            0, "server-session", 1);
        if (session_open(the_authctxt, c->self) != 1) {
@@ -903,7 +1040,7 @@ server_request_session(char *ctype)
                channel_free(c);
                return NULL;
        }
-       channel_register_cleanup(c->self, session_close_by_channel);
+       channel_register_cleanup(c->self, session_close_by_channel, 0);
        return c;
 }
 
@@ -924,9 +1061,11 @@ server_input_channel_open(int type, u_int32_t seq, void *ctxt)
            ctype, rchan, rwindow, rmaxpack);
 
        if (strcmp(ctype, "session") == 0) {
-               c = server_request_session(ctype);
+               c = server_request_session();
        } else if (strcmp(ctype, "direct-tcpip") == 0) {
-               c = server_request_direct_tcpip(ctype);
+               c = server_request_direct_tcpip();
+       } else if (strcmp(ctype, "tun@openssh.com") == 0) {
+               c = server_request_tun();
        }
        if (c != NULL) {
                debug("server_input_channel_open: confirm %s", ctype);
@@ -960,7 +1099,7 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt)
 {
        char *rtype;
        int want_reply;
-       int success = 0;
+       int success = 0, allocated_listen_port = 0;
 
        rtype = packet_get_string(NULL);
        want_reply = packet_get_char();
@@ -973,8 +1112,8 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt)
                u_short listen_port;
 
                pw = the_authctxt->pw;
-               if (pw == NULL)
-                       fatal("server_input_global_request: no user");
+               if (pw == NULL || !the_authctxt->valid)
+                       fatal("server_input_global_request: no/invalid user");
                listen_address = packet_get_string(NULL);
                listen_port = (u_short)packet_get_int();
                debug("server_input_global_request: tcpip-forward listen %s port %d",
@@ -982,28 +1121,49 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt)
 
                /* check permissions */
                if (!options.allow_tcp_forwarding ||
-                   no_port_forwarding_flag
+                   no_port_forwarding_flag ||
+                   (!want_reply && listen_port == 0)
 #ifndef NO_IPPORT_RESERVED_CONCEPT
-                   || (listen_port < IPPORT_RESERVED && pw->pw_uid != 0)
+                   || (listen_port != 0 && listen_port < IPPORT_RESERVED &&
+                    pw->pw_uid != 0)
 #endif
-                  ) {
+                   ) {
                        success = 0;
                        packet_send_debug("Server has disabled port forwarding.");
                } else {
                        /* Start listening on the port */
                        success = channel_setup_remote_fwd_listener(
-                           listen_address, listen_port, options.gateway_ports);
+                           listen_address, listen_port,
+                           &allocated_listen_port, options.gateway_ports);
                }
                xfree(listen_address);
+       } else if (strcmp(rtype, "cancel-tcpip-forward") == 0) {
+               char *cancel_address;
+               u_short cancel_port;
+
+               cancel_address = packet_get_string(NULL);
+               cancel_port = (u_short)packet_get_int();
+               debug("%s: cancel-tcpip-forward addr %s port %d", __func__,
+                   cancel_address, cancel_port);
+
+               success = channel_cancel_rport_listener(cancel_address,
+                   cancel_port);
+               xfree(cancel_address);
+       } else if (strcmp(rtype, "no-more-sessions@openssh.com") == 0) {
+               no_more_sessions = 1;
+               success = 1;
        }
        if (want_reply) {
                packet_start(success ?
                    SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE);
+               if (success && allocated_listen_port > 0)
+                       packet_put_int(allocated_listen_port);
                packet_send();
                packet_write_wait();
        }
        xfree(rtype);
 }
+
 static void
 server_input_channel_req(int type, u_int32_t seq, void *ctxt)
 {
@@ -1021,7 +1181,11 @@ server_input_channel_req(int type, u_int32_t seq, void *ctxt)
        if ((c = channel_lookup(id)) == NULL)
                packet_disconnect("server_input_channel_req: "
                    "unknown channel %d", id);
-       if (c->type == SSH_CHANNEL_LARVAL || c->type == SSH_CHANNEL_OPEN)
+       if (!strcmp(rtype, "eow@openssh.com")) {
+               packet_check_eom();
+               chan_rcvd_eow(c);
+       } else if ((c->type == SSH_CHANNEL_LARVAL ||
+           c->type == SSH_CHANNEL_OPEN) && strcmp(c->ctype, "session") == 0)
                success = session_input_channel_req(c, rtype);
        if (reply) {
                packet_start(success ?
@@ -1048,7 +1212,10 @@ server_init_dispatch_20(void)
        dispatch_set(SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust);
        dispatch_set(SSH2_MSG_GLOBAL_REQUEST, &server_input_global_request);
        /* client_alive */
-       dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &server_input_channel_failure);
+       dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, &server_input_keep_alive);
+       dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &server_input_keep_alive);
+       dispatch_set(SSH2_MSG_REQUEST_SUCCESS, &server_input_keep_alive);
+       dispatch_set(SSH2_MSG_REQUEST_FAILURE, &server_input_keep_alive);
        /* rekeying */
        dispatch_set(SSH2_MSG_KEXINIT, &kex_input_kexinit);
 }
This page took 0.172903 seconds and 4 git commands to generate.