This commit is contained in:
Lukas Johannes Möller 2026-06-27 20:11:16 +08:00 committed by GitHub
commit cd7e215b3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 571 additions and 17 deletions

View file

@ -50,7 +50,7 @@ CC = @CC@
CPPFLAGS = @CPPFLAGS@
CFLAGS = @CFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
LIBS = @LIBS@ @PTHREAD_LIBS@
DEFS = @DEFS@ -DNCAT_DATADIR="\"$(pkgdatadir)\""
# With GCC, add extra security checks to source code.
DEFS += -D_FORTIFY_SOURCE=2
@ -73,8 +73,8 @@ SHTOOL = ../shtool
# usual directory structure into a different tree.
DESTDIR =
SRCS = ncat_main.c ncat_connect.c ncat_core.c ncat_posix.c ncat_listen.c ncat_proxy.c ncat_ssl.c base64.c http.c util.c sys_wrap.c
OBJS = ncat_main.o ncat_connect.o ncat_core.o ncat_posix.o ncat_listen.o ncat_proxy.o ncat_ssl.o base64.o http.o util.o sys_wrap.o
SRCS = ncat_main.c ncat_connect.c ncat_core.c ncat_posix.c ncat_listen.c ncat_proxy.c ncat_ssl.c allowdeny_watcher.c base64.c http.c util.c sys_wrap.c
OBJS = ncat_main.o ncat_connect.o ncat_core.o ncat_posix.o ncat_listen.o ncat_proxy.o ncat_ssl.o allowdeny_watcher.o base64.o http.o util.o sys_wrap.o
DATAFILES =
ifneq ($(HAVE_OPENSSL),)
@ -138,16 +138,16 @@ config.h:
$(SHELL) ./config.status; \
fi
test/addrset: test/addrset.o ncat_core.o sys_wrap.o util.o ncat_posix.o $(LUA_OBJS) @LUA_DEPENDS@
test/addrset: test/addrset.o ncat_core.o sys_wrap.o util.o ncat_posix.o allowdeny_watcher.o $(LUA_OBJS) @LUA_DEPENDS@
$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^ $(NSOCKLIB) $(NBASELIB) $(OPENSSL_LIBS) $(PCAP_LIBS) $(LUA_LIBS) $(LIBS)
test/test-uri: test/test-uri.o base64.o http.o ncat_core.o sys_wrap.o util.o ncat_posix.o $(LUA_OBJS) @LUA_DEPENDS@
test/test-uri: test/test-uri.o base64.o http.o ncat_core.o sys_wrap.o util.o ncat_posix.o allowdeny_watcher.o $(LUA_OBJS) @LUA_DEPENDS@
$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^ $(NSOCKLIB) $(NBASELIB) $(OPENSSL_LIBS) $(PCAP_LIBS) $(LUA_LIBS) $(LIBS)
test/test-cmdline-split: test/test-cmdline-split.o ncat_posix.o ncat_core.o sys_wrap.o util.o $(LUA_OBJS) @LUA_DEPENDS@
test/test-cmdline-split: test/test-cmdline-split.o ncat_posix.o ncat_core.o sys_wrap.o util.o allowdeny_watcher.o $(LUA_OBJS) @LUA_DEPENDS@
$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^ $(NSOCKLIB) $(NBASELIB) $(OPENSSL_LIBS) $(PCAP_LIBS) $(LUA_LIBS) $(LIBS)
test/test-wildcard: test/test-wildcard.o ncat_core.o ncat_ssl.o sys_wrap.o util.o ncat_posix.o $(LUA_OBJS) @LUA_DEPENDS@
test/test-wildcard: test/test-wildcard.o ncat_core.o ncat_ssl.o sys_wrap.o util.o ncat_posix.o allowdeny_watcher.o $(LUA_OBJS) @LUA_DEPENDS@
$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^ $(NSOCKLIB) $(NBASELIB) $(OPENSSL_LIBS) $(PCAP_LIBS) $(LUA_LIBS) $(LIBS)
.PHONY: uninstall all clean distclean build-lua
@ -191,7 +191,7 @@ distclean-lua:
distclean: clean @LUA_DIST_CLEAN@
-rm -f Makefile makefile.dep $(CONFIG_CLEAN_FILES)
TESTS = ./test-addrset.sh ./test-cmdline-split ./test-uri
TESTS = ./test-addrset.sh ./test-cmdline-split ./test-uri ./test-watch-allow-deny.sh
ifneq ($(HAVE_OPENSSL),)
TESTS += ./test-wildcard
endif

