/*
 * netcap.c - A program that lists network apps with capabilities
 * Copyright (c) 2009-10,2012,2019-20,2024,2026 Red Hat Inc.
 * All Rights Reserved.
 *
 * This software may be freely redistributed and/or modified under the
 * terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * This program 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING. If not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor
 * Boston, MA 02110-1335, USA.
 *
 * Authors:
 *   Steve Grubb <sgrubb@redhat.com>
 *
 * The /proc/net/tcp|udp|raw parsing code was borrowed from netstat.c
 */

#include "config.h"
#include <arpa/inet.h>
#ifdef HAVE_NETCAP_ADVANCED
#include <linux/inet_diag.h>
#include <linux/netlink.h>
#include <linux/sock_diag.h>
#ifdef HAVE_NETCAP_VSOCK
#include <sys/socket.h>
#include <linux/vm_sockets.h>
#ifdef HAVE_LINUX_VM_SOCKETS_DIAG_H
#include <linux/vm_sockets_diag.h>
#endif
#endif
#endif
#include <limits.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <pwd.h>
#include "cap-ng.h"
#include "proc-account.h"
#include "proc-llist.h"
#include "netcap-advanced.h"
#include "proc-sanitize.h"

#ifndef NETCAP_NO_MAIN
static llist l;
static int perm_warn = 0, header = 0;
static uid_t last_uid = (uid_t)-1;
static char *tacct = NULL;
#endif

#ifdef NETCAP_TEST
#define NETCAP_TESTABLE
#else
#define NETCAP_TESTABLE static
#endif

/*
 * VSOCK is the production user of this parser. Keep it available to the
 * direct utility test even when configure disables all advanced support.
 */
#if defined(HAVE_NETCAP_VSOCK) || defined(NETCAP_TEST)
NETCAP_TESTABLE int parse_u32_hex_or_dec(const char *s, unsigned int *out)
	__attr_access ((__read_only__, 1))
	__attr_access ((__write_only__, 2))
	__wur;
#endif

#ifndef NETCAP_NO_MAIN
/*
 * utility_logic_test only exercises parse_u32_hex_or_dec(). Keep the rest
 * of netcap out of NETCAP_NO_MAIN builds so the test object does not carry
 * main-program helpers that trigger unused-function warnings.
 */

static void usage(void)
{
	fprintf(stderr, "usage: netcap [--advanced "
		"[--interface IFACE] [--list-interfaces] [--json] "
		"[--no-color]]\n");
	exit(1);
}

