diff options
-rw-r--r-- | ChangeLog | 23 | ||||
-rw-r--r-- | channels.c | 214 | ||||
-rw-r--r-- | channels.h | 18 | ||||
-rw-r--r-- | clientloop.c | 35 | ||||
-rw-r--r-- | clientloop.h | 12 | ||||
-rw-r--r-- | mux.c | 1840 | ||||
-rw-r--r-- | nchan.c | 21 | ||||
-rw-r--r-- | ssh.c | 22 |
8 files changed, 1676 insertions, 509 deletions
@@ -8,6 +8,29 @@ | |||
8 | [roaming_client.c] | 8 | [roaming_client.c] |
9 | s/long long unsigned/unsigned long long/, from tim via portable | 9 | s/long long unsigned/unsigned long long/, from tim via portable |
10 | (Id sync only, change already in portable) | 10 | (Id sync only, change already in portable) |
11 | - djm@cvs.openbsd.org 2010/01/26 01:28:35 | ||
12 | [channels.c channels.h clientloop.c clientloop.h mux.c nchan.c ssh.c] | ||
13 | rewrite ssh(1) multiplexing code to a more sensible protocol. | ||
14 | |||
15 | The new multiplexing code uses channels for the listener and | ||
16 | accepted control sockets to make the mux master non-blocking, so | ||
17 | no stalls when processing messages from a slave. | ||
18 | |||
19 | avoid use of fatal() in mux master protocol parsing so an errant slave | ||
20 | process cannot take down a running master. | ||
21 | |||
22 | implement requesting of port-forwards over multiplexed sessions. Any | ||
23 | port forwards requested by the slave are added to those the master has | ||
24 | established. | ||
25 | |||
26 | add support for stdio forwarding ("ssh -W host:port ...") in mux slaves. | ||
27 | |||
28 | document master/slave mux protocol so that other tools can use it to | ||
29 | control a running ssh(1). Note: there are no guarantees that this | ||
30 | protocol won't be incompatibly changed (though it is versioned). | ||
31 | |||
32 | feedback Salvador Fandino, dtucker@ | ||
33 | channel changes ok markus@ | ||
11 | 34 | ||
12 | 20100122 | 35 | 20100122 |
13 | - (tim) [configure.ac] Due to constraints in Windows Sockets in terms of | 36 | - (tim) [configure.ac] Due to constraints in Windows Sockets in terms of |
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 |
diff --git a/channels.h b/channels.h index 79ebe047a..cc71885f4 100644 --- a/channels.h +++ b/channels.h | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: channels.h,v 1.102 2010/01/11 01:39:46 dtucker Exp $ */ | 1 | /* $OpenBSD: channels.h,v 1.103 2010/01/26 01:28:35 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
@@ -53,7 +53,9 @@ | |||
53 | #define SSH_CHANNEL_CONNECTING 12 | 53 | #define SSH_CHANNEL_CONNECTING 12 |
54 | #define SSH_CHANNEL_DYNAMIC 13 | 54 | #define SSH_CHANNEL_DYNAMIC 13 |
55 | #define SSH_CHANNEL_ZOMBIE 14 /* Almost dead. */ | 55 | #define SSH_CHANNEL_ZOMBIE 14 /* Almost dead. */ |
56 | #define SSH_CHANNEL_MAX_TYPE 15 | 56 | #define SSH_CHANNEL_MUX_LISTENER 15 /* Listener for mux conn. */ |
57 | #define SSH_CHANNEL_MUX_CLIENT 16 /* Conn. to mux slave */ | ||
58 | #define SSH_CHANNEL_MAX_TYPE 17 | ||
57 | 59 | ||
58 | struct Channel; | 60 | struct Channel; |
59 | typedef struct Channel Channel; | 61 | typedef struct Channel Channel; |
@@ -81,6 +83,9 @@ struct channel_connect { | |||
81 | struct addrinfo *ai, *aitop; | 83 | struct addrinfo *ai, *aitop; |
82 | }; | 84 | }; |
83 | 85 | ||
86 | /* Callbacks for mux channels back into client-specific code */ | ||
87 | typedef int mux_callback_fn(struct Channel *); | ||
88 | |||
84 | struct Channel { | 89 | struct Channel { |
85 | int type; /* channel type/state */ | 90 | int type; /* channel type/state */ |
86 | int self; /* my own channel identifier */ | 91 | int self; /* my own channel identifier */ |
@@ -92,7 +97,7 @@ struct Channel { | |||
92 | int wfd; /* write fd */ | 97 | int wfd; /* write fd */ |
93 | int efd; /* extended fd */ | 98 | int efd; /* extended fd */ |
94 | int sock; /* sock fd */ | 99 | int sock; /* sock fd */ |
95 | int ctl_fd; /* control fd (client sharing) */ | 100 | int ctl_chan; /* control channel (multiplexed connections) */ |
96 | int isatty; /* rfd is a tty */ | 101 | int isatty; /* rfd is a tty */ |
97 | int wfd_isatty; /* wfd is a tty */ | 102 | int wfd_isatty; /* wfd is a tty */ |
98 | int client_tty; /* (client) TTY has been requested */ | 103 | int client_tty; /* (client) TTY has been requested */ |
@@ -142,6 +147,10 @@ struct Channel { | |||
142 | 147 | ||
143 | /* non-blocking connect */ | 148 | /* non-blocking connect */ |
144 | struct channel_connect connect_ctx; | 149 | struct channel_connect connect_ctx; |
150 | |||
151 | /* multiplexing protocol hook, called for each packet received */ | ||
152 | mux_callback_fn *mux_rcb; | ||
153 | void *mux_ctx; | ||
145 | }; | 154 | }; |
146 | 155 | ||
147 | #define CHAN_EXTENDED_IGNORE 0 | 156 | #define CHAN_EXTENDED_IGNORE 0 |
@@ -172,6 +181,7 @@ struct Channel { | |||
172 | #define CHAN_CLOSE_RCVD 0x02 | 181 | #define CHAN_CLOSE_RCVD 0x02 |
173 | #define CHAN_EOF_SENT 0x04 | 182 | #define CHAN_EOF_SENT 0x04 |
174 | #define CHAN_EOF_RCVD 0x08 | 183 | #define CHAN_EOF_RCVD 0x08 |
184 | #define CHAN_LOCAL 0x10 | ||
175 | 185 | ||
176 | #define CHAN_RBUF 16*1024 | 186 | #define CHAN_RBUF 16*1024 |
177 | 187 | ||
@@ -243,7 +253,7 @@ void channel_clear_adm_permitted_opens(void); | |||
243 | void channel_print_adm_permitted_opens(void); | 253 | void channel_print_adm_permitted_opens(void); |
244 | int channel_input_port_forward_request(int, int); | 254 | int channel_input_port_forward_request(int, int); |
245 | Channel *channel_connect_to(const char *, u_short, char *, char *); | 255 | Channel *channel_connect_to(const char *, u_short, char *, char *); |
246 | Channel *channel_connect_stdio_fwd(const char*, u_short); | 256 | Channel *channel_connect_stdio_fwd(const char*, u_short, int, int); |
247 | Channel *channel_connect_by_listen_address(u_short, char *, char *); | 257 | Channel *channel_connect_by_listen_address(u_short, char *, char *); |
248 | int channel_request_remote_forwarding(const char *, u_short, | 258 | int channel_request_remote_forwarding(const char *, u_short, |
249 | const char *, u_short); | 259 | const char *, u_short); |
diff --git a/clientloop.c b/clientloop.c index 5793a6e91..05f4720a1 100644 --- a/clientloop.c +++ b/clientloop.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: clientloop.c,v 1.216 2010/01/09 05:04:24 djm Exp $ */ | 1 | /* $OpenBSD: clientloop.c,v 1.217 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 |
@@ -121,7 +121,7 @@ extern int stdin_null_flag; | |||
121 | extern int no_shell_flag; | 121 | extern int no_shell_flag; |
122 | 122 | ||
123 | /* Control socket */ | 123 | /* Control socket */ |
124 | extern int muxserver_sock; | 124 | extern int muxserver_sock; /* XXX use mux_client_cleanup() instead */ |
125 | 125 | ||
126 | /* | 126 | /* |
127 | * Name of the host we are connecting to. This is the name given on the | 127 | * Name of the host we are connecting to. This is the name given on the |
@@ -146,7 +146,7 @@ static volatile sig_atomic_t received_signal = 0; | |||
146 | static int in_non_blocking_mode = 0; | 146 | static int in_non_blocking_mode = 0; |
147 | 147 | ||
148 | /* Common data for the client loop code. */ | 148 | /* Common data for the client loop code. */ |
149 | static volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */ | 149 | volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */ |
150 | static int escape_char1; /* Escape character. (proto1 only) */ | 150 | static int escape_char1; /* Escape character. (proto1 only) */ |
151 | static int escape_pending1; /* Last character was an escape (proto1 only) */ | 151 | static int escape_pending1; /* Last character was an escape (proto1 only) */ |
152 | static int last_was_cr; /* Last character was a newline. */ | 152 | static int last_was_cr; /* Last character was a newline. */ |
@@ -564,9 +564,6 @@ client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, | |||
564 | if (packet_have_data_to_write()) | 564 | if (packet_have_data_to_write()) |
565 | FD_SET(connection_out, *writesetp); | 565 | FD_SET(connection_out, *writesetp); |
566 | 566 | ||
567 | if (muxserver_sock != -1) | ||
568 | FD_SET(muxserver_sock, *readsetp); | ||
569 | |||
570 | /* | 567 | /* |
571 | * Wait for something to happen. This will suspend the process until | 568 | * Wait for something to happen. This will suspend the process until |
572 | * some selected descriptor can be read, written, or has some other | 569 | * some selected descriptor can be read, written, or has some other |
@@ -695,7 +692,7 @@ client_status_confirm(int type, Channel *c, void *ctx) | |||
695 | 692 | ||
696 | /* XXX supress on mux _client_ quietmode */ | 693 | /* XXX supress on mux _client_ quietmode */ |
697 | tochan = options.log_level >= SYSLOG_LEVEL_ERROR && | 694 | tochan = options.log_level >= SYSLOG_LEVEL_ERROR && |
698 | c->ctl_fd != -1 && c->extended_usage == CHAN_EXTENDED_WRITE; | 695 | c->ctl_chan != -1 && c->extended_usage == CHAN_EXTENDED_WRITE; |
699 | 696 | ||
700 | if (type == SSH2_MSG_CHANNEL_SUCCESS) { | 697 | if (type == SSH2_MSG_CHANNEL_SUCCESS) { |
701 | debug2("%s request accepted on channel %d", | 698 | debug2("%s request accepted on channel %d", |
@@ -839,6 +836,7 @@ process_cmdline(void) | |||
839 | while (isspace(*++s)) | 836 | while (isspace(*++s)) |
840 | ; | 837 | ; |
841 | 838 | ||
839 | /* XXX update list of forwards in options */ | ||
842 | if (delete) { | 840 | if (delete) { |
843 | cancel_port = 0; | 841 | cancel_port = 0; |
844 | cancel_host = hpdelim(&s); /* may be NULL */ | 842 | cancel_host = hpdelim(&s); /* may be NULL */ |
@@ -936,7 +934,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr, | |||
936 | escape_char); | 934 | escape_char); |
937 | buffer_append(berr, string, strlen(string)); | 935 | buffer_append(berr, string, strlen(string)); |
938 | 936 | ||
939 | if (c && c->ctl_fd != -1) { | 937 | if (c && c->ctl_chan != -1) { |
940 | chan_read_failed(c); | 938 | chan_read_failed(c); |
941 | chan_write_failed(c); | 939 | chan_write_failed(c); |
942 | return 0; | 940 | return 0; |
@@ -946,7 +944,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr, | |||
946 | 944 | ||
947 | case 'Z' - 64: | 945 | case 'Z' - 64: |
948 | /* XXX support this for mux clients */ | 946 | /* XXX support this for mux clients */ |
949 | if (c && c->ctl_fd != -1) { | 947 | if (c && c->ctl_chan != -1) { |
950 | noescape: | 948 | noescape: |
951 | snprintf(string, sizeof string, | 949 | snprintf(string, sizeof string, |
952 | "%c%c escape not available to " | 950 | "%c%c escape not available to " |
@@ -991,7 +989,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr, | |||
991 | continue; | 989 | continue; |
992 | 990 | ||
993 | case '&': | 991 | case '&': |
994 | if (c && c->ctl_fd != -1) | 992 | if (c && c->ctl_chan != -1) |
995 | goto noescape; | 993 | goto noescape; |
996 | /* | 994 | /* |
997 | * Detach the program (continue to serve | 995 | * Detach the program (continue to serve |
@@ -1042,7 +1040,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr, | |||
1042 | continue; | 1040 | continue; |
1043 | 1041 | ||
1044 | case '?': | 1042 | case '?': |
1045 | if (c && c->ctl_fd != -1) { | 1043 | if (c && c->ctl_chan != -1) { |
1046 | snprintf(string, sizeof string, | 1044 | snprintf(string, sizeof string, |
1047 | "%c?\r\n\ | 1045 | "%c?\r\n\ |
1048 | Supported escape sequences:\r\n\ | 1046 | Supported escape sequences:\r\n\ |
@@ -1091,7 +1089,7 @@ Supported escape sequences:\r\n\ | |||
1091 | continue; | 1089 | continue; |
1092 | 1090 | ||
1093 | case 'C': | 1091 | case 'C': |
1094 | if (c && c->ctl_fd != -1) | 1092 | if (c && c->ctl_chan != -1) |
1095 | goto noescape; | 1093 | goto noescape; |
1096 | process_cmdline(); | 1094 | process_cmdline(); |
1097 | continue; | 1095 | continue; |
@@ -1327,8 +1325,6 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) | |||
1327 | connection_in = packet_get_connection_in(); | 1325 | connection_in = packet_get_connection_in(); |
1328 | connection_out = packet_get_connection_out(); | 1326 | connection_out = packet_get_connection_out(); |
1329 | max_fd = MAX(connection_in, connection_out); | 1327 | max_fd = MAX(connection_in, connection_out); |
1330 | if (muxserver_sock != -1) | ||
1331 | max_fd = MAX(max_fd, muxserver_sock); | ||
1332 | 1328 | ||
1333 | if (!compat20) { | 1329 | if (!compat20) { |
1334 | /* enable nonblocking unless tty */ | 1330 | /* enable nonblocking unless tty */ |
@@ -1446,12 +1442,6 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) | |||
1446 | /* Buffer input from the connection. */ | 1442 | /* Buffer input from the connection. */ |
1447 | client_process_net_input(readset); | 1443 | client_process_net_input(readset); |
1448 | 1444 | ||
1449 | /* Accept control connections. */ | ||
1450 | if (muxserver_sock != -1 &&FD_ISSET(muxserver_sock, readset)) { | ||
1451 | if (muxserver_accept_control()) | ||
1452 | quit_pending = 1; | ||
1453 | } | ||
1454 | |||
1455 | if (quit_pending) | 1445 | if (quit_pending) |
1456 | break; | 1446 | break; |
1457 | 1447 | ||
@@ -1859,9 +1849,8 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt) | |||
1859 | chan_rcvd_eow(c); | 1849 | chan_rcvd_eow(c); |
1860 | } else if (strcmp(rtype, "exit-status") == 0) { | 1850 | } else if (strcmp(rtype, "exit-status") == 0) { |
1861 | exitval = packet_get_int(); | 1851 | exitval = packet_get_int(); |
1862 | if (c->ctl_fd != -1) { | 1852 | if (c->ctl_chan != -1) { |
1863 | /* Dispatch to mux client */ | 1853 | mux_exit_message(c, exitval); |
1864 | atomicio(vwrite, c->ctl_fd, &exitval, sizeof(exitval)); | ||
1865 | success = 1; | 1854 | success = 1; |
1866 | } else if (id == session_ident) { | 1855 | } else if (id == session_ident) { |
1867 | /* Record exit value of local session */ | 1856 | /* Record exit value of local session */ |
diff --git a/clientloop.h b/clientloop.h index 8bb874b38..0b8257b99 100644 --- a/clientloop.h +++ b/clientloop.h | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: clientloop.h,v 1.22 2008/06/12 15:19:17 djm Exp $ */ | 1 | /* $OpenBSD: clientloop.h,v 1.23 2010/01/26 01:28:35 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
@@ -56,18 +56,14 @@ typedef void global_confirm_cb(int, u_int32_t seq, void *); | |||
56 | void client_register_global_confirm(global_confirm_cb *, void *); | 56 | void client_register_global_confirm(global_confirm_cb *, void *); |
57 | 57 | ||
58 | /* Multiplexing protocol version */ | 58 | /* Multiplexing protocol version */ |
59 | #define SSHMUX_VER 2 | 59 | #define SSHMUX_VER 4 |
60 | 60 | ||
61 | /* Multiplexing control protocol flags */ | 61 | /* Multiplexing control protocol flags */ |
62 | #define SSHMUX_COMMAND_OPEN 1 /* Open new connection */ | 62 | #define SSHMUX_COMMAND_OPEN 1 /* Open new connection */ |
63 | #define SSHMUX_COMMAND_ALIVE_CHECK 2 /* Check master is alive */ | 63 | #define SSHMUX_COMMAND_ALIVE_CHECK 2 /* Check master is alive */ |
64 | #define SSHMUX_COMMAND_TERMINATE 3 /* Ask master to exit */ | 64 | #define SSHMUX_COMMAND_TERMINATE 3 /* Ask master to exit */ |
65 | 65 | #define SSHMUX_COMMAND_STDIO_FWD 4 /* Open stdio fwd (ssh -W) */ | |
66 | #define SSHMUX_FLAG_TTY (1) /* Request tty on open */ | ||
67 | #define SSHMUX_FLAG_SUBSYS (1<<1) /* Subsystem request on open */ | ||
68 | #define SSHMUX_FLAG_X11_FWD (1<<2) /* Request X11 forwarding */ | ||
69 | #define SSHMUX_FLAG_AGENT_FWD (1<<3) /* Request agent forwarding */ | ||
70 | 66 | ||
71 | void muxserver_listen(void); | 67 | void muxserver_listen(void); |
72 | int muxserver_accept_control(void); | ||
73 | void muxclient(const char *); | 68 | void muxclient(const char *); |
69 | void mux_exit_message(Channel *, int); | ||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: mux.c,v 1.9 2010/01/09 05:04:24 djm Exp $ */ | 1 | /* $OpenBSD: mux.c,v 1.10 2010/01/26 01:28:35 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2002-2008 Damien Miller <djm@openbsd.org> | 3 | * Copyright (c) 2002-2008 Damien Miller <djm@openbsd.org> |
4 | * | 4 | * |
@@ -17,25 +17,21 @@ | |||
17 | 17 | ||
18 | /* ssh session multiplexing support */ | 18 | /* ssh session multiplexing support */ |
19 | 19 | ||
20 | #include "includes.h" | ||
21 | |||
22 | /* | 20 | /* |
23 | * TODO: | 21 | * TODO: |
24 | * 1. partial reads in muxserver_accept_control (maybe make channels | 22 | * - Better signalling from master to slave, especially passing of |
25 | * from accepted connections) | ||
26 | * 2. Better signalling from master to slave, especially passing of | ||
27 | * error messages | 23 | * error messages |
28 | * 3. Better fall-back from mux slave error to new connection. | 24 | * - Better fall-back from mux slave error to new connection. |
29 | * 3. Add/delete forwardings via slave | 25 | * - ExitOnForwardingFailure |
30 | * 4. ExitOnForwardingFailure (after #3 obviously) | 26 | * - Maybe extension mechanisms for multi-X11/multi-agent forwarding |
31 | * 5. Maybe extension mechanisms for multi-X11/multi-agent forwarding | 27 | * - Support ~^Z in mux slaves. |
32 | * 6. Document the mux mini-protocol somewhere. | 28 | * - Inspect or control sessions in master. |
33 | * 7. Support ~^Z in mux slaves. | 29 | * - If we ever support the "signal" channel request, send signals on |
34 | * 8. Inspect or control sessions in master. | 30 | * sessions in master. |
35 | * 9. If we ever support the "signal" channel request, send signals on | ||
36 | * sessions in master. | ||
37 | */ | 31 | */ |
38 | 32 | ||
33 | #include "includes.h" | ||
34 | |||
39 | #include <sys/types.h> | 35 | #include <sys/types.h> |
40 | #include <sys/param.h> | 36 | #include <sys/param.h> |
41 | #include <sys/stat.h> | 37 | #include <sys/stat.h> |
@@ -55,6 +51,14 @@ | |||
55 | #include <paths.h> | 51 | #include <paths.h> |
56 | #endif | 52 | #endif |
57 | 53 | ||
54 | #ifdef HAVE_POLL_H | ||
55 | #include <poll.h> | ||
56 | #else | ||
57 | # ifdef HAVE_SYS_POLL_H | ||
58 | # include <sys/poll.h> | ||
59 | # endif | ||
60 | #endif | ||
61 | |||
58 | #ifdef HAVE_UTIL_H | 62 | #ifdef HAVE_UTIL_H |
59 | # include <util.h> | 63 | # include <util.h> |
60 | #endif | 64 | #endif |
@@ -88,13 +92,16 @@ extern int stdin_null_flag; | |||
88 | extern char *host; | 92 | extern char *host; |
89 | extern int subsystem_flag; | 93 | extern int subsystem_flag; |
90 | extern Buffer command; | 94 | extern Buffer command; |
95 | extern volatile sig_atomic_t quit_pending; | ||
96 | extern char *stdio_forward_host; | ||
97 | extern int stdio_forward_port; | ||
91 | 98 | ||
92 | /* Context for session open confirmation callback */ | 99 | /* Context for session open confirmation callback */ |
93 | struct mux_session_confirm_ctx { | 100 | struct mux_session_confirm_ctx { |
94 | int want_tty; | 101 | u_int want_tty; |
95 | int want_subsys; | 102 | u_int want_subsys; |
96 | int want_x_fwd; | 103 | u_int want_x_fwd; |
97 | int want_agent_fwd; | 104 | u_int want_agent_fwd; |
98 | Buffer cmd; | 105 | Buffer cmd; |
99 | char *term; | 106 | char *term; |
100 | struct termios tio; | 107 | struct termios tio; |
@@ -104,6 +111,9 @@ struct mux_session_confirm_ctx { | |||
104 | /* fd to control socket */ | 111 | /* fd to control socket */ |
105 | int muxserver_sock = -1; | 112 | int muxserver_sock = -1; |
106 | 113 | ||
114 | /* client request id */ | ||
115 | u_int muxclient_request_id = 0; | ||
116 | |||
107 | /* Multiplexing control command */ | 117 | /* Multiplexing control command */ |
108 | u_int muxclient_command = 0; | 118 | u_int muxclient_command = 0; |
109 | 119 | ||
@@ -113,16 +123,823 @@ static volatile sig_atomic_t muxclient_terminate = 0; | |||
113 | /* PID of multiplex server */ | 123 | /* PID of multiplex server */ |
114 | static u_int muxserver_pid = 0; | 124 | static u_int muxserver_pid = 0; |
115 | 125 | ||
126 | static Channel *mux_listener_channel = NULL; | ||
127 | |||
128 | struct mux_master_state { | ||
129 | int hello_rcvd; | ||
130 | }; | ||
131 | |||
132 | /* mux protocol messages */ | ||
133 | #define MUX_MSG_HELLO 0x00000001 | ||
134 | #define MUX_C_NEW_SESSION 0x10000002 | ||
135 | #define MUX_C_ALIVE_CHECK 0x10000004 | ||
136 | #define MUX_C_TERMINATE 0x10000005 | ||
137 | #define MUX_C_OPEN_FWD 0x10000006 | ||
138 | #define MUX_C_CLOSE_FWD 0x10000007 | ||
139 | #define MUX_C_NEW_STDIO_FWD 0x10000008 | ||
140 | #define MUX_S_OK 0x80000001 | ||
141 | #define MUX_S_PERMISSION_DENIED 0x80000002 | ||
142 | #define MUX_S_FAILURE 0x80000003 | ||
143 | #define MUX_S_EXIT_MESSAGE 0x80000004 | ||
144 | #define MUX_S_ALIVE 0x80000005 | ||
145 | #define MUX_S_SESSION_OPENED 0x80000006 | ||
146 | |||
147 | /* type codes for MUX_C_OPEN_FWD and MUX_C_CLOSE_FWD */ | ||
148 | #define MUX_FWD_LOCAL 1 | ||
149 | #define MUX_FWD_REMOTE 2 | ||
150 | #define MUX_FWD_DYNAMIC 3 | ||
151 | |||
152 | static void mux_session_confirm(int, void *); | ||
153 | |||
154 | static int process_mux_master_hello(u_int, Channel *, Buffer *, Buffer *); | ||
155 | static int process_mux_new_session(u_int, Channel *, Buffer *, Buffer *); | ||
156 | static int process_mux_alive_check(u_int, Channel *, Buffer *, Buffer *); | ||
157 | static int process_mux_terminate(u_int, Channel *, Buffer *, Buffer *); | ||
158 | static int process_mux_open_fwd(u_int, Channel *, Buffer *, Buffer *); | ||
159 | static int process_mux_close_fwd(u_int, Channel *, Buffer *, Buffer *); | ||
160 | static int process_mux_stdio_fwd(u_int, Channel *, Buffer *, Buffer *); | ||
161 | |||
162 | static const struct { | ||
163 | u_int type; | ||
164 | int (*handler)(u_int, Channel *, Buffer *, Buffer *); | ||
165 | } mux_master_handlers[] = { | ||
166 | { MUX_MSG_HELLO, process_mux_master_hello }, | ||
167 | { MUX_C_NEW_SESSION, process_mux_new_session }, | ||
168 | { MUX_C_ALIVE_CHECK, process_mux_alive_check }, | ||
169 | { MUX_C_TERMINATE, process_mux_terminate }, | ||
170 | { MUX_C_OPEN_FWD, process_mux_open_fwd }, | ||
171 | { MUX_C_CLOSE_FWD, process_mux_close_fwd }, | ||
172 | { MUX_C_NEW_STDIO_FWD, process_mux_stdio_fwd }, | ||
173 | { 0, NULL } | ||
174 | }; | ||
175 | |||
176 | /* Cleanup callback fired on closure of mux slave _session_ channel */ | ||
177 | /* ARGSUSED */ | ||
178 | static void | ||
179 | mux_master_session_cleanup_cb(int cid, void *unused) | ||
180 | { | ||
181 | Channel *cc, *c = channel_by_id(cid); | ||
182 | |||
183 | debug3("%s: entering for channel %d", __func__, cid); | ||
184 | if (c == NULL) | ||
185 | fatal("%s: channel_by_id(%i) == NULL", __func__, cid); | ||
186 | if (c->ctl_chan != -1) { | ||
187 | if ((cc = channel_by_id(c->ctl_chan)) == NULL) | ||
188 | fatal("%s: channel %d missing control channel %d", | ||
189 | __func__, c->self, c->ctl_chan); | ||
190 | c->ctl_chan = -1; | ||
191 | cc->remote_id = -1; | ||
192 | chan_rcvd_oclose(cc); | ||
193 | } | ||
194 | channel_cancel_cleanup(c->self); | ||
195 | } | ||
196 | |||
197 | /* Cleanup callback fired on closure of mux slave _control_ channel */ | ||
198 | /* ARGSUSED */ | ||
199 | static void | ||
200 | mux_master_control_cleanup_cb(int cid, void *unused) | ||
201 | { | ||
202 | Channel *sc, *c = channel_by_id(cid); | ||
203 | |||
204 | debug3("%s: entering for channel %d", __func__, cid); | ||
205 | if (c == NULL) | ||
206 | fatal("%s: channel_by_id(%i) == NULL", __func__, cid); | ||
207 | if (c->remote_id != -1) { | ||
208 | if ((sc = channel_by_id(c->remote_id)) == NULL) | ||
209 | debug2("%s: channel %d n session channel %d", | ||
210 | __func__, c->self, c->remote_id); | ||
211 | c->remote_id = -1; | ||
212 | sc->ctl_chan = -1; | ||
213 | chan_mark_dead(sc); | ||
214 | } | ||
215 | channel_cancel_cleanup(c->self); | ||
216 | } | ||
217 | |||
218 | /* Check mux client environment variables before passing them to mux master. */ | ||
219 | static int | ||
220 | env_permitted(char *env) | ||
221 | { | ||
222 | int i, ret; | ||
223 | char name[1024], *cp; | ||
224 | |||
225 | if ((cp = strchr(env, '=')) == NULL || cp == env) | ||
226 | return 0; | ||
227 | ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env); | ||
228 | if (ret <= 0 || (size_t)ret >= sizeof(name)) { | ||
229 | error("env_permitted: name '%.100s...' too long", env); | ||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | for (i = 0; i < options.num_send_env; i++) | ||
234 | if (match_pattern(name, options.send_env[i])) | ||
235 | return 1; | ||
236 | |||
237 | return 0; | ||
238 | } | ||
239 | |||
240 | /* Mux master protocol message handlers */ | ||
241 | |||
242 | static int | ||
243 | process_mux_master_hello(u_int rid, Channel *c, Buffer *m, Buffer *r) | ||
244 | { | ||
245 | u_int ver; | ||
246 | struct mux_master_state *state = (struct mux_master_state *)c->mux_ctx; | ||
247 | |||
248 | if (state == NULL) | ||
249 | fatal("%s: channel %d: c->mux_ctx == NULL", __func__, c->self); | ||
250 | if (state->hello_rcvd) { | ||
251 | error("%s: HELLO received twice", __func__); | ||
252 | return -1; | ||
253 | } | ||
254 | if (buffer_get_int_ret(&ver, m) != 0) { | ||
255 | malf: | ||
256 | error("%s: malformed message", __func__); | ||
257 | return -1; | ||
258 | } | ||
259 | if (ver != SSHMUX_VER) { | ||
260 | error("Unsupported multiplexing protocol version %d " | ||
261 | "(expected %d)", ver, SSHMUX_VER); | ||
262 | return -1; | ||
263 | } | ||
264 | debug2("%s: channel %d slave version %u", __func__, c->self, ver); | ||
265 | |||
266 | /* No extensions are presently defined */ | ||
267 | while (buffer_len(m) > 0) { | ||
268 | char *name = buffer_get_string_ret(m, NULL); | ||
269 | char *value = buffer_get_string_ret(m, NULL); | ||
270 | |||
271 | if (name == NULL || value == NULL) { | ||
272 | if (name != NULL) | ||
273 | xfree(name); | ||
274 | goto malf; | ||
275 | } | ||
276 | debug2("Unrecognised slave extension \"%s\"", name); | ||
277 | xfree(name); | ||
278 | xfree(value); | ||
279 | } | ||
280 | state->hello_rcvd = 1; | ||
281 | return 0; | ||
282 | } | ||
283 | |||
284 | static int | ||
285 | process_mux_new_session(u_int rid, Channel *c, Buffer *m, Buffer *r) | ||
286 | { | ||
287 | Channel *nc; | ||
288 | struct mux_session_confirm_ctx *cctx; | ||
289 | char *reserved, *cmd, *cp; | ||
290 | u_int i, j, len, env_len, escape_char, window, packetmax; | ||
291 | int new_fd[3]; | ||
292 | |||
293 | /* Reply for SSHMUX_COMMAND_OPEN */ | ||
294 | cctx = xcalloc(1, sizeof(*cctx)); | ||
295 | cctx->term = NULL; | ||
296 | cmd = reserved = NULL; | ||
297 | if ((reserved = buffer_get_string_ret(m, NULL)) == NULL || | ||
298 | buffer_get_int_ret(&cctx->want_tty, m) != 0 || | ||
299 | buffer_get_int_ret(&cctx->want_x_fwd, m) != 0 || | ||
300 | buffer_get_int_ret(&cctx->want_agent_fwd, m) != 0 || | ||
301 | buffer_get_int_ret(&cctx->want_subsys, m) != 0 || | ||
302 | buffer_get_int_ret(&escape_char, m) != 0 || | ||
303 | (cctx->term = buffer_get_string_ret(m, &len)) == NULL || | ||
304 | (cmd = buffer_get_string_ret(m, &len)) == NULL) { | ||
305 | malf: | ||
306 | if (cmd != NULL) | ||
307 | xfree(cmd); | ||
308 | if (reserved != NULL) | ||
309 | xfree(reserved); | ||
310 | if (cctx->term != NULL) | ||
311 | xfree(cctx->term); | ||
312 | error("%s: malformed message", __func__); | ||
313 | return -1; | ||
314 | } | ||
315 | xfree(reserved); | ||
316 | reserved = NULL; | ||
317 | |||
318 | cctx->env = NULL; | ||
319 | env_len = 0; | ||
320 | while (buffer_len(m) > 0) { | ||
321 | #define MUX_MAX_ENV_VARS 4096 | ||
322 | if ((cp = buffer_get_string_ret(m, &len)) == NULL) { | ||
323 | xfree(cmd); | ||
324 | goto malf; | ||
325 | } | ||
326 | if (!env_permitted(cp)) { | ||
327 | xfree(cp); | ||
328 | continue; | ||
329 | } | ||
330 | cctx->env = xrealloc(cctx->env, env_len + 2, | ||
331 | sizeof(*cctx->env)); | ||
332 | cctx->env[env_len++] = cp; | ||
333 | cctx->env[env_len] = NULL; | ||
334 | if (env_len > MUX_MAX_ENV_VARS) { | ||
335 | error(">%d environment variables received, ignoring " | ||
336 | "additional", MUX_MAX_ENV_VARS); | ||
337 | break; | ||
338 | } | ||
339 | } | ||
340 | |||
341 | debug2("%s: channel %d: request tty %d, X %d, agent %d, subsys %d, " | ||
342 | "term \"%s\", cmd \"%s\", env %u", __func__, c->self, | ||
343 | cctx->want_tty, cctx->want_x_fwd, cctx->want_agent_fwd, | ||
344 | cctx->want_subsys, cctx->term, cmd, env_len); | ||
345 | |||
346 | buffer_init(&cctx->cmd); | ||
347 | buffer_append(&cctx->cmd, cmd, strlen(cmd)); | ||
348 | xfree(cmd); | ||
349 | cmd = NULL; | ||
350 | |||
351 | /* Gather fds from client */ | ||
352 | for(i = 0; i < 3; i++) { | ||
353 | if ((new_fd[i] = mm_receive_fd(c->sock)) == -1) { | ||
354 | error("%s: failed to receive fd %d from slave", | ||
355 | __func__, i); | ||
356 | for (j = 0; j < i; j++) | ||
357 | close(new_fd[j]); | ||
358 | for (j = 0; j < env_len; j++) | ||
359 | xfree(cctx->env[j]); | ||
360 | if (env_len > 0) | ||
361 | xfree(cctx->env); | ||
362 | xfree(cctx->term); | ||
363 | buffer_free(&cctx->cmd); | ||
364 | xfree(cctx); | ||
365 | |||
366 | /* prepare reply */ | ||
367 | buffer_put_int(r, MUX_S_FAILURE); | ||
368 | buffer_put_int(r, rid); | ||
369 | buffer_put_cstring(r, | ||
370 | "did not receive file descriptors"); | ||
371 | return -1; | ||
372 | } | ||
373 | } | ||
374 | |||
375 | debug3("%s: got fds stdin %d, stdout %d, stderr %d", __func__, | ||
376 | new_fd[0], new_fd[1], new_fd[2]); | ||
377 | |||
378 | /* XXX support multiple child sessions in future */ | ||
379 | if (c->remote_id != -1) { | ||
380 | debug2("%s: session already open", __func__); | ||
381 | /* prepare reply */ | ||
382 | buffer_put_int(r, MUX_S_FAILURE); | ||
383 | buffer_put_int(r, rid); | ||
384 | buffer_put_cstring(r, "Multiple sessions not supported"); | ||
385 | cleanup: | ||
386 | close(new_fd[0]); | ||
387 | close(new_fd[1]); | ||
388 | close(new_fd[2]); | ||
389 | xfree(cctx->term); | ||
390 | if (env_len != 0) { | ||
391 | for (i = 0; i < env_len; i++) | ||
392 | xfree(cctx->env[i]); | ||
393 | xfree(cctx->env); | ||
394 | } | ||
395 | buffer_free(&cctx->cmd); | ||
396 | return 0; | ||
397 | } | ||
398 | |||
399 | if (options.control_master == SSHCTL_MASTER_ASK || | ||
400 | options.control_master == SSHCTL_MASTER_AUTO_ASK) { | ||
401 | if (!ask_permission("Allow shared connection to %s? ", host)) { | ||
402 | debug2("%s: session refused by user", __func__); | ||
403 | /* prepare reply */ | ||
404 | buffer_put_int(r, MUX_S_PERMISSION_DENIED); | ||
405 | buffer_put_int(r, rid); | ||
406 | buffer_put_cstring(r, "Permission denied"); | ||
407 | goto cleanup; | ||
408 | } | ||
409 | } | ||
410 | |||
411 | /* Try to pick up ttymodes from client before it goes raw */ | ||
412 | if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1) | ||
413 | error("%s: tcgetattr: %s", __func__, strerror(errno)); | ||
414 | |||
415 | /* enable nonblocking unless tty */ | ||
416 | if (!isatty(new_fd[0])) | ||
417 | set_nonblock(new_fd[0]); | ||
418 | if (!isatty(new_fd[1])) | ||
419 | set_nonblock(new_fd[1]); | ||
420 | if (!isatty(new_fd[2])) | ||
421 | set_nonblock(new_fd[2]); | ||
422 | |||
423 | window = CHAN_SES_WINDOW_DEFAULT; | ||
424 | packetmax = CHAN_SES_PACKET_DEFAULT; | ||
425 | if (cctx->want_tty) { | ||
426 | window >>= 1; | ||
427 | packetmax >>= 1; | ||
428 | } | ||
429 | |||
430 | nc = channel_new("session", SSH_CHANNEL_OPENING, | ||
431 | new_fd[0], new_fd[1], new_fd[2], window, packetmax, | ||
432 | CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0); | ||
433 | |||
434 | nc->ctl_chan = c->self; /* link session -> control channel */ | ||
435 | c->remote_id = nc->self; /* link control -> session channel */ | ||
436 | |||
437 | if (cctx->want_tty && escape_char != 0xffffffff) { | ||
438 | channel_register_filter(nc->self, | ||
439 | client_simple_escape_filter, NULL, | ||
440 | client_filter_cleanup, | ||
441 | client_new_escape_filter_ctx((int)escape_char)); | ||
442 | } | ||
443 | |||
444 | debug2("%s: channel_new: %d linked to control channel %d", | ||
445 | __func__, nc->self, nc->ctl_chan); | ||
446 | |||
447 | channel_send_open(nc->self); | ||
448 | channel_register_open_confirm(nc->self, mux_session_confirm, cctx); | ||
449 | channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0); | ||
450 | |||
451 | /* prepare reply */ | ||
452 | /* XXX defer until mux_session_confirm() fires */ | ||
453 | buffer_put_int(r, MUX_S_SESSION_OPENED); | ||
454 | buffer_put_int(r, rid); | ||
455 | buffer_put_int(r, nc->self); | ||
456 | |||
457 | return 0; | ||
458 | } | ||
459 | |||
460 | static int | ||
461 | process_mux_alive_check(u_int rid, Channel *c, Buffer *m, Buffer *r) | ||
462 | { | ||
463 | debug2("%s: channel %d: alive check", __func__, c->self); | ||
464 | |||
465 | /* prepare reply */ | ||
466 | buffer_put_int(r, MUX_S_ALIVE); | ||
467 | buffer_put_int(r, rid); | ||
468 | buffer_put_int(r, (u_int)getpid()); | ||
469 | |||
470 | return 0; | ||
471 | } | ||
472 | |||
473 | static int | ||
474 | process_mux_terminate(u_int rid, Channel *c, Buffer *m, Buffer *r) | ||
475 | { | ||
476 | debug2("%s: channel %d: terminate request", __func__, c->self); | ||
477 | |||
478 | if (options.control_master == SSHCTL_MASTER_ASK || | ||
479 | options.control_master == SSHCTL_MASTER_AUTO_ASK) { | ||
480 | if (!ask_permission("Terminate shared connection to %s? ", | ||
481 | host)) { | ||
482 | debug2("%s: termination refused by user", __func__); | ||
483 | buffer_put_int(r, MUX_S_PERMISSION_DENIED); | ||
484 | buffer_put_int(r, rid); | ||
485 | buffer_put_cstring(r, "Permission denied"); | ||
486 | return 0; | ||
487 | } | ||
488 | } | ||
489 | |||
490 | quit_pending = 1; | ||
491 | buffer_put_int(r, MUX_S_OK); | ||
492 | buffer_put_int(r, rid); | ||
493 | /* XXX exit happens too soon - message never makes it to client */ | ||
494 | return 0; | ||
495 | } | ||
496 | |||
497 | static char * | ||
498 | format_forward(u_int ftype, Forward *fwd) | ||
499 | { | ||
500 | char *ret; | ||
501 | |||
502 | switch (ftype) { | ||
503 | case MUX_FWD_LOCAL: | ||
504 | xasprintf(&ret, "local forward %.200s:%d -> %.200s:%d", | ||
505 | (fwd->listen_host == NULL) ? | ||
506 | (options.gateway_ports ? "*" : "LOCALHOST") : | ||
507 | fwd->listen_host, fwd->listen_port, | ||
508 | fwd->connect_host, fwd->connect_port); | ||
509 | break; | ||
510 | case MUX_FWD_DYNAMIC: | ||
511 | xasprintf(&ret, "dynamic forward %.200s:%d -> *", | ||
512 | (fwd->listen_host == NULL) ? | ||
513 | (options.gateway_ports ? "*" : "LOCALHOST") : | ||
514 | fwd->listen_host, fwd->listen_port); | ||
515 | break; | ||
516 | case MUX_FWD_REMOTE: | ||
517 | xasprintf(&ret, "remote forward %.200s:%d -> %.200s:%d", | ||
518 | (fwd->listen_host == NULL) ? | ||
519 | "LOCALHOST" : fwd->listen_host, | ||
520 | fwd->listen_port, | ||
521 | fwd->connect_host, fwd->connect_port); | ||
522 | break; | ||
523 | default: | ||
524 | fatal("%s: unknown forward type %u", __func__, ftype); | ||
525 | } | ||
526 | return ret; | ||
527 | } | ||
528 | |||
529 | static int | ||
530 | compare_host(const char *a, const char *b) | ||
531 | { | ||
532 | if (a == NULL && b == NULL) | ||
533 | return 1; | ||
534 | if (a == NULL || b == NULL) | ||
535 | return 0; | ||
536 | return strcmp(a, b) == 0; | ||
537 | } | ||
538 | |||
539 | static int | ||
540 | compare_forward(Forward *a, Forward *b) | ||
541 | { | ||
542 | if (!compare_host(a->listen_host, b->listen_host)) | ||
543 | return 0; | ||
544 | if (a->listen_port != b->listen_port) | ||
545 | return 0; | ||
546 | if (!compare_host(a->connect_host, b->connect_host)) | ||
547 | return 0; | ||
548 | if (a->connect_port != b->connect_port) | ||
549 | return 0; | ||
550 | |||
551 | return 1; | ||
552 | } | ||
553 | |||
554 | static int | ||
555 | process_mux_open_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r) | ||
556 | { | ||
557 | Forward fwd; | ||
558 | char *fwd_desc = NULL; | ||
559 | u_int ftype; | ||
560 | int i, ret = 0, freefwd = 1; | ||
561 | |||
562 | fwd.listen_host = fwd.connect_host = NULL; | ||
563 | if (buffer_get_int_ret(&ftype, m) != 0 || | ||
564 | (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL || | ||
565 | buffer_get_int_ret(&fwd.listen_port, m) != 0 || | ||
566 | (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL || | ||
567 | buffer_get_int_ret(&fwd.connect_port, m) != 0) { | ||
568 | error("%s: malformed message", __func__); | ||
569 | ret = -1; | ||
570 | goto out; | ||
571 | } | ||
572 | |||
573 | if (*fwd.listen_host == '\0') { | ||
574 | xfree(fwd.listen_host); | ||
575 | fwd.listen_host = NULL; | ||
576 | } | ||
577 | if (*fwd.connect_host == '\0') { | ||
578 | xfree(fwd.connect_host); | ||
579 | fwd.connect_host = NULL; | ||
580 | } | ||
581 | |||
582 | debug2("%s: channel %d: request %s", __func__, c->self, | ||
583 | (fwd_desc = format_forward(ftype, &fwd))); | ||
584 | |||
585 | if (ftype != MUX_FWD_LOCAL && ftype != MUX_FWD_REMOTE && | ||
586 | ftype != MUX_FWD_DYNAMIC) { | ||
587 | logit("%s: invalid forwarding type %u", __func__, ftype); | ||
588 | invalid: | ||
589 | xfree(fwd.listen_host); | ||
590 | xfree(fwd.connect_host); | ||
591 | buffer_put_int(r, MUX_S_FAILURE); | ||
592 | buffer_put_int(r, rid); | ||
593 | buffer_put_cstring(r, "Invalid forwarding request"); | ||
594 | return 0; | ||
595 | } | ||
596 | /* XXX support rport0 forwarding with reply of port assigned */ | ||
597 | if (fwd.listen_port == 0 || fwd.listen_port >= 65536) { | ||
598 | logit("%s: invalid listen port %u", __func__, | ||
599 | fwd.listen_port); | ||
600 | goto invalid; | ||
601 | } | ||
602 | if (fwd.connect_port >= 65536 || (ftype != MUX_FWD_DYNAMIC && | ||
603 | ftype != MUX_FWD_REMOTE && fwd.connect_port == 0)) { | ||
604 | logit("%s: invalid connect port %u", __func__, | ||
605 | fwd.connect_port); | ||
606 | goto invalid; | ||
607 | } | ||
608 | if (ftype != MUX_FWD_DYNAMIC && fwd.connect_host == NULL) { | ||
609 | logit("%s: missing connect host", __func__); | ||
610 | goto invalid; | ||
611 | } | ||
612 | |||
613 | /* Skip forwards that have already been requested */ | ||
614 | switch (ftype) { | ||
615 | case MUX_FWD_LOCAL: | ||
616 | case MUX_FWD_DYNAMIC: | ||
617 | for (i = 0; i < options.num_local_forwards; i++) { | ||
618 | if (compare_forward(&fwd, | ||
619 | options.local_forwards + i)) { | ||
620 | exists: | ||
621 | debug2("%s: found existing forwarding", | ||
622 | __func__); | ||
623 | buffer_put_int(r, MUX_S_OK); | ||
624 | buffer_put_int(r, rid); | ||
625 | goto out; | ||
626 | } | ||
627 | } | ||
628 | break; | ||
629 | case MUX_FWD_REMOTE: | ||
630 | for (i = 0; i < options.num_remote_forwards; i++) { | ||
631 | if (compare_forward(&fwd, | ||
632 | options.remote_forwards + i)) | ||
633 | goto exists; | ||
634 | } | ||
635 | break; | ||
636 | } | ||
637 | |||
638 | if (options.control_master == SSHCTL_MASTER_ASK || | ||
639 | options.control_master == SSHCTL_MASTER_AUTO_ASK) { | ||
640 | if (!ask_permission("Open %s on %s?", fwd_desc, host)) { | ||
641 | debug2("%s: forwarding refused by user", __func__); | ||
642 | buffer_put_int(r, MUX_S_PERMISSION_DENIED); | ||
643 | buffer_put_int(r, rid); | ||
644 | buffer_put_cstring(r, "Permission denied"); | ||
645 | goto out; | ||
646 | } | ||
647 | } | ||
648 | |||
649 | if (ftype == MUX_FWD_LOCAL || ftype == MUX_FWD_DYNAMIC) { | ||
650 | if (options.num_local_forwards + 1 >= | ||
651 | SSH_MAX_FORWARDS_PER_DIRECTION || | ||
652 | channel_setup_local_fwd_listener(fwd.listen_host, | ||
653 | fwd.listen_port, fwd.connect_host, fwd.connect_port, | ||
654 | options.gateway_ports) < 0) { | ||
655 | fail: | ||
656 | logit("slave-requested %s failed", fwd_desc); | ||
657 | buffer_put_int(r, MUX_S_FAILURE); | ||
658 | buffer_put_int(r, rid); | ||
659 | buffer_put_cstring(r, "Port forwarding failed"); | ||
660 | goto out; | ||
661 | } | ||
662 | add_local_forward(&options, &fwd); | ||
663 | freefwd = 0; | ||
664 | } else { | ||
665 | /* XXX wait for remote to confirm */ | ||
666 | if (options.num_remote_forwards + 1 >= | ||
667 | SSH_MAX_FORWARDS_PER_DIRECTION || | ||
668 | channel_request_remote_forwarding(fwd.listen_host, | ||
669 | fwd.listen_port, fwd.connect_host, fwd.connect_port) < 0) | ||
670 | goto fail; | ||
671 | add_remote_forward(&options, &fwd); | ||
672 | freefwd = 0; | ||
673 | } | ||
674 | buffer_put_int(r, MUX_S_OK); | ||
675 | buffer_put_int(r, rid); | ||
676 | out: | ||
677 | if (fwd_desc != NULL) | ||
678 | xfree(fwd_desc); | ||
679 | if (freefwd) { | ||
680 | if (fwd.listen_host != NULL) | ||
681 | xfree(fwd.listen_host); | ||
682 | if (fwd.connect_host != NULL) | ||
683 | xfree(fwd.connect_host); | ||
684 | } | ||
685 | return ret; | ||
686 | } | ||
687 | |||
688 | static int | ||
689 | process_mux_close_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r) | ||
690 | { | ||
691 | Forward fwd; | ||
692 | char *fwd_desc = NULL; | ||
693 | u_int ftype; | ||
694 | int ret = 0; | ||
695 | |||
696 | fwd.listen_host = fwd.connect_host = NULL; | ||
697 | if (buffer_get_int_ret(&ftype, m) != 0 || | ||
698 | (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL || | ||
699 | buffer_get_int_ret(&fwd.listen_port, m) != 0 || | ||
700 | (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL || | ||
701 | buffer_get_int_ret(&fwd.connect_port, m) != 0) { | ||
702 | error("%s: malformed message", __func__); | ||
703 | ret = -1; | ||
704 | goto out; | ||
705 | } | ||
706 | |||
707 | if (*fwd.listen_host == '\0') { | ||
708 | xfree(fwd.listen_host); | ||
709 | fwd.listen_host = NULL; | ||
710 | } | ||
711 | if (*fwd.connect_host == '\0') { | ||
712 | xfree(fwd.connect_host); | ||
713 | fwd.connect_host = NULL; | ||
714 | } | ||
715 | |||
716 | debug2("%s: channel %d: request %s", __func__, c->self, | ||
717 | (fwd_desc = format_forward(ftype, &fwd))); | ||
718 | |||
719 | /* XXX implement this */ | ||
720 | buffer_put_int(r, MUX_S_FAILURE); | ||
721 | buffer_put_int(r, rid); | ||
722 | buffer_put_cstring(r, "unimplemented"); | ||
723 | |||
724 | out: | ||
725 | if (fwd_desc != NULL) | ||
726 | xfree(fwd_desc); | ||
727 | if (fwd.listen_host != NULL) | ||
728 | xfree(fwd.listen_host); | ||
729 | if (fwd.connect_host != NULL) | ||
730 | xfree(fwd.connect_host); | ||
731 | |||
732 | return ret; | ||
733 | } | ||
734 | |||
735 | static int | ||
736 | process_mux_stdio_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r) | ||
737 | { | ||
738 | Channel *nc; | ||
739 | char *reserved, *chost; | ||
740 | u_int cport, i, j; | ||
741 | int new_fd[2]; | ||
742 | |||
743 | chost = reserved = NULL; | ||
744 | if ((reserved = buffer_get_string_ret(m, NULL)) == NULL || | ||
745 | (chost = buffer_get_string_ret(m, NULL)) == NULL || | ||
746 | buffer_get_int_ret(&cport, m) != 0) { | ||
747 | if (reserved != NULL) | ||
748 | xfree(reserved); | ||
749 | if (chost != NULL) | ||
750 | xfree(chost); | ||
751 | error("%s: malformed message", __func__); | ||
752 | return -1; | ||
753 | } | ||
754 | xfree(reserved); | ||
755 | |||
756 | debug2("%s: channel %d: request stdio fwd to %s:%u", | ||
757 | __func__, c->self, chost, cport); | ||
758 | |||
759 | /* Gather fds from client */ | ||
760 | for(i = 0; i < 2; i++) { | ||
761 | if ((new_fd[i] = mm_receive_fd(c->sock)) == -1) { | ||
762 | error("%s: failed to receive fd %d from slave", | ||
763 | __func__, i); | ||
764 | for (j = 0; j < i; j++) | ||
765 | close(new_fd[j]); | ||
766 | xfree(chost); | ||
767 | |||
768 | /* prepare reply */ | ||
769 | buffer_put_int(r, MUX_S_FAILURE); | ||
770 | buffer_put_int(r, rid); | ||
771 | buffer_put_cstring(r, | ||
772 | "did not receive file descriptors"); | ||
773 | return -1; | ||
774 | } | ||
775 | } | ||
776 | |||
777 | debug3("%s: got fds stdin %d, stdout %d", __func__, | ||
778 | new_fd[0], new_fd[1]); | ||
779 | |||
780 | /* XXX support multiple child sessions in future */ | ||
781 | if (c->remote_id != -1) { | ||
782 | debug2("%s: session already open", __func__); | ||
783 | /* prepare reply */ | ||
784 | buffer_put_int(r, MUX_S_FAILURE); | ||
785 | buffer_put_int(r, rid); | ||
786 | buffer_put_cstring(r, "Multiple sessions not supported"); | ||
787 | cleanup: | ||
788 | close(new_fd[0]); | ||
789 | close(new_fd[1]); | ||
790 | xfree(chost); | ||
791 | return 0; | ||
792 | } | ||
793 | |||
794 | if (options.control_master == SSHCTL_MASTER_ASK || | ||
795 | options.control_master == SSHCTL_MASTER_AUTO_ASK) { | ||
796 | if (!ask_permission("Allow forward to to %s:%u? ", | ||
797 | chost, cport)) { | ||
798 | debug2("%s: stdio fwd refused by user", __func__); | ||
799 | /* prepare reply */ | ||
800 | buffer_put_int(r, MUX_S_PERMISSION_DENIED); | ||
801 | buffer_put_int(r, rid); | ||
802 | buffer_put_cstring(r, "Permission denied"); | ||
803 | goto cleanup; | ||
804 | } | ||
805 | } | ||
806 | |||
807 | /* enable nonblocking unless tty */ | ||
808 | if (!isatty(new_fd[0])) | ||
809 | set_nonblock(new_fd[0]); | ||
810 | if (!isatty(new_fd[1])) | ||
811 | set_nonblock(new_fd[1]); | ||
812 | |||
813 | nc = channel_connect_stdio_fwd(chost, cport, new_fd[0], new_fd[1]); | ||
814 | |||
815 | nc->ctl_chan = c->self; /* link session -> control channel */ | ||
816 | c->remote_id = nc->self; /* link control -> session channel */ | ||
817 | |||
818 | debug2("%s: channel_new: %d linked to control channel %d", | ||
819 | __func__, nc->self, nc->ctl_chan); | ||
116 | 820 | ||
117 | /* ** Multiplexing master support */ | 821 | channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0); |
822 | |||
823 | /* prepare reply */ | ||
824 | /* XXX defer until channel confirmed */ | ||
825 | buffer_put_int(r, MUX_S_SESSION_OPENED); | ||
826 | buffer_put_int(r, rid); | ||
827 | buffer_put_int(r, nc->self); | ||
828 | |||
829 | return 0; | ||
830 | } | ||
831 | |||
832 | /* Channel callbacks fired on read/write from mux slave fd */ | ||
833 | static int | ||
834 | mux_master_read_cb(Channel *c) | ||
835 | { | ||
836 | struct mux_master_state *state = (struct mux_master_state *)c->mux_ctx; | ||
837 | Buffer in, out; | ||
838 | void *ptr; | ||
839 | u_int type, rid, have, i; | ||
840 | int ret = -1; | ||
841 | |||
842 | /* Setup ctx and */ | ||
843 | if (c->mux_ctx == NULL) { | ||
844 | state = xcalloc(1, sizeof(state)); | ||
845 | c->mux_ctx = state; | ||
846 | channel_register_cleanup(c->self, | ||
847 | mux_master_control_cleanup_cb, 0); | ||
848 | |||
849 | /* Send hello */ | ||
850 | buffer_init(&out); | ||
851 | buffer_put_int(&out, MUX_MSG_HELLO); | ||
852 | buffer_put_int(&out, SSHMUX_VER); | ||
853 | /* no extensions */ | ||
854 | buffer_put_string(&c->output, buffer_ptr(&out), | ||
855 | buffer_len(&out)); | ||
856 | buffer_free(&out); | ||
857 | debug3("%s: channel %d: hello sent", __func__, c->self); | ||
858 | return 0; | ||
859 | } | ||
860 | |||
861 | buffer_init(&in); | ||
862 | buffer_init(&out); | ||
863 | |||
864 | /* Channel code ensures that we receive whole packets */ | ||
865 | if ((ptr = buffer_get_string_ptr_ret(&c->input, &have)) == NULL) { | ||
866 | malf: | ||
867 | error("%s: malformed message", __func__); | ||
868 | goto out; | ||
869 | } | ||
870 | buffer_append(&in, ptr, have); | ||
871 | |||
872 | if (buffer_get_int_ret(&type, &in) != 0) | ||
873 | goto malf; | ||
874 | debug3("%s: channel %d packet type 0x%08x len %u", | ||
875 | __func__, c->self, type, buffer_len(&in)); | ||
876 | |||
877 | if (type == MUX_MSG_HELLO) | ||
878 | rid = 0; | ||
879 | else { | ||
880 | if (!state->hello_rcvd) { | ||
881 | error("%s: expected MUX_MSG_HELLO(0x%08x), " | ||
882 | "received 0x%08x", __func__, MUX_MSG_HELLO, type); | ||
883 | goto out; | ||
884 | } | ||
885 | if (buffer_get_int_ret(&rid, &in) != 0) | ||
886 | goto malf; | ||
887 | } | ||
888 | |||
889 | for (i = 0; mux_master_handlers[i].handler != NULL; i++) { | ||
890 | if (type == mux_master_handlers[i].type) { | ||
891 | ret = mux_master_handlers[i].handler(rid, c, &in, &out); | ||
892 | break; | ||
893 | } | ||
894 | } | ||
895 | if (mux_master_handlers[i].handler == NULL) { | ||
896 | error("%s: unsupported mux message 0x%08x", __func__, type); | ||
897 | buffer_put_int(&out, MUX_S_FAILURE); | ||
898 | buffer_put_int(&out, rid); | ||
899 | buffer_put_cstring(&out, "unsupported request"); | ||
900 | ret = 0; | ||
901 | } | ||
902 | /* Enqueue reply packet */ | ||
903 | if (buffer_len(&out) != 0) { | ||
904 | buffer_put_string(&c->output, buffer_ptr(&out), | ||
905 | buffer_len(&out)); | ||
906 | } | ||
907 | out: | ||
908 | buffer_free(&in); | ||
909 | buffer_free(&out); | ||
910 | return ret; | ||
911 | } | ||
912 | |||
913 | void | ||
914 | mux_exit_message(Channel *c, int exitval) | ||
915 | { | ||
916 | Buffer m; | ||
917 | Channel *mux_chan; | ||
918 | |||
919 | debug3("%s: channel %d: exit message, evitval %d", __func__, c->self, | ||
920 | exitval); | ||
921 | |||
922 | if ((mux_chan = channel_by_id(c->ctl_chan)) == NULL) | ||
923 | fatal("%s: channel %d missing mux channel %d", | ||
924 | __func__, c->self, c->ctl_chan); | ||
925 | |||
926 | /* Append exit message packet to control socket output queue */ | ||
927 | buffer_init(&m); | ||
928 | buffer_put_int(&m, MUX_S_EXIT_MESSAGE); | ||
929 | buffer_put_int(&m, c->self); | ||
930 | buffer_put_int(&m, exitval); | ||
931 | |||
932 | buffer_put_string(&mux_chan->output, buffer_ptr(&m), buffer_len(&m)); | ||
933 | buffer_free(&m); | ||
934 | } | ||
118 | 935 | ||
119 | /* Prepare a mux master to listen on a Unix domain socket. */ | 936 | /* Prepare a mux master to listen on a Unix domain socket. */ |
120 | void | 937 | void |
121 | muxserver_listen(void) | 938 | muxserver_listen(void) |
122 | { | 939 | { |
123 | struct sockaddr_un addr; | 940 | struct sockaddr_un addr; |
941 | socklen_t sun_len; | ||
124 | mode_t old_umask; | 942 | mode_t old_umask; |
125 | int addr_len; | ||
126 | 943 | ||
127 | if (options.control_path == NULL || | 944 | if (options.control_path == NULL || |
128 | options.control_master == SSHCTL_MASTER_NO) | 945 | options.control_master == SSHCTL_MASTER_NO) |
@@ -132,7 +949,7 @@ muxserver_listen(void) | |||
132 | 949 | ||
133 | memset(&addr, '\0', sizeof(addr)); | 950 | memset(&addr, '\0', sizeof(addr)); |
134 | addr.sun_family = AF_UNIX; | 951 | addr.sun_family = AF_UNIX; |
135 | addr_len = offsetof(struct sockaddr_un, sun_path) + | 952 | sun_len = offsetof(struct sockaddr_un, sun_path) + |
136 | strlen(options.control_path) + 1; | 953 | strlen(options.control_path) + 1; |
137 | 954 | ||
138 | if (strlcpy(addr.sun_path, options.control_path, | 955 | if (strlcpy(addr.sun_path, options.control_path, |
@@ -143,7 +960,7 @@ muxserver_listen(void) | |||
143 | fatal("%s socket(): %s", __func__, strerror(errno)); | 960 | fatal("%s socket(): %s", __func__, strerror(errno)); |
144 | 961 | ||
145 | old_umask = umask(0177); | 962 | old_umask = umask(0177); |
146 | if (bind(muxserver_sock, (struct sockaddr *)&addr, addr_len) == -1) { | 963 | if (bind(muxserver_sock, (struct sockaddr *)&addr, sun_len) == -1) { |
147 | muxserver_sock = -1; | 964 | muxserver_sock = -1; |
148 | if (errno == EINVAL || errno == EADDRINUSE) { | 965 | if (errno == EINVAL || errno == EADDRINUSE) { |
149 | error("ControlSocket %s already exists, " | 966 | error("ControlSocket %s already exists, " |
@@ -163,6 +980,14 @@ muxserver_listen(void) | |||
163 | fatal("%s listen(): %s", __func__, strerror(errno)); | 980 | fatal("%s listen(): %s", __func__, strerror(errno)); |
164 | 981 | ||
165 | set_nonblock(muxserver_sock); | 982 | set_nonblock(muxserver_sock); |
983 | |||
984 | mux_listener_channel = channel_new("mux listener", | ||
985 | SSH_CHANNEL_MUX_LISTENER, muxserver_sock, muxserver_sock, -1, | ||
986 | CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, | ||
987 | 0, addr.sun_path, 1); | ||
988 | mux_listener_channel->mux_rcb = mux_master_read_cb; | ||
989 | debug3("%s: mux listener channel %d fd %d", __func__, | ||
990 | mux_listener_channel->self, mux_listener_channel->sock); | ||
166 | } | 991 | } |
167 | 992 | ||
168 | /* Callback on open confirmation in mux master for a mux client session. */ | 993 | /* Callback on open confirmation in mux master for a mux client session. */ |
@@ -176,7 +1001,7 @@ mux_session_confirm(int id, void *arg) | |||
176 | 1001 | ||
177 | if (cctx == NULL) | 1002 | if (cctx == NULL) |
178 | fatal("%s: cctx == NULL", __func__); | 1003 | fatal("%s: cctx == NULL", __func__); |
179 | if ((c = channel_lookup(id)) == NULL) | 1004 | if ((c = channel_by_id(id)) == NULL) |
180 | fatal("%s: no channel for id %d", __func__, id); | 1005 | fatal("%s: no channel for id %d", __func__, id); |
181 | 1006 | ||
182 | display = getenv("DISPLAY"); | 1007 | display = getenv("DISPLAY"); |
@@ -211,291 +1036,616 @@ mux_session_confirm(int id, void *arg) | |||
211 | xfree(cctx); | 1036 | xfree(cctx); |
212 | } | 1037 | } |
213 | 1038 | ||
1039 | /* ** Multiplexing client support */ | ||
1040 | |||
1041 | /* Exit signal handler */ | ||
1042 | static void | ||
1043 | control_client_sighandler(int signo) | ||
1044 | { | ||
1045 | muxclient_terminate = signo; | ||
1046 | } | ||
1047 | |||
214 | /* | 1048 | /* |
215 | * Accept a connection on the mux master socket and process the | 1049 | * Relay signal handler - used to pass some signals from mux client to |
216 | * client's request. Returns flag indicating whether mux master should | 1050 | * mux master. |
217 | * begin graceful close. | ||
218 | */ | 1051 | */ |
219 | int | 1052 | static void |
220 | muxserver_accept_control(void) | 1053 | control_client_sigrelay(int signo) |
221 | { | 1054 | { |
222 | Buffer m; | 1055 | int save_errno = errno; |
223 | Channel *c; | ||
224 | int client_fd, new_fd[3], ver, allowed, window, packetmax; | ||
225 | socklen_t addrlen; | ||
226 | struct sockaddr_storage addr; | ||
227 | struct mux_session_confirm_ctx *cctx; | ||
228 | char *cmd; | ||
229 | u_int i, j, len, env_len, mux_command, flags, escape_char; | ||
230 | uid_t euid; | ||
231 | gid_t egid; | ||
232 | int start_close = 0; | ||
233 | 1056 | ||
234 | /* | 1057 | if (muxserver_pid > 1) |
235 | * Accept connection on control socket | 1058 | kill(muxserver_pid, signo); |
236 | */ | 1059 | |
237 | memset(&addr, 0, sizeof(addr)); | 1060 | errno = save_errno; |
238 | addrlen = sizeof(addr); | 1061 | } |
239 | if ((client_fd = accept(muxserver_sock, | 1062 | |
240 | (struct sockaddr*)&addr, &addrlen)) == -1) { | 1063 | static int |
241 | error("%s accept: %s", __func__, strerror(errno)); | 1064 | mux_client_read(int fd, Buffer *b, u_int need) |
242 | return 0; | 1065 | { |
1066 | u_int have; | ||
1067 | ssize_t len; | ||
1068 | u_char *p; | ||
1069 | struct pollfd pfd; | ||
1070 | |||
1071 | pfd.fd = fd; | ||
1072 | pfd.events = POLLIN; | ||
1073 | p = buffer_append_space(b, need); | ||
1074 | for (have = 0; have < need; ) { | ||
1075 | if (muxclient_terminate) { | ||
1076 | errno = EINTR; | ||
1077 | return -1; | ||
1078 | } | ||
1079 | len = read(fd, p + have, need - have); | ||
1080 | if (len < 0) { | ||
1081 | switch (errno) { | ||
1082 | #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN) | ||
1083 | case EWOULDBLOCK: | ||
1084 | #endif | ||
1085 | case EAGAIN: | ||
1086 | (void)poll(&pfd, 1, -1); | ||
1087 | /* FALLTHROUGH */ | ||
1088 | case EINTR: | ||
1089 | continue; | ||
1090 | default: | ||
1091 | return -1; | ||
1092 | } | ||
1093 | } | ||
1094 | if (len == 0) { | ||
1095 | errno = EPIPE; | ||
1096 | return -1; | ||
1097 | } | ||
1098 | have += (u_int)len; | ||
243 | } | 1099 | } |
1100 | return 0; | ||
1101 | } | ||
244 | 1102 | ||
245 | if (getpeereid(client_fd, &euid, &egid) < 0) { | 1103 | static int |
246 | error("%s getpeereid failed: %s", __func__, strerror(errno)); | 1104 | mux_client_write_packet(int fd, Buffer *m) |
247 | close(client_fd); | 1105 | { |
248 | return 0; | 1106 | Buffer queue; |
1107 | u_int have, need; | ||
1108 | int oerrno, len; | ||
1109 | u_char *ptr; | ||
1110 | struct pollfd pfd; | ||
1111 | |||
1112 | pfd.fd = fd; | ||
1113 | pfd.events = POLLOUT; | ||
1114 | buffer_init(&queue); | ||
1115 | buffer_put_string(&queue, buffer_ptr(m), buffer_len(m)); | ||
1116 | |||
1117 | need = buffer_len(&queue); | ||
1118 | ptr = buffer_ptr(&queue); | ||
1119 | |||
1120 | for (have = 0; have < need; ) { | ||
1121 | if (muxclient_terminate) { | ||
1122 | buffer_free(&queue); | ||
1123 | errno = EINTR; | ||
1124 | return -1; | ||
1125 | } | ||
1126 | len = write(fd, ptr + have, need - have); | ||
1127 | if (len < 0) { | ||
1128 | switch (errno) { | ||
1129 | #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN) | ||
1130 | case EWOULDBLOCK: | ||
1131 | #endif | ||
1132 | case EAGAIN: | ||
1133 | (void)poll(&pfd, 1, -1); | ||
1134 | /* FALLTHROUGH */ | ||
1135 | case EINTR: | ||
1136 | continue; | ||
1137 | default: | ||
1138 | oerrno = errno; | ||
1139 | buffer_free(&queue); | ||
1140 | errno = oerrno; | ||
1141 | return -1; | ||
1142 | } | ||
1143 | } | ||
1144 | if (len == 0) { | ||
1145 | buffer_free(&queue); | ||
1146 | errno = EPIPE; | ||
1147 | return -1; | ||
1148 | } | ||
1149 | have += (u_int)len; | ||
249 | } | 1150 | } |
250 | if ((euid != 0) && (getuid() != euid)) { | 1151 | buffer_free(&queue); |
251 | error("control mode uid mismatch: peer euid %u != uid %u", | 1152 | return 0; |
252 | (u_int) euid, (u_int) getuid()); | 1153 | } |
253 | close(client_fd); | 1154 | |
254 | return 0; | 1155 | static int |
1156 | mux_client_read_packet(int fd, Buffer *m) | ||
1157 | { | ||
1158 | Buffer queue; | ||
1159 | u_int need, have; | ||
1160 | void *ptr; | ||
1161 | int oerrno; | ||
1162 | |||
1163 | buffer_init(&queue); | ||
1164 | if (mux_client_read(fd, &queue, 4) != 0) { | ||
1165 | if ((oerrno = errno) == EPIPE) | ||
1166 | debug3("%s: read header failed: %s", __func__, strerror(errno)); | ||
1167 | errno = oerrno; | ||
1168 | return -1; | ||
255 | } | 1169 | } |
1170 | need = get_u32(buffer_ptr(&queue)); | ||
1171 | if (mux_client_read(fd, &queue, need) != 0) { | ||
1172 | oerrno = errno; | ||
1173 | debug3("%s: read body failed: %s", __func__, strerror(errno)); | ||
1174 | errno = oerrno; | ||
1175 | return -1; | ||
1176 | } | ||
1177 | ptr = buffer_get_string_ptr(&queue, &have); | ||
1178 | buffer_append(m, ptr, have); | ||
1179 | buffer_free(&queue); | ||
1180 | return 0; | ||
1181 | } | ||
256 | 1182 | ||
257 | /* XXX handle asynchronously */ | 1183 | static int |
258 | unset_nonblock(client_fd); | 1184 | mux_client_hello_exchange(int fd) |
1185 | { | ||
1186 | Buffer m; | ||
1187 | u_int type, ver; | ||
259 | 1188 | ||
260 | /* Read command */ | ||
261 | buffer_init(&m); | 1189 | buffer_init(&m); |
262 | if (ssh_msg_recv(client_fd, &m) == -1) { | 1190 | buffer_put_int(&m, MUX_MSG_HELLO); |
263 | error("%s: client msg_recv failed", __func__); | 1191 | buffer_put_int(&m, SSHMUX_VER); |
264 | close(client_fd); | 1192 | /* no extensions */ |
1193 | |||
1194 | if (mux_client_write_packet(fd, &m) != 0) | ||
1195 | fatal("%s: write packet: %s", __func__, strerror(errno)); | ||
1196 | |||
1197 | buffer_clear(&m); | ||
1198 | |||
1199 | /* Read their HELLO */ | ||
1200 | if (mux_client_read_packet(fd, &m) != 0) { | ||
265 | buffer_free(&m); | 1201 | buffer_free(&m); |
266 | return 0; | 1202 | return -1; |
267 | } | 1203 | } |
268 | if ((ver = buffer_get_char(&m)) != SSHMUX_VER) { | 1204 | |
269 | error("%s: wrong client version %d", __func__, ver); | 1205 | type = buffer_get_int(&m); |
1206 | if (type != MUX_MSG_HELLO) | ||
1207 | fatal("%s: expected HELLO (%u) received %u", | ||
1208 | __func__, MUX_MSG_HELLO, type); | ||
1209 | ver = buffer_get_int(&m); | ||
1210 | if (ver != SSHMUX_VER) | ||
1211 | fatal("Unsupported multiplexing protocol version %d " | ||
1212 | "(expected %d)", ver, SSHMUX_VER); | ||
1213 | debug2("%s: master version %u", __func__, ver); | ||
1214 | /* No extensions are presently defined */ | ||
1215 | while (buffer_len(&m) > 0) { | ||
1216 | char *name = buffer_get_string(&m, NULL); | ||
1217 | char *value = buffer_get_string(&m, NULL); | ||
1218 | |||
1219 | debug2("Unrecognised master extension \"%s\"", name); | ||
1220 | xfree(name); | ||
1221 | xfree(value); | ||
1222 | } | ||
1223 | buffer_free(&m); | ||
1224 | return 0; | ||
1225 | } | ||
1226 | |||
1227 | static u_int | ||
1228 | mux_client_request_alive(int fd) | ||
1229 | { | ||
1230 | Buffer m; | ||
1231 | char *e; | ||
1232 | u_int pid, type, rid; | ||
1233 | |||
1234 | debug3("%s: entering", __func__); | ||
1235 | |||
1236 | buffer_init(&m); | ||
1237 | buffer_put_int(&m, MUX_C_ALIVE_CHECK); | ||
1238 | buffer_put_int(&m, muxclient_request_id); | ||
1239 | |||
1240 | if (mux_client_write_packet(fd, &m) != 0) | ||
1241 | fatal("%s: write packet: %s", __func__, strerror(errno)); | ||
1242 | |||
1243 | buffer_clear(&m); | ||
1244 | |||
1245 | /* Read their reply */ | ||
1246 | if (mux_client_read_packet(fd, &m) != 0) { | ||
270 | buffer_free(&m); | 1247 | buffer_free(&m); |
271 | close(client_fd); | ||
272 | return 0; | 1248 | return 0; |
273 | } | 1249 | } |
274 | 1250 | ||
275 | allowed = 1; | 1251 | type = buffer_get_int(&m); |
276 | mux_command = buffer_get_int(&m); | 1252 | if (type != MUX_S_ALIVE) { |
277 | flags = buffer_get_int(&m); | 1253 | e = buffer_get_string(&m, NULL); |
1254 | fatal("%s: master returned error: %s", __func__, e); | ||
1255 | } | ||
1256 | |||
1257 | if ((rid = buffer_get_int(&m)) != muxclient_request_id) | ||
1258 | fatal("%s: out of sequence reply: my id %u theirs %u", | ||
1259 | __func__, muxclient_request_id, rid); | ||
1260 | pid = buffer_get_int(&m); | ||
1261 | buffer_free(&m); | ||
1262 | |||
1263 | debug3("%s: done pid = %u", __func__, pid); | ||
1264 | |||
1265 | muxclient_request_id++; | ||
1266 | |||
1267 | return pid; | ||
1268 | } | ||
1269 | |||
1270 | static void | ||
1271 | mux_client_request_terminate(int fd) | ||
1272 | { | ||
1273 | Buffer m; | ||
1274 | char *e; | ||
1275 | u_int type, rid; | ||
1276 | |||
1277 | debug3("%s: entering", __func__); | ||
1278 | |||
1279 | buffer_init(&m); | ||
1280 | buffer_put_int(&m, MUX_C_TERMINATE); | ||
1281 | buffer_put_int(&m, muxclient_request_id); | ||
1282 | |||
1283 | if (mux_client_write_packet(fd, &m) != 0) | ||
1284 | fatal("%s: write packet: %s", __func__, strerror(errno)); | ||
278 | 1285 | ||
279 | buffer_clear(&m); | 1286 | buffer_clear(&m); |
280 | 1287 | ||
281 | switch (mux_command) { | 1288 | /* Read their reply */ |
282 | case SSHMUX_COMMAND_OPEN: | 1289 | if (mux_client_read_packet(fd, &m) != 0) { |
283 | if (options.control_master == SSHCTL_MASTER_ASK || | 1290 | /* Remote end exited already */ |
284 | options.control_master == SSHCTL_MASTER_AUTO_ASK) | 1291 | if (errno == EPIPE) { |
285 | allowed = ask_permission("Allow shared connection " | ||
286 | "to %s? ", host); | ||
287 | /* continue below */ | ||
288 | break; | ||
289 | case SSHMUX_COMMAND_TERMINATE: | ||
290 | if (options.control_master == SSHCTL_MASTER_ASK || | ||
291 | options.control_master == SSHCTL_MASTER_AUTO_ASK) | ||
292 | allowed = ask_permission("Terminate shared connection " | ||
293 | "to %s? ", host); | ||
294 | if (allowed) | ||
295 | start_close = 1; | ||
296 | /* FALLTHROUGH */ | ||
297 | case SSHMUX_COMMAND_ALIVE_CHECK: | ||
298 | /* Reply for SSHMUX_COMMAND_TERMINATE and ALIVE_CHECK */ | ||
299 | buffer_clear(&m); | ||
300 | buffer_put_int(&m, allowed); | ||
301 | buffer_put_int(&m, getpid()); | ||
302 | if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) { | ||
303 | error("%s: client msg_send failed", __func__); | ||
304 | close(client_fd); | ||
305 | buffer_free(&m); | 1292 | buffer_free(&m); |
306 | return start_close; | 1293 | return; |
307 | } | 1294 | } |
308 | buffer_free(&m); | 1295 | fatal("%s: read from master failed: %s", |
309 | close(client_fd); | 1296 | __func__, strerror(errno)); |
310 | return start_close; | 1297 | } |
1298 | |||
1299 | type = buffer_get_int(&m); | ||
1300 | if ((rid = buffer_get_int(&m)) != muxclient_request_id) | ||
1301 | fatal("%s: out of sequence reply: my id %u theirs %u", | ||
1302 | __func__, muxclient_request_id, rid); | ||
1303 | switch (type) { | ||
1304 | case MUX_S_OK: | ||
1305 | break; | ||
1306 | case MUX_S_PERMISSION_DENIED: | ||
1307 | e = buffer_get_string(&m, NULL); | ||
1308 | fatal("Master refused termination request: %s", e); | ||
1309 | case MUX_S_FAILURE: | ||
1310 | e = buffer_get_string(&m, NULL); | ||
1311 | fatal("%s: termination request failed: %s", __func__, e); | ||
311 | default: | 1312 | default: |
312 | error("Unsupported command %d", mux_command); | 1313 | fatal("%s: unexpected response from master 0x%08x", |
313 | buffer_free(&m); | 1314 | __func__, type); |
314 | close(client_fd); | ||
315 | return 0; | ||
316 | } | 1315 | } |
1316 | buffer_free(&m); | ||
1317 | muxclient_request_id++; | ||
1318 | } | ||
1319 | |||
1320 | static int | ||
1321 | mux_client_request_forward(int fd, u_int ftype, Forward *fwd) | ||
1322 | { | ||
1323 | Buffer m; | ||
1324 | char *e, *fwd_desc; | ||
1325 | u_int type, rid; | ||
1326 | |||
1327 | fwd_desc = format_forward(ftype, fwd); | ||
1328 | debug("Requesting %s", fwd_desc); | ||
1329 | xfree(fwd_desc); | ||
1330 | |||
1331 | buffer_init(&m); | ||
1332 | buffer_put_int(&m, MUX_C_OPEN_FWD); | ||
1333 | buffer_put_int(&m, muxclient_request_id); | ||
1334 | buffer_put_int(&m, ftype); | ||
1335 | buffer_put_cstring(&m, | ||
1336 | fwd->listen_host == NULL ? "" : fwd->listen_host); | ||
1337 | buffer_put_int(&m, fwd->listen_port); | ||
1338 | buffer_put_cstring(&m, | ||
1339 | fwd->connect_host == NULL ? "" : fwd->connect_host); | ||
1340 | buffer_put_int(&m, fwd->connect_port); | ||
1341 | |||
1342 | if (mux_client_write_packet(fd, &m) != 0) | ||
1343 | fatal("%s: write packet: %s", __func__, strerror(errno)); | ||
317 | 1344 | ||
318 | /* Reply for SSHMUX_COMMAND_OPEN */ | ||
319 | buffer_clear(&m); | 1345 | buffer_clear(&m); |
320 | buffer_put_int(&m, allowed); | 1346 | |
321 | buffer_put_int(&m, getpid()); | 1347 | /* Read their reply */ |
322 | if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) { | 1348 | if (mux_client_read_packet(fd, &m) != 0) { |
323 | error("%s: client msg_send failed", __func__); | ||
324 | close(client_fd); | ||
325 | buffer_free(&m); | 1349 | buffer_free(&m); |
326 | return 0; | 1350 | return -1; |
327 | } | 1351 | } |
328 | 1352 | ||
329 | if (!allowed) { | 1353 | type = buffer_get_int(&m); |
330 | error("Refused control connection"); | 1354 | if ((rid = buffer_get_int(&m)) != muxclient_request_id) |
331 | close(client_fd); | 1355 | fatal("%s: out of sequence reply: my id %u theirs %u", |
1356 | __func__, muxclient_request_id, rid); | ||
1357 | switch (type) { | ||
1358 | case MUX_S_OK: | ||
1359 | break; | ||
1360 | case MUX_S_PERMISSION_DENIED: | ||
1361 | e = buffer_get_string(&m, NULL); | ||
332 | buffer_free(&m); | 1362 | buffer_free(&m); |
333 | return 0; | 1363 | error("Master refused forwarding request: %s", e); |
1364 | return -1; | ||
1365 | case MUX_S_FAILURE: | ||
1366 | e = buffer_get_string(&m, NULL); | ||
1367 | buffer_free(&m); | ||
1368 | error("%s: session request failed: %s", __func__, e); | ||
1369 | return -1; | ||
1370 | default: | ||
1371 | fatal("%s: unexpected response from master 0x%08x", | ||
1372 | __func__, type); | ||
334 | } | 1373 | } |
1374 | buffer_free(&m); | ||
335 | 1375 | ||
336 | buffer_clear(&m); | 1376 | muxclient_request_id++; |
337 | if (ssh_msg_recv(client_fd, &m) == -1) { | 1377 | return 0; |
338 | error("%s: client msg_recv failed", __func__); | 1378 | } |
339 | close(client_fd); | 1379 | |
340 | buffer_free(&m); | 1380 | static int |
341 | return 0; | 1381 | mux_client_request_forwards(int fd) |
1382 | { | ||
1383 | int i; | ||
1384 | |||
1385 | debug3("%s: requesting forwardings: %d local, %d remote", __func__, | ||
1386 | options.num_local_forwards, options.num_remote_forwards); | ||
1387 | |||
1388 | /* XXX ExitOnForwardingFailure */ | ||
1389 | for (i = 0; i < options.num_local_forwards; i++) { | ||
1390 | if (mux_client_request_forward(fd, | ||
1391 | options.local_forwards[i].connect_port == 0 ? | ||
1392 | MUX_FWD_DYNAMIC : MUX_FWD_LOCAL, | ||
1393 | options.local_forwards + i) != 0) | ||
1394 | return -1; | ||
342 | } | 1395 | } |
343 | if ((ver = buffer_get_char(&m)) != SSHMUX_VER) { | 1396 | for (i = 0; i < options.num_remote_forwards; i++) { |
344 | error("%s: wrong client version %d", __func__, ver); | 1397 | if (mux_client_request_forward(fd, MUX_FWD_REMOTE, |
345 | buffer_free(&m); | 1398 | options.remote_forwards + i) != 0) |
346 | close(client_fd); | 1399 | return -1; |
347 | return 0; | ||
348 | } | 1400 | } |
1401 | return 0; | ||
1402 | } | ||
349 | 1403 | ||
350 | cctx = xcalloc(1, sizeof(*cctx)); | 1404 | static int |
351 | cctx->want_tty = (flags & SSHMUX_FLAG_TTY) != 0; | 1405 | mux_client_request_session(int fd) |
352 | cctx->want_subsys = (flags & SSHMUX_FLAG_SUBSYS) != 0; | 1406 | { |
353 | cctx->want_x_fwd = (flags & SSHMUX_FLAG_X11_FWD) != 0; | 1407 | Buffer m; |
354 | cctx->want_agent_fwd = (flags & SSHMUX_FLAG_AGENT_FWD) != 0; | 1408 | char *e, *term; |
355 | cctx->term = buffer_get_string(&m, &len); | 1409 | u_int i, rid, sid, esid, exitval, type, exitval_seen; |
356 | escape_char = buffer_get_int(&m); | 1410 | extern char **environ; |
357 | 1411 | int devnull; | |
358 | cmd = buffer_get_string(&m, &len); | ||
359 | buffer_init(&cctx->cmd); | ||
360 | buffer_append(&cctx->cmd, cmd, strlen(cmd)); | ||
361 | 1412 | ||
362 | env_len = buffer_get_int(&m); | 1413 | debug3("%s: entering", __func__); |
363 | env_len = MIN(env_len, 4096); | 1414 | |
364 | debug3("%s: receiving %d env vars", __func__, env_len); | 1415 | if ((muxserver_pid = mux_client_request_alive(fd)) == 0) { |
365 | if (env_len != 0) { | 1416 | error("%s: master alive request failed", __func__); |
366 | cctx->env = xcalloc(env_len + 1, sizeof(*cctx->env)); | 1417 | return -1; |
367 | for (i = 0; i < env_len; i++) | ||
368 | cctx->env[i] = buffer_get_string(&m, &len); | ||
369 | cctx->env[i] = NULL; | ||
370 | } | 1418 | } |
371 | 1419 | ||
372 | debug2("%s: accepted tty %d, subsys %d, cmd %s", __func__, | 1420 | signal(SIGPIPE, SIG_IGN); |
373 | cctx->want_tty, cctx->want_subsys, cmd); | ||
374 | xfree(cmd); | ||
375 | 1421 | ||
376 | /* Gather fds from client */ | 1422 | if (stdin_null_flag) { |
377 | for(i = 0; i < 3; i++) { | 1423 | if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1) |
378 | if ((new_fd[i] = mm_receive_fd(client_fd)) == -1) { | 1424 | fatal("open(/dev/null): %s", strerror(errno)); |
379 | error("%s: failed to receive fd %d from slave", | 1425 | if (dup2(devnull, STDIN_FILENO) == -1) |
380 | __func__, i); | 1426 | fatal("dup2: %s", strerror(errno)); |
381 | for (j = 0; j < i; j++) | 1427 | if (devnull > STDERR_FILENO) |
382 | close(new_fd[j]); | 1428 | close(devnull); |
383 | for (j = 0; j < env_len; j++) | 1429 | } |
384 | xfree(cctx->env[j]); | 1430 | |
385 | if (env_len > 0) | 1431 | term = getenv("TERM"); |
386 | xfree(cctx->env); | 1432 | |
387 | xfree(cctx->term); | 1433 | buffer_init(&m); |
388 | buffer_free(&cctx->cmd); | 1434 | buffer_put_int(&m, MUX_C_NEW_SESSION); |
389 | close(client_fd); | 1435 | buffer_put_int(&m, muxclient_request_id); |
390 | xfree(cctx); | 1436 | buffer_put_cstring(&m, ""); /* reserved */ |
391 | return 0; | 1437 | buffer_put_int(&m, tty_flag); |
1438 | buffer_put_int(&m, options.forward_x11); | ||
1439 | buffer_put_int(&m, options.forward_agent); | ||
1440 | buffer_put_int(&m, subsystem_flag); | ||
1441 | buffer_put_int(&m, options.escape_char == SSH_ESCAPECHAR_NONE ? | ||
1442 | 0xffffffff : (u_int)options.escape_char); | ||
1443 | buffer_put_cstring(&m, term == NULL ? "" : term); | ||
1444 | buffer_put_string(&m, buffer_ptr(&command), buffer_len(&command)); | ||
1445 | |||
1446 | if (options.num_send_env > 0 && environ != NULL) { | ||
1447 | /* Pass environment */ | ||
1448 | for (i = 0; environ[i] != NULL; i++) { | ||
1449 | if (env_permitted(environ[i])) { | ||
1450 | buffer_put_cstring(&m, environ[i]); | ||
1451 | } | ||
392 | } | 1452 | } |
393 | } | 1453 | } |
394 | 1454 | ||
395 | debug2("%s: got fds stdin %d, stdout %d, stderr %d", __func__, | 1455 | if (mux_client_write_packet(fd, &m) != 0) |
396 | new_fd[0], new_fd[1], new_fd[2]); | 1456 | fatal("%s: write packet: %s", __func__, strerror(errno)); |
397 | 1457 | ||
398 | /* Try to pick up ttymodes from client before it goes raw */ | 1458 | /* Send the stdio file descriptors */ |
399 | if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1) | 1459 | if (mm_send_fd(fd, STDIN_FILENO) == -1 || |
400 | error("%s: tcgetattr: %s", __func__, strerror(errno)); | 1460 | mm_send_fd(fd, STDOUT_FILENO) == -1 || |
1461 | mm_send_fd(fd, STDERR_FILENO) == -1) | ||
1462 | fatal("%s: send fds failed", __func__); | ||
401 | 1463 | ||
402 | /* This roundtrip is just for synchronisation of ttymodes */ | 1464 | debug3("%s: session request sent", __func__); |
1465 | |||
1466 | /* Read their reply */ | ||
403 | buffer_clear(&m); | 1467 | buffer_clear(&m); |
404 | if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) { | 1468 | if (mux_client_read_packet(fd, &m) != 0) { |
405 | error("%s: client msg_send failed", __func__); | 1469 | error("%s: read from master failed: %s", |
406 | close(client_fd); | 1470 | __func__, strerror(errno)); |
407 | close(new_fd[0]); | ||
408 | close(new_fd[1]); | ||
409 | close(new_fd[2]); | ||
410 | buffer_free(&m); | 1471 | buffer_free(&m); |
411 | xfree(cctx->term); | 1472 | return -1; |
412 | if (env_len != 0) { | ||
413 | for (i = 0; i < env_len; i++) | ||
414 | xfree(cctx->env[i]); | ||
415 | xfree(cctx->env); | ||
416 | } | ||
417 | return 0; | ||
418 | } | 1473 | } |
419 | buffer_free(&m); | ||
420 | 1474 | ||
421 | /* enable nonblocking unless tty */ | 1475 | type = buffer_get_int(&m); |
422 | if (!isatty(new_fd[0])) | 1476 | if ((rid = buffer_get_int(&m)) != muxclient_request_id) |
423 | set_nonblock(new_fd[0]); | 1477 | fatal("%s: out of sequence reply: my id %u theirs %u", |
424 | if (!isatty(new_fd[1])) | 1478 | __func__, muxclient_request_id, rid); |
425 | set_nonblock(new_fd[1]); | 1479 | switch (type) { |
426 | if (!isatty(new_fd[2])) | 1480 | case MUX_S_SESSION_OPENED: |
427 | set_nonblock(new_fd[2]); | 1481 | sid = buffer_get_int(&m); |
1482 | debug("%s: master session id: %u", __func__, sid); | ||
1483 | break; | ||
1484 | case MUX_S_PERMISSION_DENIED: | ||
1485 | e = buffer_get_string(&m, NULL); | ||
1486 | buffer_free(&m); | ||
1487 | error("Master refused forwarding request: %s", e); | ||
1488 | return -1; | ||
1489 | case MUX_S_FAILURE: | ||
1490 | e = buffer_get_string(&m, NULL); | ||
1491 | buffer_free(&m); | ||
1492 | error("%s: forwarding request failed: %s", __func__, e); | ||
1493 | return -1; | ||
1494 | default: | ||
1495 | buffer_free(&m); | ||
1496 | error("%s: unexpected response from master 0x%08x", | ||
1497 | __func__, type); | ||
1498 | return -1; | ||
1499 | } | ||
1500 | muxclient_request_id++; | ||
428 | 1501 | ||
429 | set_nonblock(client_fd); | 1502 | signal(SIGHUP, control_client_sighandler); |
1503 | signal(SIGINT, control_client_sighandler); | ||
1504 | signal(SIGTERM, control_client_sighandler); | ||
1505 | signal(SIGWINCH, control_client_sigrelay); | ||
430 | 1506 | ||
431 | window = CHAN_SES_WINDOW_DEFAULT; | 1507 | if (tty_flag) |
432 | packetmax = CHAN_SES_PACKET_DEFAULT; | 1508 | enter_raw_mode(force_tty_flag); |
433 | if (cctx->want_tty) { | ||
434 | window >>= 1; | ||
435 | packetmax >>= 1; | ||
436 | } | ||
437 | |||
438 | c = channel_new("session", SSH_CHANNEL_OPENING, | ||
439 | new_fd[0], new_fd[1], new_fd[2], window, packetmax, | ||
440 | CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0); | ||
441 | 1509 | ||
442 | c->ctl_fd = client_fd; | 1510 | /* |
443 | if (cctx->want_tty && escape_char != 0xffffffff) { | 1511 | * Stick around until the controlee closes the client_fd. |
444 | channel_register_filter(c->self, | 1512 | * Before it does, it is expected to write an exit message. |
445 | client_simple_escape_filter, NULL, | 1513 | * This process must read the value and wait for the closure of |
446 | client_filter_cleanup, | 1514 | * the client_fd; if this one closes early, the multiplex master will |
447 | client_new_escape_filter_ctx((int)escape_char)); | 1515 | * terminate early too (possibly losing data). |
1516 | */ | ||
1517 | for (exitval = 255, exitval_seen = 0;;) { | ||
1518 | buffer_clear(&m); | ||
1519 | if (mux_client_read_packet(fd, &m) != 0) | ||
1520 | break; | ||
1521 | type = buffer_get_int(&m); | ||
1522 | if (type != MUX_S_EXIT_MESSAGE) { | ||
1523 | e = buffer_get_string(&m, NULL); | ||
1524 | fatal("%s: master returned error: %s", __func__, e); | ||
1525 | } | ||
1526 | if ((esid = buffer_get_int(&m)) != sid) | ||
1527 | fatal("%s: exit on unknown session: my id %u theirs %u", | ||
1528 | __func__, sid, esid); | ||
1529 | debug("%s: master session id: %u", __func__, sid); | ||
1530 | if (exitval_seen) | ||
1531 | fatal("%s: exitval sent twice", __func__); | ||
1532 | exitval = buffer_get_int(&m); | ||
1533 | exitval_seen = 1; | ||
448 | } | 1534 | } |
449 | 1535 | ||
450 | debug3("%s: channel_new: %d", __func__, c->self); | 1536 | close(fd); |
1537 | leave_raw_mode(force_tty_flag); | ||
451 | 1538 | ||
452 | channel_send_open(c->self); | 1539 | if (muxclient_terminate) { |
453 | channel_register_open_confirm(c->self, mux_session_confirm, cctx); | 1540 | debug2("Exiting on signal %d", muxclient_terminate); |
454 | return 0; | 1541 | exitval = 255; |
455 | } | 1542 | } else if (!exitval_seen) { |
1543 | debug2("Control master terminated unexpectedly"); | ||
1544 | exitval = 255; | ||
1545 | } else | ||
1546 | debug2("Received exit status from master %d", exitval); | ||
456 | 1547 | ||
457 | /* ** Multiplexing client support */ | 1548 | if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET) |
1549 | fprintf(stderr, "Shared connection to %s closed.\r\n", host); | ||
458 | 1550 | ||
459 | /* Exit signal handler */ | 1551 | exit(exitval); |
460 | static void | ||
461 | control_client_sighandler(int signo) | ||
462 | { | ||
463 | muxclient_terminate = signo; | ||
464 | } | 1552 | } |
465 | 1553 | ||
466 | /* | 1554 | static int |
467 | * Relay signal handler - used to pass some signals from mux client to | 1555 | mux_client_request_stdio_fwd(int fd) |
468 | * mux master. | ||
469 | */ | ||
470 | static void | ||
471 | control_client_sigrelay(int signo) | ||
472 | { | 1556 | { |
473 | int save_errno = errno; | 1557 | Buffer m; |
1558 | char *e; | ||
1559 | u_int type, rid, sid; | ||
1560 | int devnull; | ||
474 | 1561 | ||
475 | if (muxserver_pid > 1) | 1562 | debug3("%s: entering", __func__); |
476 | kill(muxserver_pid, signo); | ||
477 | 1563 | ||
478 | errno = save_errno; | 1564 | if ((muxserver_pid = mux_client_request_alive(fd)) == 0) { |
479 | } | 1565 | error("%s: master alive request failed", __func__); |
1566 | return -1; | ||
1567 | } | ||
480 | 1568 | ||
481 | /* Check mux client environment variables before passing them to mux master. */ | 1569 | signal(SIGPIPE, SIG_IGN); |
482 | static int | ||
483 | env_permitted(char *env) | ||
484 | { | ||
485 | int i, ret; | ||
486 | char name[1024], *cp; | ||
487 | 1570 | ||
488 | if ((cp = strchr(env, '=')) == NULL || cp == env) | 1571 | if (stdin_null_flag) { |
489 | return (0); | 1572 | if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1) |
490 | ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env); | 1573 | fatal("open(/dev/null): %s", strerror(errno)); |
491 | if (ret <= 0 || (size_t)ret >= sizeof(name)) | 1574 | if (dup2(devnull, STDIN_FILENO) == -1) |
492 | fatal("env_permitted: name '%.100s...' too long", env); | 1575 | fatal("dup2: %s", strerror(errno)); |
1576 | if (devnull > STDERR_FILENO) | ||
1577 | close(devnull); | ||
1578 | } | ||
493 | 1579 | ||
494 | for (i = 0; i < options.num_send_env; i++) | 1580 | buffer_init(&m); |
495 | if (match_pattern(name, options.send_env[i])) | 1581 | buffer_put_int(&m, MUX_C_NEW_STDIO_FWD); |
496 | return (1); | 1582 | buffer_put_int(&m, muxclient_request_id); |
1583 | buffer_put_cstring(&m, ""); /* reserved */ | ||
1584 | buffer_put_cstring(&m, stdio_forward_host); | ||
1585 | buffer_put_int(&m, stdio_forward_port); | ||
1586 | |||
1587 | if (mux_client_write_packet(fd, &m) != 0) | ||
1588 | fatal("%s: write packet: %s", __func__, strerror(errno)); | ||
1589 | |||
1590 | /* Send the stdio file descriptors */ | ||
1591 | if (mm_send_fd(fd, STDIN_FILENO) == -1 || | ||
1592 | mm_send_fd(fd, STDOUT_FILENO) == -1) | ||
1593 | fatal("%s: send fds failed", __func__); | ||
1594 | |||
1595 | debug3("%s: stdio forward request sent", __func__); | ||
1596 | |||
1597 | /* Read their reply */ | ||
1598 | buffer_clear(&m); | ||
1599 | |||
1600 | if (mux_client_read_packet(fd, &m) != 0) { | ||
1601 | error("%s: read from master failed: %s", | ||
1602 | __func__, strerror(errno)); | ||
1603 | buffer_free(&m); | ||
1604 | return -1; | ||
1605 | } | ||
1606 | |||
1607 | type = buffer_get_int(&m); | ||
1608 | if ((rid = buffer_get_int(&m)) != muxclient_request_id) | ||
1609 | fatal("%s: out of sequence reply: my id %u theirs %u", | ||
1610 | __func__, muxclient_request_id, rid); | ||
1611 | switch (type) { | ||
1612 | case MUX_S_SESSION_OPENED: | ||
1613 | sid = buffer_get_int(&m); | ||
1614 | debug("%s: master session id: %u", __func__, sid); | ||
1615 | break; | ||
1616 | case MUX_S_PERMISSION_DENIED: | ||
1617 | e = buffer_get_string(&m, NULL); | ||
1618 | buffer_free(&m); | ||
1619 | fatal("Master refused forwarding request: %s", e); | ||
1620 | case MUX_S_FAILURE: | ||
1621 | e = buffer_get_string(&m, NULL); | ||
1622 | buffer_free(&m); | ||
1623 | fatal("%s: stdio forwarding request failed: %s", __func__, e); | ||
1624 | default: | ||
1625 | buffer_free(&m); | ||
1626 | error("%s: unexpected response from master 0x%08x", | ||
1627 | __func__, type); | ||
1628 | return -1; | ||
1629 | } | ||
1630 | muxclient_request_id++; | ||
1631 | |||
1632 | signal(SIGHUP, control_client_sighandler); | ||
1633 | signal(SIGINT, control_client_sighandler); | ||
1634 | signal(SIGTERM, control_client_sighandler); | ||
1635 | signal(SIGWINCH, control_client_sigrelay); | ||
497 | 1636 | ||
498 | return (0); | 1637 | /* |
1638 | * Stick around until the controlee closes the client_fd. | ||
1639 | */ | ||
1640 | buffer_clear(&m); | ||
1641 | if (mux_client_read_packet(fd, &m) != 0) { | ||
1642 | if (errno == EPIPE || | ||
1643 | (errno == EINTR && muxclient_terminate != 0)) | ||
1644 | return 0; | ||
1645 | fatal("%s: mux_client_read_packet: %s", | ||
1646 | __func__, strerror(errno)); | ||
1647 | } | ||
1648 | fatal("%s: master returned unexpected message %u", __func__, type); | ||
499 | } | 1649 | } |
500 | 1650 | ||
501 | /* Multiplex client main loop. */ | 1651 | /* Multiplex client main loop. */ |
@@ -503,14 +1653,16 @@ void | |||
503 | muxclient(const char *path) | 1653 | muxclient(const char *path) |
504 | { | 1654 | { |
505 | struct sockaddr_un addr; | 1655 | struct sockaddr_un addr; |
506 | int i, r, fd, sock, exitval[2], num_env, addr_len; | 1656 | socklen_t sun_len; |
507 | Buffer m; | 1657 | int sock; |
508 | char *term; | 1658 | u_int pid; |
509 | extern char **environ; | ||
510 | u_int allowed, flags; | ||
511 | 1659 | ||
512 | if (muxclient_command == 0) | 1660 | if (muxclient_command == 0) { |
513 | muxclient_command = SSHMUX_COMMAND_OPEN; | 1661 | if (stdio_forward_host != NULL) |
1662 | muxclient_command = SSHMUX_COMMAND_STDIO_FWD; | ||
1663 | else | ||
1664 | muxclient_command = SSHMUX_COMMAND_OPEN; | ||
1665 | } | ||
514 | 1666 | ||
515 | switch (options.control_master) { | 1667 | switch (options.control_master) { |
516 | case SSHCTL_MASTER_AUTO: | 1668 | case SSHCTL_MASTER_AUTO: |
@@ -525,7 +1677,7 @@ muxclient(const char *path) | |||
525 | 1677 | ||
526 | memset(&addr, '\0', sizeof(addr)); | 1678 | memset(&addr, '\0', sizeof(addr)); |
527 | addr.sun_family = AF_UNIX; | 1679 | addr.sun_family = AF_UNIX; |
528 | addr_len = offsetof(struct sockaddr_un, sun_path) + | 1680 | sun_len = offsetof(struct sockaddr_un, sun_path) + |
529 | strlen(path) + 1; | 1681 | strlen(path) + 1; |
530 | 1682 | ||
531 | if (strlcpy(addr.sun_path, path, | 1683 | if (strlcpy(addr.sun_path, path, |
@@ -535,8 +1687,12 @@ muxclient(const char *path) | |||
535 | if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) | 1687 | if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) |
536 | fatal("%s socket(): %s", __func__, strerror(errno)); | 1688 | fatal("%s socket(): %s", __func__, strerror(errno)); |
537 | 1689 | ||
538 | if (connect(sock, (struct sockaddr *)&addr, addr_len) == -1) { | 1690 | if (connect(sock, (struct sockaddr *)&addr, sun_len) == -1) { |
539 | if (muxclient_command != SSHMUX_COMMAND_OPEN) { | 1691 | switch (muxclient_command) { |
1692 | case SSHMUX_COMMAND_OPEN: | ||
1693 | case SSHMUX_COMMAND_STDIO_FWD: | ||
1694 | break; | ||
1695 | default: | ||
540 | fatal("Control socket connect(%.100s): %s", path, | 1696 | fatal("Control socket connect(%.100s): %s", path, |
541 | strerror(errno)); | 1697 | strerror(errno)); |
542 | } | 1698 | } |
@@ -549,181 +1705,35 @@ muxclient(const char *path) | |||
549 | close(sock); | 1705 | close(sock); |
550 | return; | 1706 | return; |
551 | } | 1707 | } |
1708 | set_nonblock(sock); | ||
552 | 1709 | ||
553 | if (stdin_null_flag) { | 1710 | if (mux_client_hello_exchange(sock) != 0) { |
554 | if ((fd = open(_PATH_DEVNULL, O_RDONLY)) == -1) | 1711 | error("%s: master hello exchange failed", __func__); |
555 | fatal("open(/dev/null): %s", strerror(errno)); | ||
556 | if (dup2(fd, STDIN_FILENO) == -1) | ||
557 | fatal("dup2: %s", strerror(errno)); | ||
558 | if (fd > STDERR_FILENO) | ||
559 | close(fd); | ||
560 | } | ||
561 | |||
562 | term = getenv("TERM"); | ||
563 | |||
564 | flags = 0; | ||
565 | if (tty_flag) | ||
566 | flags |= SSHMUX_FLAG_TTY; | ||
567 | if (subsystem_flag) | ||
568 | flags |= SSHMUX_FLAG_SUBSYS; | ||
569 | if (options.forward_x11) | ||
570 | flags |= SSHMUX_FLAG_X11_FWD; | ||
571 | if (options.forward_agent) | ||
572 | flags |= SSHMUX_FLAG_AGENT_FWD; | ||
573 | |||
574 | signal(SIGPIPE, SIG_IGN); | ||
575 | |||
576 | buffer_init(&m); | ||
577 | |||
578 | /* Send our command to server */ | ||
579 | buffer_put_int(&m, muxclient_command); | ||
580 | buffer_put_int(&m, flags); | ||
581 | if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) { | ||
582 | error("%s: msg_send", __func__); | ||
583 | muxerr: | ||
584 | close(sock); | 1712 | close(sock); |
585 | buffer_free(&m); | ||
586 | if (muxclient_command != SSHMUX_COMMAND_OPEN) | ||
587 | cleanup_exit(255); | ||
588 | logit("Falling back to non-multiplexed connection"); | ||
589 | xfree(options.control_path); | ||
590 | options.control_path = NULL; | ||
591 | options.control_master = SSHCTL_MASTER_NO; | ||
592 | return; | 1713 | return; |
593 | } | 1714 | } |
594 | buffer_clear(&m); | ||
595 | |||
596 | /* Get authorisation status and PID of controlee */ | ||
597 | if (ssh_msg_recv(sock, &m) == -1) { | ||
598 | error("%s: Did not receive reply from master", __func__); | ||
599 | goto muxerr; | ||
600 | } | ||
601 | if (buffer_get_char(&m) != SSHMUX_VER) { | ||
602 | error("%s: Master replied with wrong version", __func__); | ||
603 | goto muxerr; | ||
604 | } | ||
605 | if (buffer_get_int_ret(&allowed, &m) != 0) { | ||
606 | error("%s: bad server reply", __func__); | ||
607 | goto muxerr; | ||
608 | } | ||
609 | if (allowed != 1) { | ||
610 | error("Connection to master denied"); | ||
611 | goto muxerr; | ||
612 | } | ||
613 | muxserver_pid = buffer_get_int(&m); | ||
614 | |||
615 | buffer_clear(&m); | ||
616 | 1715 | ||
617 | switch (muxclient_command) { | 1716 | switch (muxclient_command) { |
618 | case SSHMUX_COMMAND_ALIVE_CHECK: | 1717 | case SSHMUX_COMMAND_ALIVE_CHECK: |
619 | fprintf(stderr, "Master running (pid=%d)\r\n", | 1718 | if ((pid = mux_client_request_alive(sock)) == 0) |
620 | muxserver_pid); | 1719 | fatal("%s: master alive check failed", __func__); |
1720 | fprintf(stderr, "Master running (pid=%d)\r\n", pid); | ||
621 | exit(0); | 1721 | exit(0); |
622 | case SSHMUX_COMMAND_TERMINATE: | 1722 | case SSHMUX_COMMAND_TERMINATE: |
1723 | mux_client_request_terminate(sock); | ||
623 | fprintf(stderr, "Exit request sent.\r\n"); | 1724 | fprintf(stderr, "Exit request sent.\r\n"); |
624 | exit(0); | 1725 | exit(0); |
625 | case SSHMUX_COMMAND_OPEN: | 1726 | case SSHMUX_COMMAND_OPEN: |
626 | buffer_put_cstring(&m, term ? term : ""); | 1727 | if (mux_client_request_forwards(sock) != 0) { |
627 | if (options.escape_char == SSH_ESCAPECHAR_NONE) | 1728 | error("%s: master forward request failed", __func__); |
628 | buffer_put_int(&m, 0xffffffff); | 1729 | return; |
629 | else | ||
630 | buffer_put_int(&m, options.escape_char); | ||
631 | buffer_append(&command, "\0", 1); | ||
632 | buffer_put_cstring(&m, buffer_ptr(&command)); | ||
633 | |||
634 | if (options.num_send_env == 0 || environ == NULL) { | ||
635 | buffer_put_int(&m, 0); | ||
636 | } else { | ||
637 | /* Pass environment */ | ||
638 | num_env = 0; | ||
639 | for (i = 0; environ[i] != NULL; i++) { | ||
640 | if (env_permitted(environ[i])) | ||
641 | num_env++; /* Count */ | ||
642 | } | ||
643 | buffer_put_int(&m, num_env); | ||
644 | for (i = 0; environ[i] != NULL && num_env >= 0; i++) { | ||
645 | if (env_permitted(environ[i])) { | ||
646 | num_env--; | ||
647 | buffer_put_cstring(&m, environ[i]); | ||
648 | } | ||
649 | } | ||
650 | } | 1730 | } |
651 | break; | 1731 | mux_client_request_session(sock); |
1732 | return; | ||
1733 | case SSHMUX_COMMAND_STDIO_FWD: | ||
1734 | mux_client_request_stdio_fwd(sock); | ||
1735 | exit(0); | ||
652 | default: | 1736 | default: |
653 | fatal("unrecognised muxclient_command %d", muxclient_command); | 1737 | fatal("unrecognised muxclient_command %d", muxclient_command); |
654 | } | 1738 | } |
655 | |||
656 | if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) { | ||
657 | error("%s: msg_send", __func__); | ||
658 | goto muxerr; | ||
659 | } | ||
660 | |||
661 | if (mm_send_fd(sock, STDIN_FILENO) == -1 || | ||
662 | mm_send_fd(sock, STDOUT_FILENO) == -1 || | ||
663 | mm_send_fd(sock, STDERR_FILENO) == -1) { | ||
664 | error("%s: send fds failed", __func__); | ||
665 | goto muxerr; | ||
666 | } | ||
667 | |||
668 | /* | ||
669 | * Mux errors are non-recoverable from this point as the master | ||
670 | * has ownership of the session now. | ||
671 | */ | ||
672 | |||
673 | /* Wait for reply, so master has a chance to gather ttymodes */ | ||
674 | buffer_clear(&m); | ||
675 | if (ssh_msg_recv(sock, &m) == -1) | ||
676 | fatal("%s: msg_recv", __func__); | ||
677 | if (buffer_get_char(&m) != SSHMUX_VER) | ||
678 | fatal("%s: wrong version", __func__); | ||
679 | buffer_free(&m); | ||
680 | |||
681 | signal(SIGHUP, control_client_sighandler); | ||
682 | signal(SIGINT, control_client_sighandler); | ||
683 | signal(SIGTERM, control_client_sighandler); | ||
684 | signal(SIGWINCH, control_client_sigrelay); | ||
685 | |||
686 | if (tty_flag) | ||
687 | enter_raw_mode(force_tty_flag); | ||
688 | |||
689 | /* | ||
690 | * Stick around until the controlee closes the client_fd. | ||
691 | * Before it does, it is expected to write this process' exit | ||
692 | * value (one int). This process must read the value and wait for | ||
693 | * the closure of the client_fd; if this one closes early, the | ||
694 | * multiplex master will terminate early too (possibly losing data). | ||
695 | */ | ||
696 | exitval[0] = 0; | ||
697 | for (i = 0; !muxclient_terminate && i < (int)sizeof(exitval);) { | ||
698 | r = read(sock, (char *)exitval + i, sizeof(exitval) - i); | ||
699 | if (r == 0) { | ||
700 | debug2("Received EOF from master"); | ||
701 | break; | ||
702 | } | ||
703 | if (r == -1) { | ||
704 | if (errno == EINTR) | ||
705 | continue; | ||
706 | fatal("%s: read %s", __func__, strerror(errno)); | ||
707 | } | ||
708 | i += r; | ||
709 | } | ||
710 | |||
711 | close(sock); | ||
712 | leave_raw_mode(force_tty_flag); | ||
713 | if (i > (int)sizeof(int)) | ||
714 | fatal("%s: master returned too much data (%d > %lu)", | ||
715 | __func__, i, (u_long)sizeof(int)); | ||
716 | if (muxclient_terminate) { | ||
717 | debug2("Exiting on signal %d", muxclient_terminate); | ||
718 | exitval[0] = 255; | ||
719 | } else if (i < (int)sizeof(int)) { | ||
720 | debug2("Control master terminated unexpectedly"); | ||
721 | exitval[0] = 255; | ||
722 | } else | ||
723 | debug2("Received exit status from master %d", exitval[0]); | ||
724 | |||
725 | if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET) | ||
726 | fprintf(stderr, "Shared connection to %s closed.\r\n", host); | ||
727 | |||
728 | exit(exitval[0]); | ||
729 | } | 1739 | } |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: nchan.c,v 1.62 2008/11/07 18:50:18 stevesk Exp $ */ | 1 | /* $OpenBSD: nchan.c,v 1.63 2010/01/26 01:28:35 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 1999, 2000, 2001, 2002 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 1999, 2000, 2001, 2002 Markus Friedl. All rights reserved. |
4 | * | 4 | * |
@@ -161,7 +161,7 @@ chan_ibuf_empty(Channel *c) | |||
161 | switch (c->istate) { | 161 | switch (c->istate) { |
162 | case CHAN_INPUT_WAIT_DRAIN: | 162 | case CHAN_INPUT_WAIT_DRAIN: |
163 | if (compat20) { | 163 | if (compat20) { |
164 | if (!(c->flags & CHAN_CLOSE_SENT)) | 164 | if (!(c->flags & (CHAN_CLOSE_SENT|CHAN_LOCAL))) |
165 | chan_send_eof2(c); | 165 | chan_send_eof2(c); |
166 | chan_set_istate(c, CHAN_INPUT_CLOSED); | 166 | chan_set_istate(c, CHAN_INPUT_CLOSED); |
167 | } else { | 167 | } else { |
@@ -278,9 +278,12 @@ static void | |||
278 | chan_rcvd_close2(Channel *c) | 278 | chan_rcvd_close2(Channel *c) |
279 | { | 279 | { |
280 | debug2("channel %d: rcvd close", c->self); | 280 | debug2("channel %d: rcvd close", c->self); |
281 | if (c->flags & CHAN_CLOSE_RCVD) | 281 | if (!(c->flags & CHAN_LOCAL)) { |
282 | error("channel %d: protocol error: close rcvd twice", c->self); | 282 | if (c->flags & CHAN_CLOSE_RCVD) |
283 | c->flags |= CHAN_CLOSE_RCVD; | 283 | error("channel %d: protocol error: close rcvd twice", |
284 | c->self); | ||
285 | c->flags |= CHAN_CLOSE_RCVD; | ||
286 | } | ||
284 | if (c->type == SSH_CHANNEL_LARVAL) { | 287 | if (c->type == SSH_CHANNEL_LARVAL) { |
285 | /* tear down larval channels immediately */ | 288 | /* tear down larval channels immediately */ |
286 | chan_set_ostate(c, CHAN_OUTPUT_CLOSED); | 289 | chan_set_ostate(c, CHAN_OUTPUT_CLOSED); |
@@ -302,11 +305,13 @@ chan_rcvd_close2(Channel *c) | |||
302 | chan_set_istate(c, CHAN_INPUT_CLOSED); | 305 | chan_set_istate(c, CHAN_INPUT_CLOSED); |
303 | break; | 306 | break; |
304 | case CHAN_INPUT_WAIT_DRAIN: | 307 | case CHAN_INPUT_WAIT_DRAIN: |
305 | chan_send_eof2(c); | 308 | if (!(c->flags & CHAN_LOCAL)) |
309 | chan_send_eof2(c); | ||
306 | chan_set_istate(c, CHAN_INPUT_CLOSED); | 310 | chan_set_istate(c, CHAN_INPUT_CLOSED); |
307 | break; | 311 | break; |
308 | } | 312 | } |
309 | } | 313 | } |
314 | |||
310 | void | 315 | void |
311 | chan_rcvd_eow(Channel *c) | 316 | chan_rcvd_eow(Channel *c) |
312 | { | 317 | { |
@@ -454,6 +459,10 @@ chan_is_dead(Channel *c, int do_send) | |||
454 | c->self, c->efd, buffer_len(&c->extended)); | 459 | c->self, c->efd, buffer_len(&c->extended)); |
455 | return 0; | 460 | return 0; |
456 | } | 461 | } |
462 | if (c->flags & CHAN_LOCAL) { | ||
463 | debug2("channel %d: is dead (local)", c->self); | ||
464 | return 1; | ||
465 | } | ||
457 | if (!(c->flags & CHAN_CLOSE_SENT)) { | 466 | if (!(c->flags & CHAN_CLOSE_SENT)) { |
458 | if (do_send) { | 467 | if (do_send) { |
459 | chan_send_close2(c); | 468 | chan_send_close2(c); |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: ssh.c,v 1.331 2010/01/11 01:39:46 dtucker Exp $ */ | 1 | /* $OpenBSD: ssh.c,v 1.332 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 |
@@ -319,6 +319,11 @@ main(int ac, char **av) | |||
319 | options.gateway_ports = 1; | 319 | options.gateway_ports = 1; |
320 | break; | 320 | break; |
321 | case 'O': | 321 | case 'O': |
322 | if (stdio_forward_host != NULL) | ||
323 | fatal("Cannot specify multiplexing " | ||
324 | "command with -W"); | ||
325 | else if (muxclient_command != 0) | ||
326 | fatal("Multiplexing command already specified"); | ||
322 | if (strcmp(optarg, "check") == 0) | 327 | if (strcmp(optarg, "check") == 0) |
323 | muxclient_command = SSHMUX_COMMAND_ALIVE_CHECK; | 328 | muxclient_command = SSHMUX_COMMAND_ALIVE_CHECK; |
324 | else if (strcmp(optarg, "exit") == 0) | 329 | else if (strcmp(optarg, "exit") == 0) |
@@ -395,6 +400,10 @@ main(int ac, char **av) | |||
395 | } | 400 | } |
396 | break; | 401 | break; |
397 | case 'W': | 402 | case 'W': |
403 | if (stdio_forward_host != NULL) | ||
404 | fatal("stdio forward already specified"); | ||
405 | if (muxclient_command != 0) | ||
406 | fatal("Cannot specify stdio forward with -O"); | ||
398 | if (parse_forward(&fwd, optarg, 1, 0)) { | 407 | if (parse_forward(&fwd, optarg, 1, 0)) { |
399 | stdio_forward_host = fwd.listen_host; | 408 | stdio_forward_host = fwd.listen_host; |
400 | stdio_forward_port = fwd.listen_port; | 409 | stdio_forward_port = fwd.listen_port; |
@@ -902,11 +911,18 @@ static int | |||
902 | client_setup_stdio_fwd(const char *host_to_connect, u_short port_to_connect) | 911 | client_setup_stdio_fwd(const char *host_to_connect, u_short port_to_connect) |
903 | { | 912 | { |
904 | Channel *c; | 913 | Channel *c; |
914 | int in, out; | ||
905 | 915 | ||
906 | debug3("client_setup_stdio_fwd %s:%d", host_to_connect, | 916 | debug3("client_setup_stdio_fwd %s:%d", host_to_connect, |
907 | port_to_connect); | 917 | port_to_connect); |
908 | if ((c = channel_connect_stdio_fwd(host_to_connect, port_to_connect)) | 918 | |
909 | == NULL) | 919 | in = dup(STDIN_FILENO); |
920 | out = dup(STDOUT_FILENO); | ||
921 | if (in < 0 || out < 0) | ||
922 | fatal("channel_connect_stdio_fwd: dup() in/out failed"); | ||
923 | |||
924 | if ((c = channel_connect_stdio_fwd(host_to_connect, port_to_connect, | ||
925 | in, out)) == NULL) | ||
910 | return 0; | 926 | return 0; |
911 | channel_register_cleanup(c->self, client_cleanup_stdio_fwd, 0); | 927 | channel_register_cleanup(c->self, client_cleanup_stdio_fwd, 0); |
912 | return 1; | 928 | return 1; |