367
ncat/allowdeny_watcher.c Normal file
View file

@ -0,0 +1,367 @@
/***************************************************************************
* allowdeny_watcher.c -- Cross-platform watcher for Ncat ACL files *
***********************IMPORTANT NMAP LICENSE TERMS************************
*
* The Nmap Security Scanner is (C) 1996-2025 Nmap Software LLC ("The Nmap
* Project"). Nmap is also a registered trademark of the Nmap Project.
*
* This program is distributed under the terms of the Nmap Public Source
* License (NPSL). The exact license text applying to a particular Nmap
* release or source code control revision is contained in the LICENSE file
* distributed with that version of Nmap or source code control revision.
* More Nmap copyright/legal information is available from
* https://nmap.org/book/man-legal.html, and further information on the NPSL
* license itself can be found at https://nmap.org/npsl/ . This header
* summarizes some key points from the Nmap license, but is no substitute
* for the actual license text.
*
* Nmap is generally free for end users to download and use themselves,
* including commercial use. It is available from https://nmap.org.
*
* The Nmap license generally prohibits companies from using and
* redistributing Nmap in commercial products, but we sell a special Nmap
* OEM Edition with a more permissive license and special features for this
* purpose. See https://nmap.org/oem/
*
* If you have received a written Nmap license agreement or contract stating
* terms other than these (such as an Nmap OEM license), you may choose to
* use and redistribute Nmap under those terms instead.
*
* The official Nmap Windows builds include the Npcap software
* (https://npcap.com) for packet capture and transmission. It is under
* separate license terms which forbid redistribution without special
* permission. So the official Nmap Windows builds may not be redistributed
* without special permission (such as an Nmap OEM license).
*
* Source is provided to this software because we believe users have a right
* to know exactly what a program is going to do before they run it. This
* also allows you to audit the software for security holes.
*
* Source code also allows you to port Nmap to new platforms, fix bugs, and
* add new features. You are highly encouraged to submit your changes as a
* Github PR or by email to the dev@nmap.org mailing list for possible
* incorporation into the main distribution. Unless you specify otherwise,
* it is understood that you are offering us very broad rights to use your
* submissions as described in the Nmap Public Source License Contributor
* Agreement. This is important because we fund the project by selling
* licenses with various terms, and also because the inability to relicense
* code has caused devastating problems for other Free Software projects
* (such as KDE and NASM).
*
* The free version of Nmap is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Warranties,
* indemnification and commercial support are all available through the
* Npcap OEM program--see https://nmap.org/oem/
*
***************************************************************************/
#include "allowdeny_watcher.h"
#include "ncat_core.h"
#include "util.h"
#include "sys_wrap.h"
#ifdef WIN32
/* Windows headers */
#include <windows.h>
#else /* POSIX */
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#if defined(__linux__)
#include <sys/inotify.h>
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
#include <sys/event.h>
#include <fcntl.h>
#endif
#endif
/* ------------------------------------------------------------------------- */
/* Internal helpers */
/* ------------------------------------------------------------------------- */
struct watcher_paths {
char *allow_path;
char *deny_path;
};
#ifndef WIN32
pthread_mutex_t g_allowdeny_mutex = PTHREAD_MUTEX_INITIALIZER;
static void reload_rules(const struct watcher_paths *wp)
{
/* Build new addrsets */
struct addrset *new_allow = NULL;
struct addrset *new_deny = NULL;
if (wp->allow_path) {
FILE *fd = fopen(wp->allow_path, "r");
if (fd) {
new_allow = addrset_new();
if (!addrset_add_file(new_allow, fd, o.af, !o.nodns)) {
loguser("[WARN] Error parsing allow file %s, keeping old set.\n", wp->allow_path);
addrset_free(new_allow);
new_allow = NULL;
}
fclose(fd);
}
}
if (wp->deny_path) {
FILE *fd = fopen(wp->deny_path, "r");
if (fd) {
new_deny = addrset_new();
if (!addrset_add_file(new_deny, fd, o.af, !o.nodns)) {
loguser("[WARN] Error parsing deny file %s, keeping old set.\n", wp->deny_path);
addrset_free(new_deny);
new_deny = NULL;
}
fclose(fd);
}
}
/* Swap in atomically under mutex */
pthread_mutex_lock(&g_allowdeny_mutex);
if (new_allow) {
addrset_free(o.allowset);
o.allowset = new_allow;
}
if (new_deny) {
addrset_free(o.denyset);
o.denyset = new_deny;
}
pthread_mutex_unlock(&g_allowdeny_mutex);
/* Log reload time */
time_t now = time(NULL);
char tsbuf[32];
struct tm tmval;
localtime_r(&now, &tmval);
strftime(tsbuf, sizeof(tsbuf), "%Y-%m-%d %H:%M:%S", &tmval);
loguser("[INFO] Re-loaded allow/deny rules (modified at %s)\n", tsbuf);
}
/* ---------------------- LINUX INOTIFY IMPLEMENTATION -------------------- */
#if defined(__linux__)
static void *watcher_thread(void *arg)
{
struct watcher_paths *wp = (struct watcher_paths *)arg;
int infd = inotify_init1(IN_NONBLOCK);
if (infd < 0) {
loguser("[WARN] Failed to init inotify: %s\n", strerror(errno));
free(wp);
return NULL;
}
int wd_allow = -1, wd_deny = -1;
if (wp->allow_path)
wd_allow = inotify_add_watch(infd, wp->allow_path, IN_CLOSE_WRITE | IN_MOVED_TO | IN_MOVE_SELF | IN_DELETE_SELF);
if (wp->deny_path)
wd_deny = inotify_add_watch(infd, wp->deny_path, IN_CLOSE_WRITE | IN_MOVED_TO | IN_MOVE_SELF | IN_DELETE_SELF);
if (wd_allow < 0 && wp->allow_path)
loguser("[WARN] Cannot watch %s: %s\n", wp->allow_path, strerror(errno));
if (wd_deny < 0 && wp->deny_path)
loguser("[WARN] Cannot watch %s: %s\n", wp->deny_path, strerror(errno));
/* Main loop */
const size_t bufsize = 4096;
char *buf = (char *)safe_malloc(bufsize);
while (1) {
ssize_t len = read(infd, buf, bufsize);
if (len <= 0) {
if (errno == EAGAIN || errno == EINTR) {
/* Sleep briefly to avoid busy loop */
usleep(200 * 1000);
continue;
}
else {
break;
}
}
for (char *p = buf; p < buf + len;) {
struct inotify_event *ev = (struct inotify_event *)p;
if (ev->mask & (IN_CLOSE_WRITE | IN_MOVED_TO | IN_MOVE_SELF | IN_DELETE_SELF)) {
reload_rules(wp);
}
p += sizeof(struct inotify_event) + ev->len;
}
}
free(buf);
close(infd);
free(wp);
return NULL;
}
#endif /* linux */
/* ---------------------- BSD KQUEUE IMPLEMENTATION ---------------------- */
#if !defined(WIN32) && (defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__))
static void *watcher_thread(void *arg)
{
struct watcher_paths *wp = (struct watcher_paths *)arg;
int kq = kqueue();
if (kq < 0) {
loguser("[WARN] kqueue init failed: %s\n", strerror(errno));
free(wp);
return NULL;
}
/* Watch the *directory* containing the file instead of the file itself.
This reliably catches atomic-replace (mv) sequences which may not
generate NOTE_WRITE for the original vnode. */
int fd_allow = -1, fd_deny = -1;
if (wp->allow_path) {
char *adir = Strdup(wp->allow_path);
char *slash = strrchr(adir, '/');
if (slash)
*slash = '\0';
fd_allow = open(adir[0] ? adir : ".", O_EVTONLY);
free(adir);
}
if (wp->deny_path) {
char *ddir = Strdup(wp->deny_path);
char *slash = strrchr(ddir, '/');
if (slash)
*slash = '\0';
fd_deny = open(ddir[0] ? ddir : ".", O_EVTONLY);
free(ddir);
}
struct kevent evlist[2];
int nev = 0;
const uint32_t flags = NOTE_WRITE | NOTE_DELETE | NOTE_EXTEND | NOTE_RENAME;
if (fd_allow >= 0)
EV_SET(&evlist[nev++], fd_allow, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, flags, 0, NULL);
if (fd_deny >= 0)
EV_SET(&evlist[nev++], fd_deny, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, flags, 0, NULL);
if (nev == 0) {
close(kq);
free(wp);
return NULL;
}
if (kevent(kq, evlist, nev, NULL, 0, NULL) < 0) {
loguser("[WARN] kevent register failed: %s\n", strerror(errno));
close(kq);
free(wp);
return NULL;
}
for (;;) {
struct kevent ev;
int n = kevent(kq, NULL, 0, &ev, 1, NULL);
if (n == -1) {
if (errno == EINTR)
continue;
break;
}
if (n > 0) {
reload_rules(wp);
}
}
if (fd_allow >= 0) close(fd_allow);
if (fd_deny >= 0) close(fd_deny);
close(kq);
free(wp);
return NULL;
}
#endif /* BSD */
#endif /* !WIN32 */
/* ---------------------- WINDOWS IMPLEMENTATION ------------------------- */
#ifdef WIN32
static DWORD WINAPI watcher_thread_win(LPVOID param)
{
struct watcher_paths *wp = (struct watcher_paths *)param;
/* Extract directory path */
char dir[MAX_PATH];
strncpy(dir, wp->allow_path ? wp->allow_path : wp->deny_path, MAX_PATH - 1);
dir[MAX_PATH-1] = '\0';
char *lastSep = strrchr(dir, '\\');
if (!lastSep) lastSep = strrchr(dir, '/');
if (lastSep) *lastSep = '\0';
HANDLE hDir = CreateFileA(dir, FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hDir == INVALID_HANDLE_VALUE) {
loguser("[WARN] Unable to watch directory %s (err=%lu)\n", dir, GetLastError());
free(wp);
return 0;
}
BYTE buf[1024];
DWORD bytesReturned;
while (1) {
if (!ReadDirectoryChangesW(hDir, buf, sizeof(buf), FALSE,
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME,
&bytesReturned, NULL, NULL)) {
Sleep(200);
continue;
}
reload_rules(wp);
}
CloseHandle(hDir);
free(wp);
return 0;
}
#endif /* WIN32 */
/* ------------------------------------------------------------------------- */
/* Public API */
/* ------------------------------------------------------------------------- */
int start_allowdeny_watcher(const char *allow_path, const char *deny_path)
{
/* If the feature is not requested, simply do nothing. Caller will ensure
not to call us when flag is absent, but be tolerant. */
if (allow_path == NULL && deny_path == NULL)
return 0;
struct watcher_paths *wp = (struct watcher_paths *)safe_malloc(sizeof(*wp));
wp->allow_path = allow_path ? Strdup(allow_path) : NULL;
wp->deny_path = deny_path ? Strdup(deny_path) : NULL;
#if defined(__linux__)
pthread_t tid;
if (pthread_create(&tid, NULL, watcher_thread, wp) != 0) {
bye("Failed to create watcher thread: %s", strerror(errno));
}
pthread_detach(tid);
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
pthread_t tid;
if (pthread_create(&tid, NULL, watcher_thread, wp) != 0) {
bye("Failed to create watcher thread: %s", strerror(errno));
}
pthread_detach(tid);
#elif defined(WIN32)
HANDLE th = CreateThread(NULL, 0, watcher_thread_win, wp, 0, NULL);
if (th == NULL) {
loguser("[WARN] Failed to start watcher thread (err=%lu)\n", GetLastError());
free(wp);
return -1;
}
CloseHandle(th);
#endif
return 0;
}

