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@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
38static llist l;
39static int perm_warn = 0, header = 0, last_uid = -1;
40static char *tacct = NULL;
41
42static void usage(void)
43{
44	fprintf(stderr, "usage: netcap\n");
45	exit(1);
46}
47
48static 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
212static 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
248static 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
282static 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
316static 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
351static 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
386static 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
419int 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