static int collect_process_info(void)
{
	DIR *d, *f;
	struct dirent *ent;
	d = opendir("/proc");
	if (d == NULL) {
		fprintf(stderr, "Can't open /proc: %s\n", strerror(errno));
		return 1;
	}
	while (( ent = readdir(d) )) {
		FILE *sf;
		int pid, ppid;
		capng_results_t caps;
		char buf[100];
		char *safe_cmd;
		char *tmp, cmd[16], state;
		char *text = NULL, *bounds = NULL, *ambient = NULL;
		int fd, len;
		uid_t euid = (uid_t)-1;

		// Skip non-process dir entries
		if(*ent->d_name<'0' || *ent->d_name>'9')
			continue;
		errno = 0;
		pid = strtol(ent->d_name, NULL, 10);
		if (errno)
			continue;

		// Parse up the stat file for the proc
		snprintf(buf, sizeof(buf), "/proc/%d/stat", pid);
		fd = open(buf, O_RDONLY|O_CLOEXEC, 0);
		if (fd < 0)
			continue;
		len = read(fd, buf, sizeof buf - 1);
		close(fd);
		if (len < 40)
			continue;
		buf[len] = 0;
		tmp = strrchr(buf, ')');
		if (tmp)
			*tmp = 0;
		else
			continue;
		memset(cmd, 0, sizeof(cmd));
		if (sscanf(buf, "%d (%15c", &ppid, cmd) != 2)
			continue;
		if (sscanf(tmp+2, "%c %d", &state, &ppid) != 2)
			continue;

		// Skip kthreads
		if (pid == 2 || ppid == 2)
			continue;

		// now get the capabilities
		capng_clear(CAPNG_SELECT_ALL);
		capng_setpid(pid);
		if (capng_get_caps_process())
			continue;
		caps = capng_have_capabilities(CAPNG_SELECT_CAPS);
		if (caps <= CAPNG_NONE)
			continue;
		if (caps == CAPNG_FULL) {
			text = strdup("full");
			if (!text) {
				fprintf(stderr, "Out of memory\n");
				continue;
			}
		} else {
			text = capng_print_caps_text(CAPNG_PRINT_BUFFER,
					CAPNG_PERMITTED);
			if (text == NULL) {
				fprintf(stderr, "Out of memory doing pid %d\n",
					pid);
				continue;
			}
		}

		// Get the effective uid
		snprintf(buf, sizeof(buf), "/proc/%d/status", pid);
		sf = fopen(buf, "rte");
		if (sf != NULL) {
			int line = 0;
			__fsetlocking(sf, FSETLOCKING_BYCALLER);
			while (fgets(buf, sizeof(buf), sf)) {
				if (line == 0) {
					line++;
					continue;
				}
				if (memcmp(buf, "Uid:", 4) == 0) {
					uid_t id;
					if (sscanf(buf, "Uid: %u %u",
						&id, &euid) == 2)
						break;
				}
			}
			fclose(sf);
		}

		caps = capng_have_capabilities(CAPNG_SELECT_AMBIENT);
		if (caps > CAPNG_NONE)
			ambient = strdup("@");
		else
			ambient = strdup("");
		if (!ambient) {
			fprintf(stderr, "Out of memory\n");
			free(text);
			continue;
		}

		// Now record the bounding set information
		caps = capng_have_capabilities(CAPNG_SELECT_BOUNDS);
		if (caps > CAPNG_NONE)
			bounds = strdup("+");
		else
			bounds = strdup("");
		if (!bounds) {
			fprintf(stderr, "Out of memory\n");
			free(text);
			free(ambient);
			continue;
		}

		// Now lets get the inodes each process has open
		snprintf(buf, sizeof(buf), "/proc/%d/fd", pid);
		f = opendir(buf);
		if (f == NULL) {
			if (errno == EACCES) {
				if (perm_warn == 0) {
					fprintf(stderr,
						"You may need to be root to "
						"get a full report\n");
					perm_warn = 1;
				}
			} else
				fprintf(stderr, "Can't open %s: %s\n", buf,
					strerror(errno));
			free(text);
			free(bounds);
			free(ambient);
			continue;
		}
		// For each file in the fd dir...
		struct dirent *fd_ent;
		while (( fd_ent = readdir(f) )) {
			char line[256], ln[256], *s, *e;
			unsigned long inode;
			lnode node;
			int llen;

			if (fd_ent->d_name[0] == '.')
				continue;
			snprintf(ln, 256, "%s/%s", buf, fd_ent->d_name);
			if ((llen = readlink(ln, line, sizeof(line)-1)) < 0)
				continue;
			line[llen] = 0;

			// Only look at the socket entries
			if (memcmp(line, "socket:", 7) == 0) {
				// Type 1 sockets
				s = strchr(line+7, '[');
				if (s == NULL)
					continue;
				s++;
				e = strchr(s, ']');
				if (e == NULL)
					continue;
				*e = 0;
			} else if (memcmp(line, "[0000]:", 7) == 0) {
				// Type 2 sockets
				s = line + 8;
			} else
				continue;
			errno = 0;
			inode = strtoul(s, NULL, 10);
			if (errno)
				continue;
			safe_cmd = sanitize_untrusted_field(cmd);
			if (!safe_cmd)
				continue;
			node.ppid = ppid;
			node.pid = pid;
			node.uid = euid;
			node.cmd = safe_cmd;
			node.inode = inode;
			node.capabilities = strdup(text);
			node.bounds = strdup(bounds);
			node.ambient = strdup(ambient);
			if (node.cmd && node.capabilities && node.bounds &&
			    node.ambient)
				// We make one entry for each socket inode
				list_append(&l, &node);
			else {
				free(node.cmd);
				free(node.capabilities);
				free(node.bounds);
				free(node.ambient);
			}
		}
		closedir(f);
		free(text);
		free(bounds);
		free(ambient);
	}
	closedir(d);
	return 0;
}