87
ncat/allowdeny_watcher.h Normal file
View file

@ -0,0 +1,87 @@
/***************************************************************************
* allowdeny_watcher.h -- Platform-independent watcher for allow/deny ACL *
***********************IMPORTANT NMAP LICENSE TERMS************************
*
* The Nmap Security Scanner is (C) 1996-2025 Nmap Software LLC ("The Nmap
* Project"). Nmap is also a registered trademark of the Nmap Project.
*
* This program is distributed under the terms of the Nmap Public Source
* License (NPSL). The exact license text applying to a particular Nmap
* release or source code control revision is contained in the LICENSE
* file distributed with that version of Nmap or source code control
* revision. More Nmap copyright/legal information is available from
* https://nmap.org/book/man-legal.html, and further information on the NPSL
* license itself can be found at https://nmap.org/npsl/ . This header
* summarizes some key points from the Nmap license, but is no substitute
* for the actual license text.
*
* Nmap is generally free for end users to download and use themselves,
* including commercial use. It is available from https://nmap.org.
*
* The Nmap license generally prohibits companies from using and
* redistributing Nmap in commercial products, but we sell a special Nmap
* OEM Edition with a more permissive license and special features for this
* purpose. See https://nmap.org/oem/
*
* If you have received a written Nmap license agreement or contract stating
* terms other than these (such as an Nmap OEM license), you may choose to
* use and redistribute Nmap under those terms instead.
*
* The official Nmap Windows builds include the Npcap software
* (https://npcap.com) for packet capture and transmission. It is under
* separate license terms which forbid redistribution without special
* permission. So the official Nmap Windows builds may not be redistributed
* without special permission (such as an Nmap OEM license).
*
* Source is provided to this software because we believe users have a right
* to know exactly what a program is going to do before they run it. This
* also allows you to audit the software for security holes.
*
* Source code also allows you to port Nmap to new platforms, fix bugs, and
* add new features. You are highly encouraged to submit your changes as a
* GitHub PR or by email to the dev@nmap.org mailing list for possible
* incorporation into the main distribution. Unless you specify otherwise,
* it is understood that you are offering us very broad rights to use your
* submissions as described in the Nmap Public Source License Contributor
* Agreement. This is important because we fund the project by selling
* licenses with various terms, and also because the inability to relicense
* code has caused devastating problems for other Free Software projects
* (such as KDE and NASM).
*
* The free version of Nmap is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Warranties,
* indemnification and commercial support are all available through the
* Npcap OEM program--see https://nmap.org/oem/
*
***************************************************************************/
#ifndef ALLOWDENY_WATCHER_H
#define ALLOWDENY_WATCHER_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* Start background watcher thread that monitors the --allow/--deny host files.
*
* allow_path: Path to the file supplied to --allowfile, or NULL if none.
* deny_path : Path to the file supplied to --denyfile, or NULL if none.
*
* Returns 0 on success, -1 on failure.
*/
int start_allowdeny_watcher(const char *allow_path, const char *deny_path);
#ifdef __cplusplus
}
#endif
#ifndef WIN32
#include <pthread.h>
/* Global mutex guarding access to o.allowset / o.denyset */
extern pthread_mutex_t g_allowdeny_mutex;
#endif
#endif /* ALLOWDENY_WATCHER_H */

