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 
38 static llist l;
39 static int perm_warn = 0, header = 0, last_uid = -1;
40 static char *tacct = NULL;
41 
usage(void)42 static void usage(void)
43 {
44 	fprintf(stderr, "usage: netcap\n");
45 	exit(1);
46 }
47 
collect_process_info(void)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 
report_finding(int port,const char * type,const char * ifc)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 
read_tcp(const char * proc,const char * type)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 
read_udp(const char * proc,const char * type)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 
read_raw(const char * proc,const char * type)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
get_interface(unsigned int iface,char * ifc)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 
read_packet(void)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 
main(int argc,char * argv[])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