diff --git a/CHANGELOG b/CHANGELOG index 1d04218b3..a03987345 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,12 @@ #Nmap Changelog ($Id$); -*-text-*- +o [Ncat] Ncat in listen mode with --udp --ssl will use DTLS to secure incoming + connections. [Daniel Miller] + +o [Ncat][GH#1223] Ncat can now accept "connections" from multiple UDP hosts in + listen mode with the --keep-open option. This also enables --broker and + --chat via UDP. [Daniel Miller] + o [GH#2507] Updates to the Japanese manpage translation by Taichi Kotake. o [NSE][GH#548] New script tftp-version requests a nonexistent file from a TFTP diff --git a/ncat/docs/ncat.xml b/ncat/docs/ncat.xml index 2d7e80161..d20284344 100644 --- a/ncat/docs/ncat.xml +++ b/ncat/docs/ncat.xml @@ -327,8 +327,8 @@ particularly handy for talking to SSL enabled HTTP servers, etc. In server mode, this option listens for incoming SSL connections, rather than plain untunneled traffic. - In UDP connect mode, this option enables Datagram TLS (DTLS). - This is not supported in server mode. + In UDP mode, this option enables Datagram TLS (DTLS). + diff --git a/ncat/docs/ncatguide.xml b/ncat/docs/ncatguide.xml index 198b81180..d9677205d 100644 --- a/ncat/docs/ncatguide.xml +++ b/ncat/docs/ncatguide.xml @@ -131,9 +131,7 @@ the tool? Many of these examples suppose a Unix environment. --> to the connection limit. With (or for short), the server receives everything sent by any of its clients, and anything the server sends is sent to all of - them. A UDP server will communicate with only one client (the first - one to send it data), because in UDP there is no list of - connected clients. + them. @@ -304,12 +302,9 @@ Content-Type: text/html; charset=UTF-8 the User Datagram Protocol, is an unreliable protocol often used by applications that can't afford the overhead of TCP. Use the (Ncat option) - option to make Ncat use UDP. In listen mode, Ncat will communicate - with only one client, and the - (Ncat option)not supported with UDP - option doesn't work, the reason for this being that UDP has no notion - of a connection. UDP may be secured by a form of SSL called Datagram TLS (DTLS)DTLSDatagram TLS. - This is currently only supported in connect (client) mode.SSLnot supported with UDP in server mode + option to make Ncat use UDP. + UDP may be secured using the option, which enables + Datagram TLS (DTLS)DTLSDatagram TLS. @@ -646,8 +641,8 @@ print("Hello, world!") combined with ,with Ncat will accept multiple connections, forking off a new handler for - each. This works even in UDP mode; the usual limit of only one client - doesn't apply. The server will keep running until you press + each. + The server will keep running until you press ctrlC or otherwise terminate it externally. In this way Ncat can work much like inetd.inetd @@ -1779,20 +1774,17 @@ host1$ ncat --send-only host2 < log.txt UDP discard server -ncat -l 9 --keep-open --udp --sh-exec "cat > /dev/null" +ncat --udp -l --keep-open 9 --recv-only > /dev/null - With the TCP server we used so the server - could handle multiple simultaneous connections, not just one. For the - UDP server we had to use to allow multiple - concurrent connections. Recall from that - a UDP server can handle only one client but with - and this limitation - does not apply. + Ncat in UDP mode uses all the same options as TCP. The caveat here is that + connections can't be closed, only timed out, so you will eventually run out + of sockets if you do not use a timeout. Currently, none of the timeout + options do the appropriate thing in this instance. diff --git a/ncat/ncat_listen.c b/ncat/ncat_listen.c index be84b45d7..4493bf15e 100644 --- a/ncat/ncat_listen.c +++ b/ncat/ncat_listen.c @@ -117,7 +117,7 @@ static int listen_socket[NUM_LISTEN_ADDRS]; static int stdin_eof = 0; static int crlf_state = 0; -static void handle_connection(int socket_accept); +static void handle_connection(int socket_accept, int type, fd_set *listen_fds); static int read_stdin(void); static int read_socket(int recv_fd); static void post_handle_connection(struct fdinfo sinfo); @@ -165,14 +165,51 @@ static void sigchld_handler(int signum) } #endif -static int ncat_listen_stream(int proto) +int new_listen_socket(int type, int proto, const union sockaddr_u *addr, fd_set *listen_fds) +{ + struct fdinfo fdi; + fdi.fd = do_listen(type, proto, addr); + if (fdi.fd < 0) { + return -1; + } + fdi.remoteaddr = *addr; /* actually our local addr, but whatevs */ + + /* Make our listening socket non-blocking because there are timing issues + * which could cause us to block on accept() even though select() says it's + * readable. See UNPv1 2nd ed, p422 for more. + */ + unblock_socket(fdi.fd); + + /* setup select sets and max fd */ + checked_fd_set(fdi.fd, &master_readfds); + add_fdinfo(&client_fdlist, &fdi); + + checked_fd_set(fdi.fd, listen_fds); + + return fdi.fd; +} + +int ncat_listen() { int rc, i, fds_ready; fd_set listen_fds; struct timeval tv; struct timeval *tvp = NULL; unsigned int num_sockets; + int proto = o.proto; + int type = o.proto == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM; + if (o.httpserver) + return ncat_http_server(); + +#if HAVE_SYS_UN_H + if (o.af == AF_UNIX) + proto = 0; +#endif +#if HAVE_LINUX_VM_SOCKETS_H + if (o.af == AF_VSOCK) + proto = 0; +#endif /* clear out structs */ FD_ZERO(&master_readfds); FD_ZERO(&master_writefds); @@ -199,7 +236,7 @@ static int ncat_listen_stream(int proto) { if (o.sslalpn) bye("ALPN is not supported in listen mode\n"); - setup_ssl_listen(); + setup_ssl_listen(type == SOCK_STREAM ? SSLv23_server_method() : DTLS_server_method()); } #endif @@ -230,25 +267,12 @@ static int ncat_listen_stream(int proto) num_sockets = 0; for (i = 0; i < num_listenaddrs; i++) { /* setup the main listening socket */ - listen_socket[num_sockets] = do_listen(SOCK_STREAM, proto, &listenaddrs[i]); + listen_socket[num_sockets] = new_listen_socket(type, proto, &listenaddrs[i], &listen_fds); if (listen_socket[num_sockets] == -1) { if (o.debug > 0) logdebug("do_listen(\"%s\"): %s\n", inet_ntop_ez(&listenaddrs[i].storage, sizeof(listenaddrs[i].storage)), socket_strerror(socket_errno())); continue; } - - /* Make our listening socket non-blocking because there are timing issues - * which could cause us to block on accept() even though select() says it's - * readable. See UNPv1 2nd ed, p422 for more. - */ - unblock_socket(listen_socket[num_sockets]); - - /* setup select sets and max fd */ - checked_fd_set(listen_socket[num_sockets], &master_readfds); - add_fd(&client_fdlist, listen_socket[num_sockets]); - - checked_fd_set(listen_socket[num_sockets], &listen_fds); - num_sockets++; } if (num_sockets == 0) { @@ -341,7 +365,7 @@ static int ncat_listen_stream(int proto) #endif if (checked_fd_isset(cfd, &listen_fds)) { /* we have a new connection request */ - handle_connection(cfd); + handle_connection(cfd, type, &listen_fds); } else if (cfd == STDIN_FILENO) { if (o.broker) { read_and_broadcast(cfd); @@ -354,7 +378,7 @@ static int ncat_listen_stream(int proto) receiving anything, we can quit here. */ return 0; } - if (!o.noshutdown) shutdown_sockets(SHUT_WR); + if (!o.noshutdown && type == SOCK_STREAM) shutdown_sockets(SHUT_WR); } if (rc < 0) return 1; @@ -380,7 +404,7 @@ static int ncat_listen_stream(int proto) /* Accept a connection on a listening socket. Allow or deny the connection. Fork a command if o.cmdexec is set. Otherwise, add the new socket to the watch set. */ -static void handle_connection(int socket_accept) +static void handle_connection(int socket_accept, int type, fd_set *listen_fds) { union sockaddr_u remoteaddr; socklen_t ss_len; @@ -393,7 +417,39 @@ static void handle_connection(int socket_accept) ss_len = sizeof(remoteaddr.storage); errno = 0; - s.fd = accept(socket_accept, &remoteaddr.sockaddr, &ss_len); + if (type == SOCK_STREAM) { + s.fd = accept(socket_accept, &remoteaddr.sockaddr, &ss_len); + } + else { + char buf[4] = {0}; + int nbytes = recvfrom(socket_accept, buf, sizeof(buf), MSG_PEEK, + &remoteaddr.sockaddr, &ss_len); + if (nbytes < 0) { + loguser("%s.\n", socket_strerror(socket_errno())); + return; + } + /* + * We're using connected udp. This has the down side of only + * being able to handle one udp client at a time + */ + Connect(socket_accept, &remoteaddr.sockaddr, ss_len); + s.fd = socket_accept; + // Remove this socket from listening and put a new one up. + for (int i = 0; i < num_listenaddrs; i++) { + if (listen_socket[i] == socket_accept) { + struct fdinfo *lfdi = get_fdinfo(&client_fdlist, socket_accept); + union sockaddr_u localaddr = lfdi->remoteaddr; + checked_fd_clr(socket_accept, &master_readfds); + checked_fd_clr(socket_accept, listen_fds); + rm_fd(&client_fdlist, socket_accept); + listen_socket[i] = new_listen_socket(type, (o.af == AF_INET || o.af == AF_INET6) ? o.proto : 0, &localaddr, listen_fds); + if (listen_socket[i] < 0) { + bye("do_listen(\"%s\"): %s\n", inet_ntop_ez(&listenaddrs[i].storage, sizeof(listenaddrs[i].storage)), socket_strerror(socket_errno())); + return; + } + } + } + } if (s.fd < 0) { if (o.debug) @@ -406,12 +462,12 @@ static void handle_connection(int socket_accept) if (o.verbose) { #if HAVE_SYS_UN_H if (remoteaddr.sockaddr.sa_family == AF_UNIX) - loguser("Connection from a client on Unix domain socket.\n"); + loguser("Connection from %s.\n", remoteaddr.un.sun_path); else #endif #ifdef HAVE_LINUX_VM_SOCKETS_H if (remoteaddr.sockaddr.sa_family == AF_VSOCK) - loguser("Connection from a client on vsock socket.\n"); + loguser("Connection from %u.\n", remoteaddr.vm.svm_cid); else #endif if (o.chat) @@ -616,350 +672,6 @@ int read_socket(int recv_fd) return nbytes; } -/* This is sufficiently different from the TCP code (wrt SSL, etc) that it - * resides in its own simpler function - */ -static int ncat_listen_dgram(int proto) -{ - struct { - int fd; - union sockaddr_u addr; - } sockfd[NUM_LISTEN_ADDRS]; - int i, fdn = -1; - int fdmax, nbytes, n, fds_ready; - char buf[DEFAULT_UDP_BUF_LEN] = { 0 }; - char *tempbuf = NULL; - fd_set read_fds; - union sockaddr_u remotess; - socklen_t sslen = sizeof(remotess.storage); - struct timeval tv; - struct timeval *tvp = NULL; - unsigned int num_sockets; - -#ifdef HAVE_OPENSSL - if(o.ssl) - bye("DTLS is not supported in listen mode\n"); -#endif - - for (i = 0; i < NUM_LISTEN_ADDRS; i++) { - sockfd[i].fd = -1; - sockfd[i].addr.storage.ss_family = AF_UNSPEC; - } - - FD_ZERO(&read_fds); - - /* Initialize remotess struct so recvfrom() doesn't hit the fan.. */ - zmem(&remotess.storage, sizeof(remotess.storage)); - remotess.storage.ss_family = o.af; - -#ifdef WIN32 - set_pseudo_sigchld_handler(decrease_conn_count); -#else - /* Reap on SIGCHLD */ - Signal(SIGCHLD, sigchld_handler); - /* Ignore the SIGPIPE that occurs when a client disconnects suddenly and we - send data to it before noticing. */ - Signal(SIGPIPE, SIG_IGN); -#endif - -/* Not sure if this problem exists on Windows, but fcntl and /dev/null don't */ -#ifndef WIN32 - /* Check whether stdin is closed. Because we treat this fd specially, we - * can't risk it being reopened for an incoming connection, so we'll hold - * it open instead. */ - if (fcntl(STDIN_FILENO, F_GETFD) == -1 && errno == EBADF) { - logdebug("stdin is closed, attempting to reserve STDIN_FILENO\n"); - i = open("/dev/null", O_RDONLY); - if (i >= 0 && i != STDIN_FILENO) { - /* Oh well, we tried */ - logdebug("Couldn't reserve STDIN_FILENO\n"); - close(i); - } - } -#endif - - /* set for selecting udp listening sockets */ - fd_set listen_fds; - fd_list_t listen_fdlist; - FD_ZERO(&listen_fds); - init_fdlist(&listen_fdlist, num_listenaddrs); - - num_sockets = 0; - for (i = 0; i < num_listenaddrs; i++) { - /* create the UDP listen sockets */ - sockfd[num_sockets].fd = do_listen(SOCK_DGRAM, proto, &listenaddrs[i]); - if (sockfd[num_sockets].fd == -1) { - if (o.debug > 0) - logdebug("do_listen(\"%s\"): %s\n", inet_ntop_ez(&listenaddrs[i].storage, sizeof(listenaddrs[i].storage)), socket_strerror(socket_errno())); - continue; - } - checked_fd_set(sockfd[num_sockets].fd, &listen_fds); - add_fd(&listen_fdlist, sockfd[num_sockets].fd); - sockfd[num_sockets].addr = listenaddrs[i]; - num_sockets++; - } - if (num_sockets == 0) { - if (num_listenaddrs == 1) - bye("Unable to open listening socket on %s: %s", inet_ntop_ez(&listenaddrs[0].storage, sizeof(listenaddrs[0].storage)), socket_strerror(socket_errno())); - else - bye("Unable to open any listening sockets."); - } - - if (o.idletimeout > 0) - tvp = &tv; - - while (1) { - int i, j, conn_count, socket_n; - - if (fdn != -1) { - /*remove socket descriptor which is burnt */ - checked_fd_clr(sockfd[fdn].fd, &listen_fds); - rm_fd(&listen_fdlist, sockfd[fdn].fd); - - /* Rebuild the udp socket which got burnt */ - sockfd[fdn].fd = do_listen(SOCK_DGRAM, proto, &sockfd[fdn].addr); - if (sockfd[fdn].fd == -1) - bye("do_listen: %s", socket_strerror(socket_errno())); - checked_fd_set(sockfd[fdn].fd, &listen_fds); - add_fd(&listen_fdlist, sockfd[fdn].fd); - - } - fdn = -1; - socket_n = -1; - fd_set fds; - FD_ZERO(&fds); - while (1) { - /* - * We just select to get a list of sockets which we can talk to - */ - if (o.debug > 1) - logdebug("selecting, fdmax %d\n", listen_fdlist.fdmax); - fds = listen_fds; - - if (o.idletimeout > 0) - ms_to_timeval(tvp, o.idletimeout); - - /* The idle timer should only be running when there are active connections */ - if (get_conn_count()) - fds_ready = fselect(listen_fdlist.fdmax + 1, &fds, NULL, NULL, tvp); - else - fds_ready = fselect(listen_fdlist.fdmax + 1, &fds, NULL, NULL, NULL); - - if (o.debug > 1) - logdebug("select returned %d fds ready\n", fds_ready); - - if (fds_ready == 0) - bye("Idle timeout expired (%d ms).", o.idletimeout); - - /* - * Figure out which listening socket got a connection. This loop should - * really call a function for each ready socket instead of breaking on - * the first one. - */ - for (i = 0; i <= listen_fdlist.fdmax && fds_ready > 0; i++) { - /* Loop through descriptors until there is something ready */ - if (!checked_fd_isset(i, &fds)) - continue; - - /* Check each listening socket */ - for (j = 0; j < num_sockets; j++) { - if (i == sockfd[j].fd) { - if (o.debug > 1) - logdebug("Valid descriptor %d \n", i); - fdn = j; - socket_n = i; - break; - } - } - - /* if we found a valid socket break */ - if (fdn != -1) { - fds_ready--; - break; - } - } - - /* Make sure someone connected */ - if (fdn == -1) - continue; - - /* - * We just peek so we can get the client connection details without - * removing anything from the queue. Sigh. - */ - nbytes = recvfrom(socket_n, buf, sizeof(buf), MSG_PEEK, - &remotess.sockaddr, &sslen); - if (nbytes < 0) { - loguser("%s.\n", socket_strerror(socket_errno())); - close(socket_n); - return 1; - } - - /* Check conditions that might cause us to deny the connection. */ - conn_count = get_conn_count(); - if (conn_count >= o.conn_limit) { - if (o.verbose) - loguser("New connection denied: connection limit reached (%d)\n", conn_count); - } else if (!allow_access(&remotess)) { - if (o.verbose) - loguser("New connection denied: not allowed\n"); - } else { - /* Good to go. */ - break; - } - - /* Dump the current datagram */ - nbytes = recv(socket_n, buf, sizeof(buf), 0); - if (nbytes < 0) { - loguser("%s.\n", socket_strerror(socket_errno())); - close(socket_n); - return 1; - } - ncat_log_recv(buf, nbytes); - } - - if (o.verbose) { -#if HAVE_SYS_UN_H - if (remotess.sockaddr.sa_family == AF_UNIX) - loguser("Connection from %s.\n", remotess.un.sun_path); - else -#endif -#ifdef HAVE_LINUX_VM_SOCKETS_H - if (remotess.sockaddr.sa_family == AF_VSOCK) - loguser("Connection from %u.\n", remotess.vm.svm_cid); - else -#endif - loguser("Connection from %s.\n", inet_socktop(&remotess)); - } - - conn_inc++; - - /* - * We're using connected udp. This has the down side of only - * being able to handle one udp client at a time - */ - Connect(socket_n, &remotess.sockaddr, sslen); - - /* clean slate for buf */ - zmem(buf, sizeof(buf)); - - /* are we executing a command? then do it */ - if (o.cmdexec) { - struct fdinfo info = { 0 }; - - info.fd = socket_n; - info.remoteaddr = remotess; - if (o.keepopen) - netrun(&info, o.cmdexec); - else - netexec(&info, o.cmdexec); - continue; - } - - checked_fd_set(socket_n, &read_fds); - checked_fd_set(STDIN_FILENO, &read_fds); - fdmax = socket_n; - - /* stdin -> socket and socket -> stdout */ - while (1) { - fd_set fds; - - fds = read_fds; - - if (o.debug > 1) - logdebug("udp select'ing\n"); - - if (o.idletimeout > 0) - ms_to_timeval(tvp, o.idletimeout); - - fds_ready = fselect(fdmax + 1, &fds, NULL, NULL, tvp); - - if (fds_ready == 0) - bye("Idle timeout expired (%d ms).", o.idletimeout); - - if (checked_fd_isset(STDIN_FILENO, &fds)) { - nbytes = Read(STDIN_FILENO, buf, sizeof(buf)); - if (nbytes <= 0) { - if (nbytes < 0 && o.verbose) { - logdebug("Error reading from stdin: %s\n", strerror(errno)); - } else if (nbytes == 0 && o.debug) { - logdebug("EOF on stdin\n"); - } - checked_fd_clr(STDIN_FILENO, &read_fds); - if (nbytes < 0) - return 1; - continue; - } - if (o.crlf) - fix_line_endings((char *) buf, &nbytes, &tempbuf, &crlf_state); - if (!o.recvonly) { - if (tempbuf != NULL) - n = send(socket_n, tempbuf, nbytes, 0); - else - n = send(socket_n, buf, nbytes, 0); - if (n < nbytes) { - loguser("%s.\n", socket_strerror(socket_errno())); - close(socket_n); - return 1; - } - ncat_log_send(buf, nbytes); - } - if (tempbuf != NULL) { - free(tempbuf); - tempbuf = NULL; - } - } - if (checked_fd_isset(socket_n, &fds)) { - nbytes = recv(socket_n, buf, sizeof(buf), 0); - if (nbytes < 0) { - loguser("%s.\n", socket_strerror(socket_errno())); - close(socket_n); - return 1; - } - ncat_log_recv(buf, nbytes); - if (!o.sendonly) - Write(STDOUT_FILENO, buf, nbytes); - } - - zmem(buf, sizeof(buf)); - } - } - - return 0; -} - -int ncat_listen() -{ -#if HAVE_SYS_UN_H - if (o.af == AF_UNIX) - if (o.proto == IPPROTO_UDP) - return ncat_listen_dgram(0); - else - return ncat_listen_stream(0); - else -#endif -#if HAVE_LINUX_VM_SOCKETS_H - if (o.af == AF_VSOCK) { - if (o.proto == IPPROTO_UDP) - return ncat_listen_dgram(0); - else - return ncat_listen_stream(0); - } else -#endif - if (o.httpserver) - return ncat_http_server(); - else if (o.proto == IPPROTO_UDP) - return ncat_listen_dgram(o.proto); - else if (o.proto == IPPROTO_SCTP) - return ncat_listen_stream(o.proto); - else if (o.proto == IPPROTO_TCP) - return ncat_listen_stream(o.proto); - else - bye("Unknown o.proto %d\n", o.proto); - - /* unreached */ - return 1; -} //--------------- /* Read from recv_fd and broadcast whatever is read to all other descriptors in diff --git a/ncat/ncat_main.c b/ncat/ncat_main.c index e1923ed2f..ce36ea236 100644 --- a/ncat/ncat_main.c +++ b/ncat/ncat_main.c @@ -984,13 +984,6 @@ int main(int argc, char *argv[]) if (o.ssl) bye("OpenSSL does not have DTLS support compiled in."); #endif - if (o.keepopen && o.cmdexec == NULL) - bye("UDP mode does not support the -k or --keep-open options, except with --exec or --sh-exec."); - if (o.broker) - bye("UDP mode does not support connection brokering.\n\ -If this feature is important to you, write dev@nmap.org with a\n\ -description of how you intend to use it, as an aid to deciding how UDP\n\ -connection brokering should work."); } /* Do whatever is necessary to receive \n for line endings on input from diff --git a/ncat/ncat_proxy.c b/ncat/ncat_proxy.c index 86ec8e407..7f4f477d0 100644 --- a/ncat/ncat_proxy.c +++ b/ncat/ncat_proxy.c @@ -140,7 +140,7 @@ int ncat_http_server(void) #ifdef HAVE_OPENSSL if (o.ssl) - setup_ssl_listen(); + setup_ssl_listen(SSLv23_server_method()); #endif /* Clear the socket list */ for (i = 0; i < NUM_LISTEN_ADDRS; i++) diff --git a/ncat/ncat_ssl.c b/ncat/ncat_ssl.c index f98df66ac..0c3cad512 100644 --- a/ncat/ncat_ssl.c +++ b/ncat/ncat_ssl.c @@ -105,10 +105,8 @@ enum { }; #define CERTIFICATE_COMMENT "Automatically generated by Ncat. See https://nmap.org/ncat/." -SSL_CTX *setup_ssl_listen(void) +SSL_CTX *setup_ssl_listen(const SSL_METHOD *method) { - const SSL_METHOD *method; - if (sslctx) goto done; @@ -138,8 +136,8 @@ SSL_CTX *setup_ssl_listen(void) if (!RAND_status()) bye("Failed to seed OpenSSL PRNG (RAND_status returned false)."); - if (!(method = SSLv23_server_method())) - bye("SSLv23_server_method(): %s.", ERR_error_string(ERR_get_error(), NULL)); + if (!method) + bye("Invalid SSL method: %s.", ERR_error_string(ERR_get_error(), NULL)); if (!(sslctx = SSL_CTX_new(method))) bye("SSL_CTX_new(): %s.", ERR_error_string(ERR_get_error(), NULL)); diff --git a/ncat/ncat_ssl.h b/ncat/ncat_ssl.h index 458736e27..9e3dc707b 100644 --- a/ncat/ncat_ssl.h +++ b/ncat/ncat_ssl.h @@ -84,7 +84,7 @@ enum { NCAT_SSL_HANDSHAKE_FAILED = 3 }; -extern SSL_CTX *setup_ssl_listen(void); +extern SSL_CTX *setup_ssl_listen(const SSL_METHOD *method); extern SSL *new_ssl(int fd);