View file

@ -64,6 +64,12 @@ AC_SEARCH_LIBS(gethostbyname, nsl)
AC_SEARCH_LIBS(dlopen, dl)
AC_CHECK_FUNCS([dup2 gettimeofday inet_ntoa memset mkstemp select socket strcasecmp strchr strdup strerror strncasecmp strtol])
# Detect pthread library (for allow/deny watcher thread)
AC_SEARCH_LIBS([pthread_create], [pthread], [PTHREAD_LIBS="$ac_cv_search_pthread_create"], [PTHREAD_LIBS=""])
# Sanitize possible 'none required' response which indicates pthread symbols are in libc
AS_IF([test "x$PTHREAD_LIBS" = "xnone required"], [PTHREAD_LIBS=""])
AC_SUBST([PTHREAD_LIBS])
# If they didn't specify it, we try to find it
if test "$use_openssl" = "yes" -a -z "$specialssldir" ; then
AC_CHECK_HEADER(openssl/ssl.h,,

View file

@ -79,11 +79,12 @@ Options taking a time assume seconds\&. Append \*(Aqms\*(Aq for milliseconds,
\-\-send\-only Only send data, ignoring received; quit on EOF
\-\-recv\-only Only receive data, never send anything
\-\-no\-shutdown Continue half\-duplex when receiving EOF on stdin
\-q <time> After EOF on stdin, wait <time> then quit\&.
\-q <time> After EOF on stdin, wait <time> then quit\&.
\-\-allow Allow only given hosts to connect to Ncat
\-\-allowfile A file of hosts allowed to connect to Ncat
\-\-deny Deny given hosts from connecting to Ncat
\-\-denyfile A file of hosts denied from connecting to Ncat
\-\-watch\-allow\-deny Re\-read allow/deny files automatically on change
\-\-broker Enable Ncat\*(Aqs connection brokering mode
\-\-chat Start a simple Ncat chat server
\-\-proxy <addr[:port]> Specify address of host to proxy through
@ -442,6 +443,22 @@ takes precedence\&. Host specifications follow the same syntax used by Nmap\&.
This is the same functionality as
\fB\-\-deny\fR, except that excluded hosts are provided in a new\-line delimited deny file, rather than directly on the command line\&.
.RE
.PP
\fB\-\-watch\-allow\-deny\fR (Watch allow/deny files)
.RS 4
When this option is given in listen mode, Ncat keeps an eye on the files that
were provided with
\fB\-\-allowfile\fR
and
\fB\-\-denyfile\fR.
If either file is modified, Ncat automatically reloads the in\-memory access
control lists so that newly accepted connections are evaluated against the
updated rules. Existing connections are unaffected. The implementation uses
efficient, eventdriven file notification facilities on each platform
(\fBinotify\fR on Linux, \fBkqueue\fR on BSD/macOS, and
\fBReadDirectoryChangesW\fR on Windows), so it has no impact on performance
when the option is not selected.
.RE
.SH "TIMING OPTIONS"
.PP
These options accept a

View file

@ -123,6 +123,7 @@ void options_init(void)
o.allowset = addrset_new();
o.denyset = addrset_new();
o.httpserver = 0;
o.watch_allow_deny = 0;
o.nsock_engine = 0;

View file

@ -128,6 +128,8 @@ struct options {
struct addrset *allowset;
struct addrset *denyset;
int httpserver;
/* When true, automatically reload allow/deny files on modification. */
int watch_allow_deny;
int nsock_engine;
/* Output messages useful for testing to stderr? */
int test;

View file

@ -63,6 +63,7 @@
#include "ncat.h"
#include "util.h"
#include "sys_wrap.h"
#include "allowdeny_watcher.h"
#include <getopt.h>
@ -202,7 +203,8 @@ static void host_list_to_set(struct addrset *set, struct host_list_node *list)
char *spec, *commalist;
commalist = node->spec;
while ((spec = strtok(commalist, ",")) != NULL) {
while ((spec = strtok(commalist, ",")) != NULL)
{
commalist = NULL;
if (!addrset_add_spec(set, spec, o.af, !o.nodns))
bye("error in host specification \"%s\".", node->spec);
@ -224,6 +226,10 @@ int main(int argc, char *argv[])
struct host_list_node *allow_host_list = NULL;
struct host_list_node *deny_host_list = NULL;
/* Remember first allow/deny filename (if any) for watcher */
char *allowfile_path = NULL;
char *denyfile_path = NULL;
unsigned short proxyport;
/* vsock ports are 32 bits, so port variables must be at least that wide. */
unsigned int max_port = 65535;
@ -299,6 +305,7 @@ int main(int argc, char *argv[])
{"ssl-ciphers", optional_argument, NULL, 0},
{"ssl-alpn", optional_argument, NULL, 0},
#endif
{"watch-allow-deny",no_argument, NULL, 0},
{0, 0, 0, 0}
};
@ -483,10 +490,14 @@ int main(int argc, char *argv[])
} else if (strcmp(long_options[option_index].name, "allowfile") == 0) {
o.allow = 1;
host_list_add_filename(&allow_host_list, optarg);
if (!allowfile_path) allowfile_path = optarg;
} else if (strcmp(long_options[option_index].name, "deny") == 0) {
host_list_add_spec(&deny_host_list, optarg);
} else if (strcmp(long_options[option_index].name, "denyfile") == 0) {
host_list_add_filename(&deny_host_list, optarg);
if (!denyfile_path) denyfile_path = optarg;
} else if (strcmp(long_options[option_index].name, "watch-allow-deny") == 0) {
o.watch_allow_deny = 1;
} else if (strcmp(long_options[option_index].name, "append-output") == 0) {
o.append = 1;
} else if (strcmp(long_options[option_index].name, "sctp") == 0) {
@ -626,6 +637,7 @@ int main(int argc, char *argv[])
" --allowfile A file of hosts allowed to connect to Ncat\n"
" --deny Deny given hosts from connecting to Ncat\n"
" --denyfile A file of hosts denied from connecting to Ncat\n"
" --watch-allow-deny Reload allow/deny files automatically on change\n"
" --broker Enable Ncat's connection brokering mode\n"
" --chat Start a simple Ncat chat server\n"
" --proxy <addr[:port]> Specify address of host to proxy through\n"
@ -833,6 +845,11 @@ int main(int argc, char *argv[])
host_list_to_set(o.denyset, deny_host_list);
host_list_free(deny_host_list);
/* Launch background watcher if requested and at least one filename is supplied */
if (o.watch_allow_deny) {
start_allowdeny_watcher(allowfile_path, denyfile_path);
}
int rc;
int num_ports = 0;
if (srcport != -1 && o.listen) {

View file

@ -0,0 +1,45 @@
#!/bin/sh
# Test script for Ncat --watch-allow-deny / auto-reload feature
# Part of the Nmap Project - see LICENSE for terms
set -e
PORT=41001
TMP_ALLOW=$(mktemp ncat_allow_XXXX)
trap 'rm -f "$TMP_ALLOW"; kill $SERVER_PID 2>/dev/null || true' EXIT
# Initial allow rule permits localhost
printf "127.0.0.1\n" > "$TMP_ALLOW"
# Start Ncat server in background
../ncat -l $PORT --keep-open --recv-only --allowfile "$TMP_ALLOW" --watch-allow-deny 2>/dev/null &
SERVER_PID=$!
# Give it a moment to start
sleep 1
# First connection should succeed
if ! ../ncat 127.0.0.1 $PORT -z 2>/dev/null; then
echo "Initial connection unexpectedly failed"
exit 1
fi
# Overwrite via temp move to avoid partial read races
TMP2=$(mktemp ncat_allow2_XXXX)
printf "192.0.2.1\n" > "$TMP2"
mv "$TMP2" "$TMP_ALLOW"
# Wait up to ~10 s (20 × 0.5 s)
tries=20
while [ $tries -gt 0 ]; do
if printf "ping\n" | ../ncat 127.0.0.1 $PORT -w1 2>/dev/null; then
# Still allowed -> retry after short delay
sleep 0.5
tries=$((tries-1))
else
echo "PASS watch-allow-deny"
exit 0
fi
done
echo "Connection still allowed after watcher delay"
exit 1

View file

@ -72,6 +72,9 @@
#include <stdarg.h>
#include <string.h>
#include <stddef.h>
#ifndef WIN32
#include <pthread.h>
#endif
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
@ -538,14 +541,23 @@ unsigned char *buildsrcrte(struct in_addr dstaddr, struct in_addr routes[],
int allow_access(const union sockaddr_u *su)
{
/* A host not in the allow set is denied, but only if the --allow or
--allowfile option was given. */
if (o.allow && !addrset_contains(o.allowset, &su->sockaddr))
return 0;
if (addrset_contains(o.denyset, &su->sockaddr))
return 0;
#ifndef WIN32
extern pthread_mutex_t g_allowdeny_mutex;
pthread_mutex_lock(&g_allowdeny_mutex);
#endif
return 1;
int allowed = 1;
/* A host not in the allow set is denied, but only if --allow/--allowfile was used. */
if (o.allow && !addrset_contains(o.allowset, &su->sockaddr))
allowed = 0;
if (addrset_contains(o.denyset, &su->sockaddr))
allowed = 0;
#ifndef WIN32
pthread_mutex_unlock(&g_allowdeny_mutex);
#endif
return allowed;
}
/*