static void report_finding(const lnode *n, unsigned int port, const char *type,
			   const char *ifc)
{
	// And print out anything with capabilities
	if (header == 0) {
		printf("%-7s %-7s %-16s %-15s %-8s %-15s %s\n",
			"ppid", "pid", "acct", "command", "type", "port",
			"capabilities");
		header = 1;
	}
	netcap_update_account_cache(n->uid, &last_uid, (const char **)&tacct);
	if (tacct) {
		printf("%-7d %-7d %-16s", n->ppid, n->pid, tacct);
	} else
		printf("%-7d %-7d %-16u", n->ppid, n->pid,
		       (unsigned int)last_uid);
	printf(" %-15s %-8s", n->cmd, type);
	if (ifc)
		printf(" %-15s", ifc);
	else
		printf(" %-15u", port);
	printf(" %s %s%s\n", n->capabilities, n->ambient, n->bounds);
}

/*
 * Multiple processes can legitimately hold descriptors for the same socket
 * inode. Report every owner so preforked or reuseport listeners are not
 * collapsed into a single row.
 */
static void report_inode_findings(unsigned long inode, unsigned int port,
				  const char *type, const char *ifc)
{
	lnode *n;

	for (n = list_find_inode(&l, inode); n; n = list_next_inode(&l, inode))
		report_finding(n, port, type, ifc);
}

static void read_net(const char *proc, const char *type, int use_local_port)
{
	int line = 0;
	FILE *f;
	char buf[256];
	unsigned long rxq, txq, time_len, retr, inode;
	unsigned int local_port, rem_port, state, timer_run;
	int d, uid, timeout;
	char rem_addr[128], local_addr[128], more[512];

	f = fopen(proc, "rte");
	if (f == NULL) {
		if (errno != ENOENT)
			fprintf(stderr, "Can't open %s: %s\n",
				proc, strerror(errno));
		return;
	}
	__fsetlocking(f, FSETLOCKING_BYCALLER);
	while (fgets(buf, sizeof(buf), f)) {
		if (line == 0) {
			line++;
			continue;
		}
		more[0] = 0;
		if (sscanf(buf, "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X "
			"%lX:%lX %X:%lX %lX %d %d %lu %511s\n",
			&d, local_addr, &local_port, rem_addr, &rem_port,
			&state, &txq, &rxq, &timer_run, &time_len, &retr,
			&uid, &timeout, &inode, more) < 14)
			continue;
		report_inode_findings(inode, use_local_port ? local_port : 0,
				      type, NULL);
	}
	fclose(f);
}

// Caller must have buffer >= 65 bytes
static void get_interface(unsigned int iface, char *ifc)
{
	unsigned int line = 0;
	FILE *f;
	char buf[256], more[256];

	// Terminate the interface in case of error
	*ifc = 0;

	// Offset the interface number since header is 2 lines long
	iface += 2;

	f = fopen("/proc/net/dev", "rte");
	if (f == NULL) {
		if (errno != ENOENT)
			fprintf(stderr, "Can't open /proc/net/dev: %s\n",
				strerror(errno));
		return;
	}
	__fsetlocking(f, FSETLOCKING_BYCALLER);
	while (fgets(buf, sizeof(buf), f)) {
		if (line == iface) {
			char *c;
			char *safe_ifc;

			sscanf(buf, "%16s: %255s\n", ifc, more);
			c = strchr(ifc, ':');
			if (c)
				*c = 0;
			safe_ifc = sanitize_untrusted_field(ifc);
			if (safe_ifc) {
				strncpy(ifc, safe_ifc, 64);
				ifc[64] = '\0';
				free(safe_ifc);
			} else
				*ifc = 0;
			fclose(f);
			return;
		}
		line++;
	}
	fclose(f);
}

static void read_packet(void)
{
	int line = 0;
	FILE *f;
	char buf[256];
	unsigned long sk, inode;
	unsigned int ref_cnt, type, proto, iface, r, rmem, uid;
	char more[256], ifc[65];

	f = fopen("/proc/net/packet", "rte");
	if (f == NULL) {
		if (errno != ENOENT)
			fprintf(stderr, "Can't open /proc/net/packet: %s\n",
				strerror(errno));
		return;
	}
	__fsetlocking(f, FSETLOCKING_BYCALLER);
	while (fgets(buf, sizeof(buf), f)) {
		if (line == 0) {
			line++;
			continue;
		}
		more[0] = 0;
		if (sscanf(buf, "%lX %u %u %X %u %u %u %u %lu %255s\n",
			&sk, &ref_cnt, &type, &proto, &iface,
			&r, &rmem, &uid, &inode, more) < 9)
			continue;
		get_interface(iface, ifc);
		report_inode_findings(inode, 0, "pkt", ifc);
	}
	fclose(f);
}
#endif

