1
0
mirror of https://github.com/nmap/nmap.git synced 2026-01-05 06:09:00 +00:00

Enable multiple UDP connections in listen mode. Fixes #1223

This commit is contained in:
dmiller
2022-10-10 20:48:14 +00:00
parent 1641a291e9
commit 4e6c8feb15
8 changed files with 104 additions and 402 deletions

View File

@@ -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

View File

@@ -327,8 +327,8 @@
particularly handy for talking to SSL enabled HTTP servers, etc.</para>
<para>In server mode, this option listens for incoming SSL connections,
rather than plain untunneled traffic.</para>
<para>In UDP connect mode, this option enables Datagram TLS (DTLS).
This is not supported in server mode.</para>
<para>In UDP mode, this option enables Datagram TLS (DTLS).
</para>
</listitem>
</varlistentry>

View File

@@ -131,9 +131,7 @@ the tool? Many of these examples suppose a Unix environment. -->
to the connection limit. With <option>--keep-open</option> (or
<option>-k</option> 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
<quote>connected</quote> clients.
them.
</para>
<para>
@@ -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
<option>--udp</option><indexterm><primary><option>--udp</option> (Ncat option)</primary></indexterm>
option to make Ncat use UDP. In listen mode, Ncat will communicate
with only one client, and the
<option>--keep-open</option><indexterm><primary><option>--keep-open</option> (Ncat option)</primary><secondary>not supported with UDP</secondary></indexterm>
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)<indexterm><primary>DTLS</primary><secondary>Datagram TLS</secondary></indexterm>.
This is currently only supported in connect (client) mode.<indexterm><primary>SSL</primary><secondary>not supported with UDP in server mode</secondary></indexterm>
option to make Ncat use UDP.
UDP may be secured using the <option>--ssl</option> option, which enables
Datagram TLS (DTLS)<indexterm><primary>DTLS</primary><secondary>Datagram TLS</secondary></indexterm>.
</para>
<para>
@@ -646,8 +641,8 @@ print("Hello, world!")
combined with
<option>--keep-open</option>,<indexterm><primary><option>--keep-open (Ncat option)</option></primary><secondary>with <option>--exec</option></secondary></indexterm>
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
<keycombo><keycap>ctrl</keycap><keycap>C</keycap></keycombo> or
otherwise terminate it externally. In this way Ncat can work much like
inetd.<indexterm><primary>inetd</primary></indexterm>
@@ -1779,20 +1774,17 @@ host1$ <userinput>ncat --send-only host2 &lt; log.txt</userinput>
<term>UDP discard server</term>
<listitem>
<literallayout>
<command>ncat -l 9 --keep-open --udp --sh-exec "cat > /dev/null"</command>
<command>ncat --udp -l --keep-open 9 --recv-only > /dev/null</command>
</literallayout>
</listitem>
</varlistentry>
</variablelist>
<para>
With the TCP server we used <option>--keep-open</option> so the server
could handle multiple simultaneous connections, not just one. For the
UDP server we had to use <option>--sh-exec</option> to allow multiple
concurrent connections. Recall from <xref linkend="ncat-usage"/> that
a UDP server can handle only one client but with
<option>--exec</option> and <option>--sh-exec</option> 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. </para>
</para>
<para>

View File

@@ -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

View File

@@ -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

View File

@@ -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++)

View File

@@ -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));

View File

@@ -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);