Home | History | Annotate | Download | only in utils
      1 /*
      2  * netcap.c - A program that lists network apps with capabilities
      3  * Copyright (c) 2009-10 Red Hat Inc., Durham, North Carolina.
      4  * All Rights Reserved.
      5  *
      6  * This software may be freely redistributed and/or modified under the
      7  * terms of the GNU General Public License as published by the Free
      8  * Software Foundation; either version 2, or (at your option) any
      9  * later version.
     10  *
     11  * This program is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14  * GNU General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU General Public License
     17  * along with this program; see the file COPYING. If not, write to the
     18  * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     19  *
     20  * Authors:
     21  *   Steve Grubb <sgrubb (at) redhat.com>
     22  *
     23  * The /proc/net/tcp|udp|raw parsing code was borrowed from netstat.c
     24  */
     25 
     26 #include "config.h"
     27 #include <stdio.h>
     28 #include <stdio_ext.h>
     29 #include <stdlib.h>
     30 #include <errno.h>
     31 #include <string.h>
     32 #include <dirent.h>
     33 #include <fcntl.h>
     34 #include <pwd.h>
     35 #include "cap-ng.h"
     36 #include "proc-llist.h"
     37 
     38 static llist l;
     39 static int perm_warn = 0, header = 0, last_uid = -1;
     40 static char *tacct = NULL;
     41 
     42 static void usage(void)
     43 {
     44 	fprintf(stderr, "usage: netcap\n");
     45 	exit(1);
     46 }
     47 
     48 static int collect_process_info(void)
     49 {
     50 	DIR *d, *f;
     51 	struct dirent *ent;
     52 	d = opendir("/proc");
     53 	if (d == NULL) {
     54 		fprintf(stderr, "Can't open /proc: %s\n", strerror(errno));
     55 		return 1;
     56 	}
     57 	while (( ent = readdir(d) )) {
     58 		FILE *sf;
     59 		int pid, ppid;
     60 		capng_results_t caps;
     61 		char buf[100];
     62 		char *tmp, cmd[16], state, *text, *bounds;
     63 		int fd, len, euid = -1;
     64 
     65 		// Skip non-process dir entries
     66 		if(*ent->d_name<'0' || *ent->d_name>'9')
     67 			continue;
     68 		errno = 0;
     69 		pid = strtol(ent->d_name, NULL, 10);
     70 		if (errno)
     71 			continue;
     72 
     73 		// Parse up the stat file for the proc
     74 		snprintf(buf, 32, "/proc/%d/stat", pid);
     75 		fd = open(buf, O_RDONLY|O_CLOEXEC, 0);
     76 		if (fd < 0)
     77 			continue;
     78 		len = read(fd, buf, sizeof buf - 1);
     79 		close(fd);
     80 		if (len < 40)
     81 			continue;
     82 		buf[len] = 0;
     83 		tmp = strrchr(buf, ')');
     84 		if (tmp)
     85 			*tmp = 0;
     86 		else
     87 			continue;
     88 		memset(cmd, 0, sizeof(cmd));
     89 		sscanf(buf, "%d (%15c", &ppid, cmd);
     90 		sscanf(tmp+2, "%c %d", &state, &ppid);
     91 
     92 		// Skip kthreads
     93 		if (pid == 2 || ppid == 2)
     94 			continue;
     95 
     96 		// now get the capabilities
     97 		capng_clear(CAPNG_SELECT_BOTH);
     98 		capng_setpid(pid);
     99 		if (capng_get_caps_process())
    100 			continue;
    101 		caps = capng_have_capabilities(CAPNG_SELECT_CAPS);
    102 		if (caps <= CAPNG_NONE)
    103 			continue;
    104 		if (caps == CAPNG_FULL)
    105 			text = strdup("full");
    106 		else
    107 			text = capng_print_caps_text(CAPNG_PRINT_BUFFER,
    108 					CAPNG_PERMITTED);
    109 
    110 		// Get the effective uid
    111 		snprintf(buf, 32, "/proc/%d/status", pid);
    112 		sf = fopen(buf, "rte");
    113 		if (sf == NULL)
    114 			euid = 0;
    115 		else {
    116 			int line = 0;
    117 			__fsetlocking(sf, FSETLOCKING_BYCALLER);
    118 			while (fgets(buf, sizeof(buf), sf)) {
    119 				if (line == 0) {
    120 					line++;
    121 					continue;
    122 				}
    123 				if (memcmp(buf, "Uid:", 4) == 0) {
    124 					int id;
    125 					sscanf(buf, "Uid: %d %d",
    126 						&id, &euid);
    127 					break;
    128 				}
    129 			}
    130 			fclose(sf);
    131 		}
    132 
    133 		// Now record the bounding set information
    134 		if (caps == CAPNG_PARTIAL) {
    135 			caps = capng_have_capabilities(CAPNG_SELECT_BOUNDS);
    136 			if (caps == CAPNG_FULL)
    137 				bounds = strdup("+");
    138 			else
    139 				bounds = strdup("");
    140 		} else
    141 			bounds = strdup("");
    142 
    143 		// Now lets get the inodes each process has open
    144 		snprintf(buf, 32, "/proc/%d/fd", pid);
    145 		f = opendir(buf);
    146 		if (f == NULL) {
    147 			if (errno == EACCES) {
    148 				if (perm_warn == 0) {
    149 					printf("You may need to be root to "
    150 						"get a full report\n");
    151 					perm_warn = 1;
    152 				}
    153 			} else
    154 				fprintf(stderr, "Can't open %s: %s\n", buf,
    155 					strerror(errno));
    156 			free(text);
    157 			free(bounds);
    158 			continue;
    159 		}
    160 		// For each file in the fd dir...
    161 		while (( ent = readdir(f) )) {
    162 			char line[256], ln[256], *s, *e;
    163 			unsigned long inode;
    164 			lnode node;
    165 			int llen;
    166 
    167 			if (ent->d_name[0] == '.')
    168 				continue;
    169 			snprintf(ln, 256, "%s/%s", buf, ent->d_name);
    170 			if ((llen = readlink(ln, line, sizeof(line)-1)) < 0)
    171 				continue;
    172 			line[llen] = 0;
    173 
    174 			// Only look at the socket entries
    175 			if (memcmp(line, "socket:", 7) == 0) {
    176 				// Type 1 sockets
    177 				s = strchr(line+7, '[');
    178 				if (s == NULL)
    179 					continue;
    180 				s++;
    181 				e = strchr(s, ']');
    182 				if (e == NULL)
    183 					continue;
    184 				*e = 0;
    185 			} else if (memcmp(line, "[0000]:", 7) == 0) {
    186 				// Type 2 sockets
    187 				s = line + 8;
    188 			} else
    189 				continue;
    190 			errno = 0;
    191 			inode = strtoul(s, NULL, 10);
    192 			if (errno)
    193 				continue;
    194 			node.ppid = ppid;
    195 			node.pid = pid;
    196 			node.uid = euid;
    197 			node.cmd = strdup(cmd);
    198 			node.inode = inode;
    199 			node.capabilities = strdup(text);
    200 			node.bounds = strdup(bounds);
    201 			// We make one entry for each socket inode
    202 			list_append(&l, &node);
    203 		}
    204 		closedir(f);
    205 		free(text);
    206 		free(bounds);
    207 	}
    208 	closedir(d);
    209 	return 0;
    210 }
    211 
    212 static void report_finding(int port, const char *type, const char *ifc)
    213 {
    214 	struct passwd *p;
    215 	lnode *n = list_get_cur(&l);
    216 
    217 	// And print out anything with capabilities
    218 	if (header == 0) {
    219 		printf("%-5s %-5s %-10s %-16s %-4s %-6s %s\n",
    220 			"ppid", "pid", "acct", "command", "type", "port",
    221 			"capabilities");
    222 			header = 1;
    223 	}
    224 	if (n->uid == 0) {
    225 		// Take short cut for this one
    226 		tacct = "root";
    227 		last_uid = 0;
    228 	} else if (last_uid != (int)n->uid) {
    229 		// Only look up if name changed
    230 		p = getpwuid(n->uid);
    231 		last_uid = n->uid;
    232 		if (p)
    233 			tacct = p->pw_name;
    234 		// If not taking this branch, use last val
    235 	}
    236 	if (tacct) {
    237 		printf("%-5d %-5d %-10s", n->ppid, n->pid, tacct);
    238 	} else
    239 		printf("%-5d %-5d %-10d", n->ppid, n->pid, last_uid);
    240 	printf(" %-16s %-4s", n->cmd, type);
    241 	if (ifc)
    242 		printf(" %-6s", ifc);
    243 	else
    244 		printf(" %-6d", port);
    245 	printf(" %s %s\n", n->capabilities, n->bounds);
    246 }
    247 
    248 static void read_tcp(const char *proc, const char *type)
    249 {
    250 	int line = 0;
    251 	FILE *f;
    252 	char buf[256];
    253 	unsigned long rxq, txq, time_len, retr, inode;
    254 	int local_port, rem_port, d, state, timer_run, uid, timeout;
    255 	char rem_addr[128], local_addr[128], more[512];
    256 
    257 	f = fopen(proc, "rte");
    258 	if (f == NULL) {
    259 		if (errno != ENOENT)
    260 			fprintf(stderr, "Can't open %s: %s\n",
    261 				proc, strerror(errno));
    262 		return;
    263 	}
    264 	__fsetlocking(f, FSETLOCKING_BYCALLER);
    265 	while (fgets(buf, sizeof(buf), f)) {
    266 		if (line == 0) {
    267 			line++;
    268 			continue;
    269 		}
    270 		more[0] = 0;
    271 		sscanf(buf, "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X "
    272 			"%lX:%lX %X:%lX %lX %d %d %lu %512s\n",
    273 			&d, local_addr, &local_port, rem_addr, &rem_port,
    274 			&state, &txq, &rxq, &timer_run, &time_len, &retr,
    275 			&uid, &timeout, &inode, more);
    276 		if (list_find_inode(&l, inode))
    277 			report_finding(local_port, type, NULL);
    278 	}
    279 	fclose(f);
    280 }
    281 
    282 static void read_udp(const char *proc, const char *type)
    283 {
    284 	int line = 0;
    285 	FILE *f;
    286 	char buf[256];
    287 	unsigned long rxq, txq, time_len, retr, inode;
    288 	int local_port, rem_port, d, state, timer_run, uid, timeout;
    289 	char rem_addr[128], local_addr[128], more[512];
    290 
    291 	f = fopen(proc, "rte");
    292 	if (f == NULL) {
    293 		if (errno != ENOENT)
    294 			fprintf(stderr, "Can't open %s: %s\n",
    295 					proc, strerror(errno));
    296 		return;
    297 	}
    298 	__fsetlocking(f, FSETLOCKING_BYCALLER);
    299 	while (fgets(buf, sizeof(buf), f)) {
    300 		if (line == 0) {
    301 			line++;
    302 			continue;
    303 		}
    304 		more[0] = 0;
    305 		sscanf(buf, "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X "
    306 			"%lX:%lX %X:%lX %lX %d %d %lu %512s\n",
    307 			&d, local_addr, &local_port, rem_addr, &rem_port,
    308 			&state, &txq, &rxq, &timer_run, &time_len, &retr,
    309 			&uid, &timeout, &inode, more);
    310 		if (list_find_inode(&l, inode))
    311 			report_finding(local_port, type, NULL);
    312 	}
    313 	fclose(f);
    314 }
    315 
    316 static void read_raw(const char *proc, const char *type)
    317 {
    318 	int line = 0;
    319 	FILE *f;
    320 	char buf[256];
    321 	unsigned long rxq, txq, time_len, retr, inode;
    322 	int local_port, rem_port, d, state, timer_run, uid, timeout;
    323 	char rem_addr[128], local_addr[128], more[512];
    324 
    325 	f = fopen(proc, "rte");
    326 	if (f == NULL) {
    327 		if (errno != ENOENT)
    328 			fprintf(stderr, "Can't open %s: %s\n",
    329 					proc, strerror(errno));
    330 		return;
    331 	}
    332 	__fsetlocking(f, FSETLOCKING_BYCALLER);
    333 	while (fgets(buf, sizeof(buf), f)) {
    334 		if (line == 0) {
    335 			line++;
    336 			continue;
    337 		}
    338 		more[0] = 0;
    339 		sscanf(buf, "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X "
    340 			"%lX:%lX %X:%lX %lX %d %d %lu %512s\n",
    341 			&d, local_addr, &local_port, rem_addr, &rem_port,
    342 			&state, &txq, &rxq, &timer_run, &time_len, &retr,
    343 			&uid, &timeout, &inode, more);
    344 		if (list_find_inode(&l, inode))
    345 			report_finding(0, type, NULL);
    346 	}
    347 	fclose(f);
    348 }
    349 
    350 // Caller must have buffer > 16 bytes
    351 static void get_interface(unsigned int iface, char *ifc)
    352 {
    353 	unsigned int line = 0;
    354 	FILE *f;
    355 	char buf[256], more[256];
    356 
    357 	// Terminate the interface in case of error
    358 	*ifc = 0;
    359 
    360 	// Increment the interface number since header is 2 lines long
    361 	iface++;
    362 
    363 	f = fopen("/proc/net/dev", "rte");
    364 	if (f == NULL) {
    365 		if (errno != ENOENT)
    366 			fprintf(stderr, "Can't open /proc/net/dev: %s\n",
    367 				strerror(errno));
    368 		return;
    369 	}
    370 	__fsetlocking(f, FSETLOCKING_BYCALLER);
    371 	while (fgets(buf, sizeof(buf), f)) {
    372 		if (line == iface) {
    373 			char *c;
    374 			sscanf(buf, "%16s: %256s\n", ifc, more);
    375 			c = strchr(ifc, ':');
    376 			if (c)
    377 				*c = 0;
    378 			fclose(f);
    379 			return;
    380 		}
    381 		line++;
    382 	}
    383 	fclose(f);
    384 }
    385 
    386 static void read_packet(void)
    387 {
    388 	int line = 0;
    389 	FILE *f;
    390 	char buf[256];
    391 	unsigned long sk, inode;
    392 	unsigned int ref_cnt, type, proto, iface, r, rmem, uid;
    393 	char more[256], ifc[32];
    394 
    395 	f = fopen("/proc/net/packet", "rte");
    396 	if (f == NULL) {
    397 		if (errno != ENOENT)
    398 			fprintf(stderr, "Can't open /proc/net/packet: %s\n",
    399 				strerror(errno));
    400 		return;
    401 	}
    402 	__fsetlocking(f, FSETLOCKING_BYCALLER);
    403 	while (fgets(buf, sizeof(buf), f)) {
    404 		if (line == 0) {
    405 			line++;
    406 			continue;
    407 		}
    408 		more[0] = 0;
    409 		sscanf(buf, "%lX %u %u %X %u %u %u %u %lu %256s\n",
    410 			&sk, &ref_cnt, &type, &proto, &iface,
    411 			&r, &rmem, &uid, &inode, more);
    412 		get_interface(iface, ifc);
    413 		if (list_find_inode(&l, inode))
    414 			report_finding(0, "pkt", ifc);
    415 	}
    416 	fclose(f);
    417 }
    418 
    419 int main(int argc, char __attribute__((unused)) *argv[])
    420 {
    421 	if (argc > 1) {
    422 		fputs("Too many arguments\n", stderr);
    423 		usage();
    424 	}
    425 
    426 	list_create(&l);
    427 	collect_process_info();
    428 
    429 	// Now we check the tcp socket list...
    430 	read_tcp("/proc/net/tcp", "tcp");
    431 	read_tcp("/proc/net/tcp6", "tcp6");
    432 
    433 	// Next udp sockets...
    434 	read_udp("/proc/net/udp", "udp");
    435 	read_udp("/proc/net/udp6", "udp6");
    436 
    437 	// Next, raw sockets...
    438 	read_raw("/proc/net/raw", "raw");
    439 	read_raw("/proc/net/raw6", "raw6");
    440 
    441 	// And last, read packet sockets
    442 	read_packet();
    443 
    444 	list_clear(&l);
    445 	return 0;
    446 }
    447 
    448