#if defined(HAVE_NETCAP_VSOCK) || defined(NETCAP_TEST)
NETCAP_TESTABLE int parse_u32_hex_or_dec(const char *s, unsigned int *out)
{
	char *end;
	unsigned long v;
	int base = 10;
	const char *p;

	/* strtoul accepts signs, but these kernel fields are unsigned. */
	if (*s == '+' || *s == '-')
		return -1;
	for (p = s; *p; p++) {
		if ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')) {
			base = 16;
			break;
		}
	}
	if (strncmp(s, "0x", 2) == 0 || strncmp(s, "0X", 2) == 0)
		base = 16;
	if (base == 10 && strlen(s) > 3 && s[0] == '0')
		base = 16;
	errno = 0;
	v = strtoul(s, &end, base);
	if (errno == ERANGE || end == s || *end)
		return -1;
	/*
	 * /proc and diag inputs are meant to fit in u32 fields. On platforms
	 * where unsigned long is wider, reject values that would be truncated.
	 */
	if (v > UINT_MAX)
		return -1;
	*out = (unsigned int)v;
	return 0;
}
#endif

#ifdef HAVE_NETCAP_ADVANCED
#ifndef NETCAP_NO_MAIN
static int read_diag_messages(int fd, int proto, const char *type)
{
	char buf[8192];
	ssize_t len;
	struct sockaddr_nl nladdr;
	socklen_t nladdr_len;

	while (1) {
		nladdr_len = sizeof(nladdr);
		memset(&nladdr, 0, sizeof(nladdr));
		len = recvfrom(fd, buf, sizeof(buf), 0,
				(struct sockaddr *)&nladdr, &nladdr_len);
		if (len < 0) {
			if (errno == EINTR)
				continue;
			return -1;
		}
		if (len == 0)
			return -1;
		if (nladdr_len < sizeof(nladdr) || nladdr.nl_pid != 0)
			continue;

		struct nlmsghdr *nlh;
		unsigned int rem;

		if (len > UINT_MAX)
			return -1;
		rem = (unsigned int)len;

		for (nlh = (struct nlmsghdr *)buf;
		     NLMSG_OK(nlh, rem);
		     nlh = NLMSG_NEXT(nlh, rem)) {
			struct inet_diag_msg *r;
			unsigned int port;

			if (nlh->nlmsg_type == NLMSG_DONE)
				return 0;
			if (nlh->nlmsg_type == NLMSG_ERROR) {
				struct nlmsgerr *e;

				if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*e)))
					return -1;
				e = NLMSG_DATA(nlh);
				if (e->error == 0)
					continue;
				errno = -e->error;
				return -1;
			}
			if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*r)))
				continue;

			r = NLMSG_DATA(nlh);
			if (!list_find_inode(&l, r->idiag_inode))
				continue;
			port = ntohs(r->id.idiag_sport);
			if (!port)
				continue;

			if (proto == IPPROTO_SCTP || proto == IPPROTO_DCCP)
				report_inode_findings(r->idiag_inode, port,
						      type, NULL);
		}
	}
}

static int read_diag_for_proto_af(int proto, int af, const char *type)
{
	struct {
		struct nlmsghdr nlh;
		struct inet_diag_req_v2 req;
	} req;
	struct sockaddr_nl sa;
	int fd;
	int rc = -1;

	fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
	if (fd < 0)
		return -1;

	memset(&req, 0, sizeof(req));
	req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.req));
	req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
	req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
	req.req.sdiag_family = af;
	req.req.sdiag_protocol = proto;
	req.req.idiag_states = 1U << TCP_LISTEN;

	memset(&sa, 0, sizeof(sa));
	sa.nl_family = AF_NETLINK;
	sa.nl_pid = 0;
	if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
		goto out;

	if (send(fd, &req, req.nlh.nlmsg_len, 0) < 0)
		goto out;

	rc = read_diag_messages(fd, proto, type);
out:
	close(fd);
	return rc;
}

