From 917722257d11ea7a33990c170542aeff2b6061b1 Mon Sep 17 00:00:00 2001 From: houseofkodai Date: Thu, 25 Jan 2018 19:51:41 +0530 Subject: [PATCH] Server chosen tcpfwd ports (#43) Server chosen tcpfwd ports --- netio.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ netio.h | 1 + svr-tcpfwd.c | 45 +++++++++++++++++++++++++++++--------------- 3 files changed, 84 insertions(+), 15 deletions(-) diff --git a/netio.c b/netio.c index 24bb1d2..2f37e70 100644 --- a/netio.c +++ b/netio.c @@ -348,6 +348,37 @@ void set_sock_priority(int sock, enum dropbear_prio prio) { } +/* from openssh/canohost.c avoid premature-optimization */ +int get_sock_port(int sock) { + struct sockaddr_storage from; + socklen_t fromlen; + char strport[NI_MAXSERV]; + int r; + + /* Get IP address of client. */ + fromlen = sizeof(from); + memset(&from, 0, sizeof(from)); + if (getsockname(sock, (struct sockaddr *)&from, &fromlen) < 0) { + TRACE(("getsockname failed: %d", errno)) + return 0; + } + + /* Work around Linux IPv6 weirdness */ + if (from.ss_family == AF_INET6) + fromlen = sizeof(struct sockaddr_in6); + + /* Non-inet sockets don't have a port number. */ + if (from.ss_family != AF_INET && from.ss_family != AF_INET6) + return 0; + + /* Return port number. */ + if ((r = getnameinfo((struct sockaddr *)&from, fromlen, NULL, 0, + strport, sizeof(strport), NI_NUMERICSERV)) != 0) { + TRACE(("netio.c/get_sock_port/getnameinfo NI_NUMERICSERV failed: %d", r)) + } + return atoi(strport); +} + /* Listen on address:port. * Special cases are address of "" listening on everything, * and address of NULL listening on localhost only. @@ -400,11 +431,29 @@ int dropbear_listen(const char* address, const char* port, return -1; } + /* + * when listening on server-assigned-port 0 + * the assigned ports may differ for address families (v4/v6) + * causing problems for tcpip-forward + * caller can do a get_socket_address to discover assigned-port + * hence, use same port for all address families + */ + u_int16_t *allocated_lport_p = 0; + int allocated_lport = 0; nsock = 0; for (res = res0; res != NULL && nsock < sockcount; res = res->ai_next) { + if (allocated_lport > 0) { + if (AF_INET == res->ai_family) { + allocated_lport_p = &((struct sockaddr_in *)res->ai_addr)->sin_port; + } else if (AF_INET6 == res->ai_family) { + allocated_lport_p = &((struct sockaddr_in6 *)res->ai_addr)->sin6_port; + } + *allocated_lport_p = htons(allocated_lport); + } + /* Get a socket */ socks[nsock] = socket(res->ai_family, res->ai_socktype, res->ai_protocol); @@ -451,6 +500,10 @@ int dropbear_listen(const char* address, const char* port, continue; } + if (0 == allocated_lport) { + allocated_lport = get_sock_port(sock); + } + *maxfd = MAX(*maxfd, sock); nsock++; diff --git a/netio.h b/netio.h index 532157c..090df3d 100644 --- a/netio.h +++ b/netio.h @@ -14,6 +14,7 @@ enum dropbear_prio { void set_sock_nodelay(int sock); void set_sock_priority(int sock, enum dropbear_prio prio); +int get_sock_port(int sock); void get_socket_address(int fd, char **local_host, char **local_port, char **remote_host, char **remote_port, int host_lookup); void getaddrstring(struct sockaddr_storage* addr, diff --git a/svr-tcpfwd.c b/svr-tcpfwd.c index faf372e..c592afb 100644 --- a/svr-tcpfwd.c +++ b/svr-tcpfwd.c @@ -47,7 +47,7 @@ void recv_msg_global_request_remotetcp() { #endif /* !DROPBEAR_SVR_REMOTETCPFWD */ static int svr_cancelremotetcp(void); -static int svr_remotetcpreq(void); +static int svr_remotetcpreq(int *allocated_listen_port); static int newtcpdirect(struct Channel * channel); #if DROPBEAR_SVR_REMOTETCPFWD @@ -86,7 +86,16 @@ void recv_msg_global_request_remotetcp() { } if (strcmp("tcpip-forward", reqname) == 0) { - ret = svr_remotetcpreq(); + int allocated_listen_port; + ret = svr_remotetcpreq(&allocated_listen_port); + /* client expects-port-number-to-make-use-of-server-allocated-ports */ + if (DROPBEAR_SUCCESS == ret) { + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_REQUEST_SUCCESS); + buf_putint(ses.writepayload, allocated_listen_port); + encrypt_packet(); + wantreply = 0; //so out does not do so + } } else if (strcmp("cancel-tcpip-forward", reqname) == 0) { ret = svr_cancelremotetcp(); } else { @@ -152,7 +161,7 @@ out: return ret; } -static int svr_remotetcpreq() { +static int svr_remotetcpreq(int *allocated_listen_port) { int ret = DROPBEAR_FAILURE; char * request_addr = NULL; @@ -170,19 +179,16 @@ static int svr_remotetcpreq() { port = buf_getint(ses.payload); - if (port == 0) { - dropbear_log(LOG_INFO, "Server chosen tcpfwd ports are unsupported"); - goto out; - } + if (port != 0) { + if (port < 1 || port > 65535) { + TRACE(("invalid port: %d", port)) + goto out; + } - if (port < 1 || port > 65535) { - TRACE(("invalid port: %d", port)) - goto out; - } - - if (!ses.allowprivport && port < IPPORT_RESERVED) { - TRACE(("can't assign port < 1024 for non-root")) - goto out; + if (!ses.allowprivport && port < IPPORT_RESERVED) { + TRACE(("can't assign port < 1024 for non-root")) + goto out; + } } tcpinfo = (struct TCPListener*)m_malloc(sizeof(struct TCPListener)); @@ -203,6 +209,13 @@ static int svr_remotetcpreq() { } ret = listen_tcpfwd(tcpinfo); + if (DROPBEAR_SUCCESS == ret) { + tcpinfo->listenport = get_sock_port(ses.listeners[0]->socks[0]); + *allocated_listen_port = tcpinfo->listenport; + dropbear_log(LOG_INFO, "tcpip-forward %s:%d '%s'", + ((NULL == tcpinfo->listenaddr)?"localhost":tcpinfo->listenaddr), + tcpinfo->listenport, ses.authstate.pw_name); + } out: if (ret == DROPBEAR_FAILURE) { @@ -211,7 +224,9 @@ out: m_free(request_addr); m_free(tcpinfo); } + TRACE(("leave remotetcpreq")) + return ret; }