diff options
author | Damien Miller <djm@mindrot.org> | 2010-01-26 13:26:22 +1100 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2010-01-26 13:26:22 +1100 |
commit | e1537f951fa87e4d070adda82b474b25cf4902ec (patch) | |
tree | 3c9d794dcf7fca1d880ffd9db24b20038d3f800b /channels.c | |
parent | f589fd1ea8c352e6bf819733ecd505119a694c51 (diff) |
- djm@cvs.openbsd.org 2010/01/26 01:28:35
[channels.c channels.h clientloop.c clientloop.h mux.c nchan.c ssh.c]
rewrite ssh(1) multiplexing code to a more sensible protocol.
The new multiplexing code uses channels for the listener and
accepted control sockets to make the mux master non-blocking, so
no stalls when processing messages from a slave.
avoid use of fatal() in mux master protocol parsing so an errant slave
process cannot take down a running master.
implement requesting of port-forwards over multiplexed sessions. Any
port forwards requested by the slave are added to those the master has
established.
add support for stdio forwarding ("ssh -W host:port ...") in mux slaves.
document master/slave mux protocol so that other tools can use it to
control a running ssh(1). Note: there are no guarantees that this
protocol won't be incompatibly changed (though it is versioned).
feedback Salvador Fandino, dtucker@
channel changes ok markus@
Diffstat (limited to 'channels.c')
-rw-r--r-- | channels.c | 214 |
1 files changed, 164 insertions, 50 deletions
diff --git a/channels.c b/channels.c index e8589d8c4..81261679a 100644 --- a/channels.c +++ b/channels.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: channels.c,v 1.301 2010/01/11 01:39:46 dtucker Exp $ */ | 1 | /* $OpenBSD: channels.c,v 1.302 2010/01/26 01:28:35 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -239,7 +239,6 @@ channel_register_fds(Channel *c, int rfd, int wfd, int efd, | |||
239 | c->rfd = rfd; | 239 | c->rfd = rfd; |
240 | c->wfd = wfd; | 240 | c->wfd = wfd; |
241 | c->sock = (rfd == wfd) ? rfd : -1; | 241 | c->sock = (rfd == wfd) ? rfd : -1; |
242 | c->ctl_fd = -1; /* XXX: set elsewhere */ | ||
243 | c->efd = efd; | 242 | c->efd = efd; |
244 | c->extended_usage = extusage; | 243 | c->extended_usage = extusage; |
245 | 244 | ||
@@ -328,6 +327,9 @@ channel_new(char *ctype, int type, int rfd, int wfd, int efd, | |||
328 | c->output_filter = NULL; | 327 | c->output_filter = NULL; |
329 | c->filter_ctx = NULL; | 328 | c->filter_ctx = NULL; |
330 | c->filter_cleanup = NULL; | 329 | c->filter_cleanup = NULL; |
330 | c->ctl_chan = -1; | ||
331 | c->mux_rcb = NULL; | ||
332 | c->mux_ctx = NULL; | ||
331 | c->delayed = 1; /* prevent call to channel_post handler */ | 333 | c->delayed = 1; /* prevent call to channel_post handler */ |
332 | TAILQ_INIT(&c->status_confirms); | 334 | TAILQ_INIT(&c->status_confirms); |
333 | debug("channel %d: new [%s]", found, remote_name); | 335 | debug("channel %d: new [%s]", found, remote_name); |
@@ -370,11 +372,10 @@ channel_close_fd(int *fdp) | |||
370 | static void | 372 | static void |
371 | channel_close_fds(Channel *c) | 373 | channel_close_fds(Channel *c) |
372 | { | 374 | { |
373 | debug3("channel %d: close_fds r %d w %d e %d c %d", | 375 | debug3("channel %d: close_fds r %d w %d e %d", |
374 | c->self, c->rfd, c->wfd, c->efd, c->ctl_fd); | 376 | c->self, c->rfd, c->wfd, c->efd); |
375 | 377 | ||
376 | channel_close_fd(&c->sock); | 378 | channel_close_fd(&c->sock); |
377 | channel_close_fd(&c->ctl_fd); | ||
378 | channel_close_fd(&c->rfd); | 379 | channel_close_fd(&c->rfd); |
379 | channel_close_fd(&c->wfd); | 380 | channel_close_fd(&c->wfd); |
380 | channel_close_fd(&c->efd); | 381 | channel_close_fd(&c->efd); |
@@ -400,8 +401,6 @@ channel_free(Channel *c) | |||
400 | 401 | ||
401 | if (c->sock != -1) | 402 | if (c->sock != -1) |
402 | shutdown(c->sock, SHUT_RDWR); | 403 | shutdown(c->sock, SHUT_RDWR); |
403 | if (c->ctl_fd != -1) | ||
404 | shutdown(c->ctl_fd, SHUT_RDWR); | ||
405 | channel_close_fds(c); | 404 | channel_close_fds(c); |
406 | buffer_free(&c->input); | 405 | buffer_free(&c->input); |
407 | buffer_free(&c->output); | 406 | buffer_free(&c->output); |
@@ -523,6 +522,7 @@ channel_still_open(void) | |||
523 | case SSH_CHANNEL_X11_LISTENER: | 522 | case SSH_CHANNEL_X11_LISTENER: |
524 | case SSH_CHANNEL_PORT_LISTENER: | 523 | case SSH_CHANNEL_PORT_LISTENER: |
525 | case SSH_CHANNEL_RPORT_LISTENER: | 524 | case SSH_CHANNEL_RPORT_LISTENER: |
525 | case SSH_CHANNEL_MUX_LISTENER: | ||
526 | case SSH_CHANNEL_CLOSED: | 526 | case SSH_CHANNEL_CLOSED: |
527 | case SSH_CHANNEL_AUTH_SOCKET: | 527 | case SSH_CHANNEL_AUTH_SOCKET: |
528 | case SSH_CHANNEL_DYNAMIC: | 528 | case SSH_CHANNEL_DYNAMIC: |
@@ -536,6 +536,7 @@ channel_still_open(void) | |||
536 | case SSH_CHANNEL_OPENING: | 536 | case SSH_CHANNEL_OPENING: |
537 | case SSH_CHANNEL_OPEN: | 537 | case SSH_CHANNEL_OPEN: |
538 | case SSH_CHANNEL_X11_OPEN: | 538 | case SSH_CHANNEL_X11_OPEN: |
539 | case SSH_CHANNEL_MUX_CLIENT: | ||
539 | return 1; | 540 | return 1; |
540 | case SSH_CHANNEL_INPUT_DRAINING: | 541 | case SSH_CHANNEL_INPUT_DRAINING: |
541 | case SSH_CHANNEL_OUTPUT_DRAINING: | 542 | case SSH_CHANNEL_OUTPUT_DRAINING: |
@@ -567,6 +568,8 @@ channel_find_open(void) | |||
567 | case SSH_CHANNEL_X11_LISTENER: | 568 | case SSH_CHANNEL_X11_LISTENER: |
568 | case SSH_CHANNEL_PORT_LISTENER: | 569 | case SSH_CHANNEL_PORT_LISTENER: |
569 | case SSH_CHANNEL_RPORT_LISTENER: | 570 | case SSH_CHANNEL_RPORT_LISTENER: |
571 | case SSH_CHANNEL_MUX_LISTENER: | ||
572 | case SSH_CHANNEL_MUX_CLIENT: | ||
570 | case SSH_CHANNEL_OPENING: | 573 | case SSH_CHANNEL_OPENING: |
571 | case SSH_CHANNEL_CONNECTING: | 574 | case SSH_CHANNEL_CONNECTING: |
572 | case SSH_CHANNEL_ZOMBIE: | 575 | case SSH_CHANNEL_ZOMBIE: |
@@ -617,6 +620,8 @@ channel_open_message(void) | |||
617 | case SSH_CHANNEL_CLOSED: | 620 | case SSH_CHANNEL_CLOSED: |
618 | case SSH_CHANNEL_AUTH_SOCKET: | 621 | case SSH_CHANNEL_AUTH_SOCKET: |
619 | case SSH_CHANNEL_ZOMBIE: | 622 | case SSH_CHANNEL_ZOMBIE: |
623 | case SSH_CHANNEL_MUX_CLIENT: | ||
624 | case SSH_CHANNEL_MUX_LISTENER: | ||
620 | continue; | 625 | continue; |
621 | case SSH_CHANNEL_LARVAL: | 626 | case SSH_CHANNEL_LARVAL: |
622 | case SSH_CHANNEL_OPENING: | 627 | case SSH_CHANNEL_OPENING: |
@@ -627,12 +632,12 @@ channel_open_message(void) | |||
627 | case SSH_CHANNEL_INPUT_DRAINING: | 632 | case SSH_CHANNEL_INPUT_DRAINING: |
628 | case SSH_CHANNEL_OUTPUT_DRAINING: | 633 | case SSH_CHANNEL_OUTPUT_DRAINING: |
629 | snprintf(buf, sizeof buf, | 634 | snprintf(buf, sizeof buf, |
630 | " #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d cfd %d)\r\n", | 635 | " #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d cc %d)\r\n", |
631 | c->self, c->remote_name, | 636 | c->self, c->remote_name, |
632 | c->type, c->remote_id, | 637 | c->type, c->remote_id, |
633 | c->istate, buffer_len(&c->input), | 638 | c->istate, buffer_len(&c->input), |
634 | c->ostate, buffer_len(&c->output), | 639 | c->ostate, buffer_len(&c->output), |
635 | c->rfd, c->wfd, c->ctl_fd); | 640 | c->rfd, c->wfd, c->ctl_chan); |
636 | buffer_append(&buffer, buf, strlen(buf)); | 641 | buffer_append(&buffer, buf, strlen(buf)); |
637 | continue; | 642 | continue; |
638 | default: | 643 | default: |
@@ -839,9 +844,6 @@ channel_pre_open(Channel *c, fd_set *readset, fd_set *writeset) | |||
839 | FD_SET(c->efd, readset); | 844 | FD_SET(c->efd, readset); |
840 | } | 845 | } |
841 | /* XXX: What about efd? races? */ | 846 | /* XXX: What about efd? races? */ |
842 | if (compat20 && c->ctl_fd != -1 && | ||
843 | c->istate == CHAN_INPUT_OPEN && c->ostate == CHAN_OUTPUT_OPEN) | ||
844 | FD_SET(c->ctl_fd, readset); | ||
845 | } | 847 | } |
846 | 848 | ||
847 | /* ARGSUSED */ | 849 | /* ARGSUSED */ |
@@ -986,6 +988,28 @@ channel_pre_x11_open(Channel *c, fd_set *readset, fd_set *writeset) | |||
986 | } | 988 | } |
987 | } | 989 | } |
988 | 990 | ||
991 | static void | ||
992 | channel_pre_mux_client(Channel *c, fd_set *readset, fd_set *writeset) | ||
993 | { | ||
994 | if (c->istate == CHAN_INPUT_OPEN && | ||
995 | buffer_check_alloc(&c->input, CHAN_RBUF)) | ||
996 | FD_SET(c->rfd, readset); | ||
997 | if (c->istate == CHAN_INPUT_WAIT_DRAIN) { | ||
998 | /* clear buffer immediately (discard any partial packet) */ | ||
999 | buffer_clear(&c->input); | ||
1000 | chan_ibuf_empty(c); | ||
1001 | /* Start output drain. XXX just kill chan? */ | ||
1002 | chan_rcvd_oclose(c); | ||
1003 | } | ||
1004 | if (c->ostate == CHAN_OUTPUT_OPEN || | ||
1005 | c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { | ||
1006 | if (buffer_len(&c->output) > 0) | ||
1007 | FD_SET(c->wfd, writeset); | ||
1008 | else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) | ||
1009 | chan_obuf_empty(c); | ||
1010 | } | ||
1011 | } | ||
1012 | |||
989 | /* try to decode a socks4 header */ | 1013 | /* try to decode a socks4 header */ |
990 | /* ARGSUSED */ | 1014 | /* ARGSUSED */ |
991 | static int | 1015 | static int |
@@ -1218,19 +1242,14 @@ channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset) | |||
1218 | } | 1242 | } |
1219 | 1243 | ||
1220 | Channel * | 1244 | Channel * |
1221 | channel_connect_stdio_fwd(const char *host_to_connect, u_short port_to_connect) | 1245 | channel_connect_stdio_fwd(const char *host_to_connect, u_short port_to_connect, |
1246 | int in, int out) | ||
1222 | { | 1247 | { |
1223 | Channel *c; | 1248 | Channel *c; |
1224 | int in, out; | ||
1225 | 1249 | ||
1226 | debug("channel_connect_stdio_fwd %s:%d", host_to_connect, | 1250 | debug("channel_connect_stdio_fwd %s:%d", host_to_connect, |
1227 | port_to_connect); | 1251 | port_to_connect); |
1228 | 1252 | ||
1229 | in = dup(STDIN_FILENO); | ||
1230 | out = dup(STDOUT_FILENO); | ||
1231 | if (in < 0 || out < 0) | ||
1232 | fatal("channel_connect_stdio_fwd: dup() in/out failed"); | ||
1233 | |||
1234 | c = channel_new("stdio-forward", SSH_CHANNEL_OPENING, in, out, | 1253 | c = channel_new("stdio-forward", SSH_CHANNEL_OPENING, in, out, |
1235 | -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, | 1254 | -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, |
1236 | 0, "stdio-forward", /*nonblock*/0); | 1255 | 0, "stdio-forward", /*nonblock*/0); |
@@ -1749,36 +1768,6 @@ channel_handle_efd(Channel *c, fd_set *readset, fd_set *writeset) | |||
1749 | return 1; | 1768 | return 1; |
1750 | } | 1769 | } |
1751 | 1770 | ||
1752 | /* ARGSUSED */ | ||
1753 | static int | ||
1754 | channel_handle_ctl(Channel *c, fd_set *readset, fd_set *writeset) | ||
1755 | { | ||
1756 | char buf[16]; | ||
1757 | int len; | ||
1758 | |||
1759 | /* Monitor control fd to detect if the slave client exits */ | ||
1760 | if (c->ctl_fd != -1 && FD_ISSET(c->ctl_fd, readset)) { | ||
1761 | len = read(c->ctl_fd, buf, sizeof(buf)); | ||
1762 | if (len < 0 && | ||
1763 | (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) | ||
1764 | return 1; | ||
1765 | if (len <= 0) { | ||
1766 | debug2("channel %d: ctl read<=0", c->self); | ||
1767 | if (c->type != SSH_CHANNEL_OPEN) { | ||
1768 | debug2("channel %d: not open", c->self); | ||
1769 | chan_mark_dead(c); | ||
1770 | return -1; | ||
1771 | } else { | ||
1772 | chan_read_failed(c); | ||
1773 | chan_write_failed(c); | ||
1774 | } | ||
1775 | return -1; | ||
1776 | } else | ||
1777 | fatal("%s: unexpected data on ctl fd", __func__); | ||
1778 | } | ||
1779 | return 1; | ||
1780 | } | ||
1781 | |||
1782 | static int | 1771 | static int |
1783 | channel_check_window(Channel *c) | 1772 | channel_check_window(Channel *c) |
1784 | { | 1773 | { |
@@ -1809,10 +1798,131 @@ channel_post_open(Channel *c, fd_set *readset, fd_set *writeset) | |||
1809 | if (!compat20) | 1798 | if (!compat20) |
1810 | return; | 1799 | return; |
1811 | channel_handle_efd(c, readset, writeset); | 1800 | channel_handle_efd(c, readset, writeset); |
1812 | channel_handle_ctl(c, readset, writeset); | ||
1813 | channel_check_window(c); | 1801 | channel_check_window(c); |
1814 | } | 1802 | } |
1815 | 1803 | ||
1804 | static u_int | ||
1805 | read_mux(Channel *c, u_int need) | ||
1806 | { | ||
1807 | char buf[CHAN_RBUF]; | ||
1808 | int len; | ||
1809 | u_int rlen; | ||
1810 | |||
1811 | if (buffer_len(&c->input) < need) { | ||
1812 | rlen = need - buffer_len(&c->input); | ||
1813 | len = read(c->rfd, buf, MIN(rlen, CHAN_RBUF)); | ||
1814 | if (len <= 0) { | ||
1815 | if (errno != EINTR && errno != EAGAIN) { | ||
1816 | debug2("channel %d: ctl read<=0 rfd %d len %d", | ||
1817 | c->self, c->rfd, len); | ||
1818 | chan_read_failed(c); | ||
1819 | return 0; | ||
1820 | } | ||
1821 | } else | ||
1822 | buffer_append(&c->input, buf, len); | ||
1823 | } | ||
1824 | return buffer_len(&c->input); | ||
1825 | } | ||
1826 | |||
1827 | static void | ||
1828 | channel_post_mux_client(Channel *c, fd_set *readset, fd_set *writeset) | ||
1829 | { | ||
1830 | u_int need; | ||
1831 | ssize_t len; | ||
1832 | |||
1833 | if (!compat20) | ||
1834 | fatal("%s: entered with !compat20", __func__); | ||
1835 | |||
1836 | if (c->rfd != -1 && FD_ISSET(c->rfd, readset) && | ||
1837 | (c->istate == CHAN_INPUT_OPEN || | ||
1838 | c->istate == CHAN_INPUT_WAIT_DRAIN)) { | ||
1839 | /* | ||
1840 | * Don't not read past the precise end of packets to | ||
1841 | * avoid disrupting fd passing. | ||
1842 | */ | ||
1843 | if (read_mux(c, 4) < 4) /* read header */ | ||
1844 | return; | ||
1845 | need = get_u32(buffer_ptr(&c->input)); | ||
1846 | #define CHANNEL_MUX_MAX_PACKET (256 * 1024) | ||
1847 | if (need > CHANNEL_MUX_MAX_PACKET) { | ||
1848 | debug2("channel %d: packet too big %u > %u", | ||
1849 | c->self, CHANNEL_MUX_MAX_PACKET, need); | ||
1850 | chan_rcvd_oclose(c); | ||
1851 | return; | ||
1852 | } | ||
1853 | if (read_mux(c, need + 4) < need + 4) /* read body */ | ||
1854 | return; | ||
1855 | if (c->mux_rcb(c) != 0) { | ||
1856 | debug("channel %d: mux_rcb failed", c->self); | ||
1857 | chan_mark_dead(c); | ||
1858 | return; | ||
1859 | } | ||
1860 | } | ||
1861 | |||
1862 | if (c->wfd != -1 && FD_ISSET(c->wfd, writeset) && | ||
1863 | buffer_len(&c->output) > 0) { | ||
1864 | len = write(c->wfd, buffer_ptr(&c->output), | ||
1865 | buffer_len(&c->output)); | ||
1866 | if (len < 0 && (errno == EINTR || errno == EAGAIN)) | ||
1867 | return; | ||
1868 | if (len <= 0) { | ||
1869 | chan_mark_dead(c); | ||
1870 | return; | ||
1871 | } | ||
1872 | buffer_consume(&c->output, len); | ||
1873 | } | ||
1874 | } | ||
1875 | |||
1876 | static void | ||
1877 | channel_post_mux_listener(Channel *c, fd_set *readset, fd_set *writeset) | ||
1878 | { | ||
1879 | Channel *nc; | ||
1880 | struct sockaddr_storage addr; | ||
1881 | socklen_t addrlen; | ||
1882 | int newsock; | ||
1883 | uid_t euid; | ||
1884 | gid_t egid; | ||
1885 | |||
1886 | if (!FD_ISSET(c->sock, readset)) | ||
1887 | return; | ||
1888 | |||
1889 | debug("multiplexing control connection"); | ||
1890 | |||
1891 | /* | ||
1892 | * Accept connection on control socket | ||
1893 | */ | ||
1894 | memset(&addr, 0, sizeof(addr)); | ||
1895 | addrlen = sizeof(addr); | ||
1896 | if ((newsock = accept(c->sock, (struct sockaddr*)&addr, | ||
1897 | &addrlen)) == -1) { | ||
1898 | error("%s accept: %s", __func__, strerror(errno)); | ||
1899 | return; | ||
1900 | } | ||
1901 | |||
1902 | if (getpeereid(newsock, &euid, &egid) < 0) { | ||
1903 | error("%s getpeereid failed: %s", __func__, | ||
1904 | strerror(errno)); | ||
1905 | close(newsock); | ||
1906 | return; | ||
1907 | } | ||
1908 | if ((euid != 0) && (getuid() != euid)) { | ||
1909 | error("multiplex uid mismatch: peer euid %u != uid %u", | ||
1910 | (u_int)euid, (u_int)getuid()); | ||
1911 | close(newsock); | ||
1912 | return; | ||
1913 | } | ||
1914 | nc = channel_new("multiplex client", SSH_CHANNEL_MUX_CLIENT, | ||
1915 | newsock, newsock, -1, c->local_window_max, | ||
1916 | c->local_maxpacket, 0, "mux-control", 1); | ||
1917 | nc->mux_rcb = c->mux_rcb; | ||
1918 | debug3("%s: new mux channel %d fd %d", __func__, | ||
1919 | nc->self, nc->sock); | ||
1920 | /* establish state */ | ||
1921 | nc->mux_rcb(nc); | ||
1922 | /* mux state transitions must not elicit protocol messages */ | ||
1923 | nc->flags |= CHAN_LOCAL; | ||
1924 | } | ||
1925 | |||
1816 | /* ARGSUSED */ | 1926 | /* ARGSUSED */ |
1817 | static void | 1927 | static void |
1818 | channel_post_output_drain_13(Channel *c, fd_set *readset, fd_set *writeset) | 1928 | channel_post_output_drain_13(Channel *c, fd_set *readset, fd_set *writeset) |
@@ -1841,6 +1951,8 @@ channel_handler_init_20(void) | |||
1841 | channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; | 1951 | channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener; |
1842 | channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; | 1952 | channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting; |
1843 | channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; | 1953 | channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic; |
1954 | channel_pre[SSH_CHANNEL_MUX_LISTENER] = &channel_pre_listener; | ||
1955 | channel_pre[SSH_CHANNEL_MUX_CLIENT] = &channel_pre_mux_client; | ||
1844 | 1956 | ||
1845 | channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; | 1957 | channel_post[SSH_CHANNEL_OPEN] = &channel_post_open; |
1846 | channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; | 1958 | channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener; |
@@ -1849,6 +1961,8 @@ channel_handler_init_20(void) | |||
1849 | channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; | 1961 | channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener; |
1850 | channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; | 1962 | channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting; |
1851 | channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; | 1963 | channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open; |
1964 | channel_post[SSH_CHANNEL_MUX_LISTENER] = &channel_post_mux_listener; | ||
1965 | channel_post[SSH_CHANNEL_MUX_CLIENT] = &channel_post_mux_client; | ||
1852 | } | 1966 | } |
1853 | 1967 | ||
1854 | static void | 1968 | static void |