static void read_diag_listeners(void)
{
	int sctp_ok = 0;
	int dccp_ok = 0;

	if (read_diag_for_proto_af(IPPROTO_SCTP, AF_INET, "sctp") == 0)
		sctp_ok = 1;
	if (read_diag_for_proto_af(IPPROTO_SCTP, AF_INET6, "sctp") == 0)
		sctp_ok = 1;
	if (read_diag_for_proto_af(IPPROTO_DCCP, AF_INET, "dccp") == 0)
		dccp_ok = 1;
	if (read_diag_for_proto_af(IPPROTO_DCCP, AF_INET6, "dccp") == 0)
		dccp_ok = 1;

	if (!dccp_ok) {
		read_net("/proc/net/dccp", "dccp", 1);
		read_net("/proc/net/dccp6", "dccp", 1);
	}
	(void)sctp_ok;
}

#ifdef HAVE_NETCAP_VSOCK
#ifdef HAVE_LINUX_VM_SOCKETS_DIAG_H
static int read_vsock_diag_messages(int fd)
{
	char buf[8192];
	ssize_t len;
	struct sockaddr_nl nladdr;
	socklen_t nladdr_len;

	while (1) {
		nladdr_len = sizeof(nladdr);
		memset(&nladdr, 0, sizeof(nladdr));
		len = recvfrom(fd, buf, sizeof(buf), 0,
				(struct sockaddr *)&nladdr, &nladdr_len);
		if (len < 0) {
			if (errno == EINTR)
				continue;
			return -1;
		}
		if (len == 0)
			return -1;
		if (nladdr_len < sizeof(nladdr) || nladdr.nl_pid != 0)
			continue;

		struct nlmsghdr *nlh;
		unsigned int rem;

		if (len > UINT_MAX)
			return -1;
		rem = (unsigned int)len;

		for (nlh = (struct nlmsghdr *)buf;
		     NLMSG_OK(nlh, rem);
		     nlh = NLMSG_NEXT(nlh, rem)) {
			struct vsock_diag_msg *r;

			if (nlh->nlmsg_type == NLMSG_DONE)
				return 0;
			if (nlh->nlmsg_type == NLMSG_ERROR) {
				struct nlmsgerr *e;

				if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*e)))
					return -1;
				e = NLMSG_DATA(nlh);
				if (e->error == 0)
					continue;
				errno = -e->error;
				return -1;
			}
			if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*r)))
				continue;

			r = NLMSG_DATA(nlh);
			if (r->vdiag_family != AF_VSOCK)
				continue;
			if (r->vdiag_type != SOCK_STREAM ||
			    r->vdiag_state != TCP_LISTEN)
				continue;
			if (!list_find_inode(&l, r->vdiag_ino))
				continue;
			if (r->vdiag_src_port == 0)
				continue;

			report_inode_findings(r->vdiag_ino,
					      r->vdiag_src_port, "vsock",
					      NULL);
		}
	}
}

static int read_vsock_diag(void)
{
	struct {
		struct nlmsghdr nlh;
		struct vsock_diag_req req;
	} req;
	struct sockaddr_nl sa;
	int fd;
	int rc = -1;

	fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
	if (fd < 0)
		return -1;

	memset(&req, 0, sizeof(req));
	req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.req));
	req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
	req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
	req.req.sdiag_family = AF_VSOCK;
	req.req.sdiag_protocol = 0;
	req.req.vdiag_states = ~0U;

	memset(&sa, 0, sizeof(sa));
	sa.nl_family = AF_NETLINK;
	sa.nl_pid = 0;

	if (sendto(fd, &req, req.nlh.nlmsg_len, 0,
		   (struct sockaddr *)&sa, sizeof(sa)) < 0)
		goto out;

	rc = read_vsock_diag_messages(fd);
out:
	close(fd);
	return rc;
}
#else
static int read_vsock_diag(void)
{
	errno = EOPNOTSUPP;
	return -1;
}
#endif

