You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
448 lines
10 KiB
448 lines
10 KiB
/*
|
|
* netcap.c - A program that lists network apps with capabilities
|
|
* Copyright (c) 2009-10 Red Hat Inc., Durham, North Carolina.
|
|
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
* Authors:
|
|
* Steve Grubb <sgrubb@redhat.com>
|
|
*
|
|
* The /proc/net/tcp|udp|raw parsing code was borrowed from netstat.c
|
|
*/
|
|
|
|
#include "config.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-llist.h"
|
|
|
|
static llist l;
|
|
static int perm_warn = 0, header = 0, last_uid = -1;
|
|
static char *tacct = NULL;
|
|
|
|
static void usage(void)
|
|
{
|
|
fprintf(stderr, "usage: netcap\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 *tmp, cmd[16], state, *text, *bounds;
|
|
int fd, len, euid = -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, 32, "/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));
|
|
sscanf(buf, "%d (%15c", &ppid, cmd);
|
|
sscanf(tmp+2, "%c %d", &state, &ppid);
|
|
|
|
// Skip kthreads
|
|
if (pid == 2 || ppid == 2)
|
|
continue;
|
|
|
|
// now get the capabilities
|
|
capng_clear(CAPNG_SELECT_BOTH);
|
|
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");
|
|
else
|
|
text = capng_print_caps_text(CAPNG_PRINT_BUFFER,
|
|
CAPNG_PERMITTED);
|
|
|
|
// Get the effective uid
|
|
snprintf(buf, 32, "/proc/%d/status", pid);
|
|
sf = fopen(buf, "rte");
|
|
if (sf == NULL)
|
|
euid = 0;
|
|
else {
|
|
int line = 0;
|
|
__fsetlocking(sf, FSETLOCKING_BYCALLER);
|
|
while (fgets(buf, sizeof(buf), sf)) {
|
|
if (line == 0) {
|
|
line++;
|
|
continue;
|
|
}
|
|
if (memcmp(buf, "Uid:", 4) == 0) {
|
|
int id;
|
|
sscanf(buf, "Uid: %d %d",
|
|
&id, &euid);
|
|
break;
|
|
}
|
|
}
|
|
fclose(sf);
|
|
}
|
|
|
|
// Now record the bounding set information
|
|
if (caps == CAPNG_PARTIAL) {
|
|
caps = capng_have_capabilities(CAPNG_SELECT_BOUNDS);
|
|
if (caps == CAPNG_FULL)
|
|
bounds = strdup("+");
|
|
else
|
|
bounds = strdup("");
|
|
} else
|
|
bounds = strdup("");
|
|
|
|
// Now lets get the inodes each process has open
|
|
snprintf(buf, 32, "/proc/%d/fd", pid);
|
|
f = opendir(buf);
|
|
if (f == NULL) {
|
|
if (errno == EACCES) {
|
|
if (perm_warn == 0) {
|
|
printf("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);
|
|
continue;
|
|
}
|
|
// For each file in the fd dir...
|
|
while (( ent = readdir(f) )) {
|
|
char line[256], ln[256], *s, *e;
|
|
unsigned long inode;
|
|
lnode node;
|
|
int llen;
|
|
|
|
if (ent->d_name[0] == '.')
|
|
continue;
|
|
snprintf(ln, 256, "%s/%s", buf, 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;
|
|
node.ppid = ppid;
|
|
node.pid = pid;
|
|
node.uid = euid;
|
|
node.cmd = strdup(cmd);
|
|
node.inode = inode;
|
|
node.capabilities = strdup(text);
|
|
node.bounds = strdup(bounds);
|
|
// We make one entry for each socket inode
|
|
list_append(&l, &node);
|
|
}
|
|
closedir(f);
|
|
free(text);
|
|
free(bounds);
|
|
}
|
|
closedir(d);
|
|
return 0;
|
|
}
|
|
|
|
static void report_finding(int port, const char *type, const char *ifc)
|
|
{
|
|
struct passwd *p;
|
|
lnode *n = list_get_cur(&l);
|
|
|
|
// And print out anything with capabilities
|
|
if (header == 0) {
|
|
printf("%-5s %-5s %-10s %-16s %-4s %-6s %s\n",
|
|
"ppid", "pid", "acct", "command", "type", "port",
|
|
"capabilities");
|
|
header = 1;
|
|
}
|
|
if (n->uid == 0) {
|
|
// Take short cut for this one
|
|
tacct = "root";
|
|
last_uid = 0;
|
|
} else if (last_uid != (int)n->uid) {
|
|
// Only look up if name changed
|
|
p = getpwuid(n->uid);
|
|
last_uid = n->uid;
|
|
if (p)
|
|
tacct = p->pw_name;
|
|
// If not taking this branch, use last val
|
|
}
|
|
if (tacct) {
|
|
printf("%-5d %-5d %-10s", n->ppid, n->pid, tacct);
|
|
} else
|
|
printf("%-5d %-5d %-10d", n->ppid, n->pid, last_uid);
|
|
printf(" %-16s %-4s", n->cmd, type);
|
|
if (ifc)
|
|
printf(" %-6s", ifc);
|
|
else
|
|
printf(" %-6d", port);
|
|
printf(" %s %s\n", n->capabilities, n->bounds);
|
|
}
|
|
|
|
static void read_tcp(const char *proc, const char *type)
|
|
{
|
|
int line = 0;
|
|
FILE *f;
|
|
char buf[256];
|
|
unsigned long rxq, txq, time_len, retr, inode;
|
|
int local_port, rem_port, d, state, timer_run, 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;
|
|
sscanf(buf, "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X "
|
|
"%lX:%lX %X:%lX %lX %d %d %lu %512s\n",
|
|
&d, local_addr, &local_port, rem_addr, &rem_port,
|
|
&state, &txq, &rxq, &timer_run, &time_len, &retr,
|
|
&uid, &timeout, &inode, more);
|
|
if (list_find_inode(&l, inode))
|
|
report_finding(local_port, type, NULL);
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
static void read_udp(const char *proc, const char *type)
|
|
{
|
|
int line = 0;
|
|
FILE *f;
|
|
char buf[256];
|
|
unsigned long rxq, txq, time_len, retr, inode;
|
|
int local_port, rem_port, d, state, timer_run, 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;
|
|
sscanf(buf, "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X "
|
|
"%lX:%lX %X:%lX %lX %d %d %lu %512s\n",
|
|
&d, local_addr, &local_port, rem_addr, &rem_port,
|
|
&state, &txq, &rxq, &timer_run, &time_len, &retr,
|
|
&uid, &timeout, &inode, more);
|
|
if (list_find_inode(&l, inode))
|
|
report_finding(local_port, type, NULL);
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
static void read_raw(const char *proc, const char *type)
|
|
{
|
|
int line = 0;
|
|
FILE *f;
|
|
char buf[256];
|
|
unsigned long rxq, txq, time_len, retr, inode;
|
|
int local_port, rem_port, d, state, timer_run, 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;
|
|
sscanf(buf, "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X "
|
|
"%lX:%lX %X:%lX %lX %d %d %lu %512s\n",
|
|
&d, local_addr, &local_port, rem_addr, &rem_port,
|
|
&state, &txq, &rxq, &timer_run, &time_len, &retr,
|
|
&uid, &timeout, &inode, more);
|
|
if (list_find_inode(&l, inode))
|
|
report_finding(0, type, NULL);
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
// Caller must have buffer > 16 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;
|
|
|
|
// Increment the interface number since header is 2 lines long
|
|
iface++;
|
|
|
|
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;
|
|
sscanf(buf, "%16s: %256s\n", ifc, more);
|
|
c = strchr(ifc, ':');
|
|
if (c)
|
|
*c = 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[32];
|
|
|
|
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;
|
|
sscanf(buf, "%lX %u %u %X %u %u %u %u %lu %256s\n",
|
|
&sk, &ref_cnt, &type, &proto, &iface,
|
|
&r, &rmem, &uid, &inode, more);
|
|
get_interface(iface, ifc);
|
|
if (list_find_inode(&l, inode))
|
|
report_finding(0, "pkt", ifc);
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
int main(int argc, char __attribute__((unused)) *argv[])
|
|
{
|
|
if (argc > 1) {
|
|
fputs("Too many arguments\n", stderr);
|
|
usage();
|
|
}
|
|
|
|
list_create(&l);
|
|
collect_process_info();
|
|
|
|
// Now we check the tcp socket list...
|
|
read_tcp("/proc/net/tcp", "tcp");
|
|
read_tcp("/proc/net/tcp6", "tcp6");
|
|
|
|
// Next udp sockets...
|
|
read_udp("/proc/net/udp", "udp");
|
|
read_udp("/proc/net/udp6", "udp6");
|
|
|
|
// Next, raw sockets...
|
|
read_raw("/proc/net/raw", "raw");
|
|
read_raw("/proc/net/raw6", "raw6");
|
|
|
|
// And last, read packet sockets
|
|
read_packet();
|
|
|
|
list_clear(&l);
|
|
return 0;
|
|
}
|
|
|