static void read_vsock_proc(void)
{
	FILE *f;
	char line[512];

	f = fopen("/proc/net/vsock", "rte");
	if (f == NULL) {
		if (errno != ENOENT)
			fprintf(stderr, "Can't open /proc/net/vsock: %s\n",
				strerror(errno));
		return;
	}
	__fsetlocking(f, FSETLOCKING_BYCALLER);
	while (fgets(line, sizeof(line), f)) {
		char work[512];
		char *tok[24];
		char *save = NULL;
		char *local, *sep, *s;
		int tcnt = 0;
		unsigned long inode;
		unsigned int st, type, cid, port;

		if (strstr(line, "Local") || strstr(line, "local") ||
		    strstr(line, "Num"))
			continue;
		snprintf(work, sizeof(work), "%s", line);
		s = strtok_r(work, " \t\n", &save);
		while (s && tcnt < (int)(sizeof(tok) / sizeof(tok[0]))) {
			tok[tcnt++] = s;
			s = strtok_r(NULL, " \t\n", &save);
		}
		if (tcnt < 5)
			continue;

		local = NULL;
		int i;

		for (i = 0; i < tcnt; i++) {
			if (strchr(tok[i], ':')) {
				local = tok[i];
				break;
			}
		}
		if (!local)
			continue;
		sep = strchr(local, ':');
		if (!sep)
			continue;
		*sep = '\0';
		if (parse_u32_hex_or_dec(local, &cid) ||
		    parse_u32_hex_or_dec(sep + 1, &port))
			continue;

		if (parse_u32_hex_or_dec(tok[tcnt - 2], &st))
			continue;
		if (parse_u32_hex_or_dec(tok[tcnt - 3], &type))
			continue;
		inode = strtoul(tok[tcnt - 1], NULL, 10);
		if (!inode)
			continue;

		if (type != SOCK_STREAM || st != 0x0A || port == 0)
			continue;
		if (!list_find_inode(&l, inode))
			continue;

		(void)cid;
		report_inode_findings(inode, port, "vsock", NULL);
	}
	fclose(f);
}

static void read_vsock(void)
{
	if (read_vsock_diag() < 0)
		read_vsock_proc();
}
#endif
#endif
#endif

#ifndef NETCAP_NO_MAIN
int main(int argc, char **argv)
{
	struct netcap_opts opts = { 0, 0, 0, 0, NULL };
	int i;

	for (i = 1; i < argc; i++) {
		if (strcmp(argv[i], "--advanced") == 0)
			opts.advanced = 1;
		else if (strcmp(argv[i], "--interface") == 0) {
			if (i + 1 >= argc) {
				fputs("--interface requires an argument\n",
					stderr);
				usage();
			}
			opts.interface = argv[++i];
		} else if (strncmp(argv[i], "--interface=", 12) == 0) {
			if (argv[i][12] == 0) {
				fputs("--interface requires an argument\n",
					stderr);
				usage();
			}
			opts.interface = argv[i] + 12;
		}
		else if (strcmp(argv[i], "--list-interfaces") == 0)
			opts.list_interfaces = 1;
		else if (strcmp(argv[i], "--json") == 0)
			opts.json = 1;
		else if (strcmp(argv[i], "--no-color") == 0)
			opts.no_color = 1;
		else {
			fprintf(stderr, "Unknown option: %s\n", argv[i]);
			usage();
		}
	}

	if (opts.json && !opts.advanced) {
		fputs("--json is only valid with --advanced\n", stderr);
		usage();
	}
	if (opts.list_interfaces && !opts.advanced) {
		fputs("--list-interfaces is only valid with --advanced\n",
			stderr);
		usage();
	}
	if (opts.interface && !opts.advanced) {
		fputs("--interface is only valid with --advanced\n", stderr);
		usage();
	}

	if (opts.advanced) {
#ifdef HAVE_NETCAP_ADVANCED
		return netcap_advanced_main(&opts);
#else
		fputs("netcap --advanced was disabled at configure time\n",
			stderr);
		fputs("because required kernel headers were not available\n",
			stderr);
		return 1;
#endif
	}

	if (argc > 1) {
		fputs("Too many arguments\n", stderr);
		usage();
	}

	list_create(&l);
	collect_process_info();

	// Now we check the tcp socket list...
	read_net("/proc/net/tcp", "tcp", 1);
	read_net("/proc/net/tcp6", "tcp6", 1);

	// Next udp sockets...
	read_net("/proc/net/udp", "udp", 1);
	read_net("/proc/net/udp6", "udp6", 1);
	read_net("/proc/net/udplite", "udplite", 1);
	read_net("/proc/net/udplite6", "udplite6", 1);

	// Next, raw sockets...
	read_net("/proc/net/raw", "raw", 0);
	read_net("/proc/net/raw6", "raw6", 0);

	// And last, read packet sockets
	read_packet();

	// Add listeners from protocols supported in advanced mode
#ifdef HAVE_NETCAP_ADVANCED
	read_diag_listeners();
#ifdef HAVE_NETCAP_VSOCK
	read_vsock();
#endif
#endif

	// Could also do icmp,netlink,unix

	list_clear(&l);
	return 0;
}
#endif
