1/*
2 * Copyright (c) 2013-2014 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29#include <sys/socket.h>
30#include <sys/errno.h>
31#include <sys/event.h>
32#include <sys/time.h>
33#include <sys/sys_domain.h>
34#include <sys/ioctl.h>
35#include <sys/kern_control.h>
36#include <sys/queue.h>
37#include <net/content_filter.h>
38#include <netinet/in.h>
39#include <stdio.h>
40#include <err.h>
41#include <string.h>
42#include <stdlib.h>
43#include <fcntl.h>
44#include <unistd.h>
45#include <ctype.h>
46#include <sysexits.h>
47
48extern void print_filter_list(void);
49extern void print_socket_list(void);
50extern void print_cfil_stats(void);
51
52#define MAX_BUFFER (65536 + 1024)
53
54#define MAXHEXDUMPCOL 16
55
56
57enum {
58	MODE_NONE = 0,
59	MODE_INTERACTIVE = 0x01,
60	MODE_PEEK = 0x02,
61	MODE_PASS = 0x04,
62	MODE_DELAY = 0x08
63};
64int mode = MODE_NONE;
65
66unsigned long delay_ms = 0;
67struct timeval delay_tv = { 0, 0 };
68long verbosity = 0;
69uint32_t necp_control_unit = 0;
70unsigned long auto_start = 0;
71uint64_t peek_inc = 0;
72uint64_t pass_offset = 0;
73struct timeval now, deadline;
74int sf = -1;
75int pass_loopback = 0;
76uint32_t random_drop = 0;
77uint32_t event_total = 0;
78uint32_t event_dropped = 0;
79
80uint64_t default_in_pass = 0;
81uint64_t default_in_peek = 0;
82uint64_t default_out_pass = 0;
83uint64_t default_out_peek = 0;
84
85unsigned long max_dump_len = 32;
86
87TAILQ_HEAD(sock_info_head, sock_info) sock_info_head = TAILQ_HEAD_INITIALIZER(sock_info_head);
88
89
90struct sock_info {
91	TAILQ_ENTRY(sock_info)	si_link;
92	cfil_sock_id_t		si_sock_id;
93	struct timeval		si_deadline;
94	uint64_t		si_in_pass;
95	uint64_t		si_in_peek;
96	uint64_t		si_out_pass;
97	uint64_t		si_out_peek;
98};
99
100static void
101HexDump(void *data, size_t len)
102{
103	size_t i, j, k;
104	unsigned char *ptr = (unsigned char *)data;
105	unsigned char buf[32 + 3 * MAXHEXDUMPCOL + 2 + MAXHEXDUMPCOL + 1];
106
107	for (i = 0; i < len; i += MAXHEXDUMPCOL) {
108		k = snprintf((char *)buf, sizeof(buf), "\t0x%04lx:  ", i);
109		for (j = i; j < i + MAXHEXDUMPCOL; j++) {
110			if (j < len) {
111				unsigned char msnbl = ptr[j] >> 4;
112				unsigned char lsnbl = ptr[j] & 0x0f;
113
114				buf[k++] = msnbl < 10 ? msnbl + '0' : msnbl + 'a' - 10;
115				buf[k++] = lsnbl < 10 ? lsnbl + '0' : lsnbl + 'a' - 10;
116			} else {
117				buf[k++] = ' ';
118				buf[k++] = ' ';
119			}
120			if ((j % 2) == 1)
121				buf[k++] = ' ';
122			if ((j % MAXHEXDUMPCOL) == MAXHEXDUMPCOL - 1)
123				buf[k++] = ' ';
124		}
125
126		buf[k++] = ' ';
127		buf[k++] = ' ';
128
129		for (j = i; j < i + MAXHEXDUMPCOL && j < len; j++) {
130			if (isprint(ptr[j]))
131				buf[k++] = ptr[j];
132			else
133				buf[k++] = '.';
134		}
135		buf[k] = 0;
136		printf("%s\n", buf);
137	}
138}
139
140void
141print_hdr(struct cfil_msg_hdr *hdr)
142{
143	const char *typestr = "unknown";
144	const char *opstr = "unknown";
145
146	if (hdr->cfm_type == CFM_TYPE_EVENT) {
147		typestr = "event";
148		switch (hdr->cfm_op) {
149			case CFM_OP_SOCKET_ATTACHED:
150				opstr = "attached";
151				break;
152			case CFM_OP_SOCKET_CLOSED:
153				opstr = "closed";
154				break;
155			case CFM_OP_DATA_OUT:
156				opstr = "dataout";
157				break;
158			case CFM_OP_DATA_IN:
159				opstr = "datain";
160				break;
161			case CFM_OP_DISCONNECT_OUT:
162				opstr = "disconnectout";
163				break;
164			case CFM_OP_DISCONNECT_IN:
165				opstr = "disconnectin";
166				break;
167
168			default:
169				break;
170		}
171	} else if (hdr->cfm_type == CFM_TYPE_ACTION) {
172		typestr = "action";
173		switch (hdr->cfm_op) {
174			case CFM_OP_DATA_UPDATE:
175				opstr = "update";
176				break;
177			case CFM_OP_DROP:
178				opstr = "drop";
179				break;
180
181			default:
182				break;
183		}
184
185	}
186	printf("%s %s len %u version %u type %u op %u sock_id 0x%llx\n",
187	       typestr, opstr,
188	       hdr->cfm_len, hdr->cfm_version, hdr->cfm_type,
189	       hdr->cfm_op, hdr->cfm_sock_id);
190}
191
192void
193print_data_req(struct cfil_msg_data_event *data_req)
194{
195	size_t datalen;
196	void *databuf;
197
198	if (verbosity <= 0)
199		return;
200
201	print_hdr(&data_req->cfd_msghdr);
202
203	printf(" start %llu end %llu\n",
204	       data_req->cfd_start_offset, data_req->cfd_end_offset);
205
206	datalen = (size_t)(data_req->cfd_end_offset - data_req->cfd_start_offset);
207
208	databuf = (void *)(data_req + 1);
209
210	if (verbosity > 1)
211		HexDump(databuf, MIN(datalen, max_dump_len));
212}
213
214void
215print_action_msg(struct cfil_msg_action *action)
216{
217	if (verbosity <= 0)
218		return;
219
220	print_hdr(&action->cfa_msghdr);
221
222	if (action->cfa_msghdr.cfm_op == CFM_OP_DATA_UPDATE)
223		printf(" out pass %llu peek %llu in pass %llu peek %llu\n",
224		       action->cfa_out_pass_offset, action->cfa_out_peek_offset,
225		       action->cfa_in_pass_offset, action->cfa_in_peek_offset);
226}
227
228struct sock_info *
229find_sock_info(cfil_sock_id_t sockid)
230{
231	struct sock_info *sock_info;
232
233	TAILQ_FOREACH(sock_info, &sock_info_head, si_link) {
234		if (sock_info->si_sock_id == sockid)
235			return (sock_info);
236	}
237	return (NULL);
238}
239
240struct sock_info *
241add_sock_info(cfil_sock_id_t sockid)
242{
243	struct sock_info *sock_info;
244
245	if (find_sock_info(sockid) != NULL)
246		return (NULL);
247
248	sock_info = calloc(1, sizeof(struct sock_info));
249	if (sock_info == NULL)
250		err(EX_OSERR, "calloc()");
251	sock_info->si_sock_id = sockid;
252	TAILQ_INSERT_TAIL(&sock_info_head, sock_info, si_link);
253
254	return (sock_info);
255}
256
257void
258remove_sock_info(cfil_sock_id_t sockid)
259{
260	struct sock_info *sock_info = find_sock_info(sockid);
261
262	if (sock_info != NULL) {
263		TAILQ_REMOVE(&sock_info_head, sock_info, si_link);
264		free(sock_info);
265	}
266}
267
268/* return 0 if timer is already set */
269int
270set_sock_info_deadline(struct sock_info *sock_info)
271{
272	if (timerisset(&sock_info->si_deadline))
273		return (0);
274
275	timeradd(&now, &sock_info->si_deadline, &sock_info->si_deadline);
276
277	if (!timerisset(&deadline)) {
278		timeradd(&now, &delay_tv, &deadline);
279	}
280
281	return (1);
282}
283
284void
285send_action_message(uint32_t op, struct sock_info *sock_info, int nodelay)
286{
287	struct cfil_msg_action action;
288
289	if (!nodelay && delay_ms) {
290		set_sock_info_deadline(sock_info);
291		return;
292	}
293	bzero(&action, sizeof(struct cfil_msg_action));
294	action.cfa_msghdr.cfm_len = sizeof(struct cfil_msg_action);
295	action.cfa_msghdr.cfm_version = CFM_VERSION_CURRENT;
296	action.cfa_msghdr.cfm_type = CFM_TYPE_ACTION;
297	action.cfa_msghdr.cfm_op = op;
298	action.cfa_msghdr.cfm_sock_id = sock_info->si_sock_id;
299	switch (op) {
300		case CFM_OP_DATA_UPDATE:
301			action.cfa_out_pass_offset = sock_info->si_out_pass;
302			action.cfa_out_peek_offset = sock_info->si_out_peek;
303			action.cfa_in_pass_offset = sock_info->si_in_pass;
304			action.cfa_in_peek_offset = sock_info->si_in_peek;
305			break;
306
307		default:
308			break;
309	}
310
311	if (verbosity > -1)
312		print_action_msg(&action);
313
314	if (send(sf, &action, sizeof(struct cfil_msg_action), 0) == -1)
315		warn("send()");
316
317	timerclear(&sock_info->si_deadline);
318}
319
320void
321process_delayed_actions()
322{
323	struct sock_info *sock_info;
324
325	TAILQ_FOREACH(sock_info, &sock_info_head, si_link) {
326		if (timerisset(&sock_info->si_deadline) &&
327		    timercmp(&sock_info->si_deadline, &now, >=))
328		    send_action_message(CFM_OP_DATA_UPDATE, sock_info, 1);
329	}
330}
331
332int
333set_non_blocking(int fd)
334{
335	int flags;
336
337	flags = fcntl(fd, F_GETFL);
338	if (flags == -1) {
339		warn("fcntl(F_GETFL)");
340		return (-1);
341	}
342	flags |= O_NONBLOCK;
343	if (fcntl(fd, F_SETFL, flags) == -1) {
344		warn("fcntl(F_SETFL)");
345		return (-1);
346	}
347	return (0);
348}
349
350int
351offset_from_str(const char *str, uint64_t *ret_val)
352{
353	char *endptr;
354	uint64_t offset;
355	int success = 1;
356
357	if (strcasecmp(str, "max") == 0 || strcasecmp(str, "all") == 0)
358		offset = CFM_MAX_OFFSET;
359	else {
360		offset = strtoull(str, &endptr, 0);
361		if (*str == '\0' || *endptr != '\0')
362			success = 0;
363 	}
364	if (success)
365		*ret_val = offset;
366	return (success);
367}
368
369#define IN6_IS_ADDR_V4MAPPED_LOOPBACK(a)               \
370	((*(const __uint32_t *)(const void *)(&(a)->s6_addr[0]) == 0) && \
371	(*(const __uint32_t *)(const void *)(&(a)->s6_addr[4]) == 0) && \
372	(*(const __uint32_t *)(const void *)(&(a)->s6_addr[8]) == ntohl(0x0000ffff)) && \
373	(*(const __uint32_t *)(const void *)(&(a)->s6_addr[12]) == ntohl(INADDR_LOOPBACK)))
374
375
376int
377is_loopback(struct cfil_msg_data_event *data_req)
378{
379	if (data_req->cfc_dst.sa.sa_family == AF_INET &&
380	    ntohl(data_req->cfc_dst.sin.sin_addr.s_addr) == INADDR_LOOPBACK)
381		return (1);
382	if (data_req->cfc_dst.sa.sa_family == AF_INET6 &&
383	    IN6_IS_ADDR_LOOPBACK(&data_req->cfc_dst.sin6.sin6_addr))
384		return (1);
385	if (data_req->cfc_dst.sa.sa_family == AF_INET6 &&
386	    IN6_IS_ADDR_V4MAPPED_LOOPBACK(&data_req->cfc_dst.sin6.sin6_addr))
387		return (1);
388
389	if (data_req->cfc_src.sa.sa_family == AF_INET &&
390	    ntohl(data_req->cfc_src.sin.sin_addr.s_addr) == INADDR_LOOPBACK)
391		return (1);
392	if (data_req->cfc_src.sa.sa_family == AF_INET6 &&
393	    IN6_IS_ADDR_LOOPBACK(&data_req->cfc_src.sin6.sin6_addr))
394		return (1);
395	if (data_req->cfc_src.sa.sa_family == AF_INET6 &&
396	    IN6_IS_ADDR_V4MAPPED_LOOPBACK(&data_req->cfc_src.sin6.sin6_addr))
397		return (1);
398
399	return (0);
400}
401
402int
403drop(struct sock_info *sock_info)
404{
405	event_total++;
406	if (random_drop > 0) {
407		uint32_t r = arc4random();
408		if (r <= random_drop) {
409			event_dropped++;
410			printf("dropping 0x%llx dropped %u total %u rate %f\n",
411			       sock_info->si_sock_id,
412			       event_dropped, event_total,
413			       (double)event_dropped/(double)event_total * 100);
414			send_action_message(CFM_OP_DROP, sock_info, 0);
415			return (1);
416		}
417	}
418	return (0);
419}
420
421int
422doit()
423{
424	struct sockaddr_ctl sac;
425	struct ctl_info ctl_info;
426	void *buffer = NULL;
427	struct cfil_msg_hdr *hdr;
428	int kq = -1;
429	struct kevent kv;
430	int fdin = fileno(stdin);
431	char *linep = NULL;
432	size_t linecap = 0;
433	char *cmdptr = NULL;
434	char *argptr = NULL;
435	size_t cmdlen = 0;
436	struct cfil_msg_action action;
437	cfil_sock_id_t last_sock_id = 0;
438	struct sock_info *sock_info = NULL;
439	struct timeval last_time, elapsed, delta;
440	struct timespec interval, *timeout = NULL;
441
442	kq = kqueue();
443	if (kq == -1)
444		err(1, "kqueue()");
445
446	sf = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
447	if (sf == -1)
448		err(1, "socket()");
449
450	bzero(&ctl_info, sizeof(struct ctl_info));
451	strlcpy(ctl_info.ctl_name, CONTENT_FILTER_CONTROL_NAME, sizeof(ctl_info.ctl_name));
452	if (ioctl(sf, CTLIOCGINFO, &ctl_info) == -1)
453		err(1, "ioctl(CTLIOCGINFO)");
454
455	if (fcntl(sf, F_SETNOSIGPIPE, 1) == -1)
456		err(1, "fcntl(F_SETNOSIGPIPE)");
457
458	bzero(&sac, sizeof(struct sockaddr_ctl));
459	sac.sc_len = sizeof(struct sockaddr_ctl);
460	sac.sc_family = AF_SYSTEM;
461	sac.ss_sysaddr = AF_SYS_CONTROL;
462	sac.sc_id = ctl_info.ctl_id;
463
464	if (connect(sf, (struct sockaddr *)&sac, sizeof(struct sockaddr_ctl)) == -1)
465		err(1, "connect()");
466
467	if (set_non_blocking(sf) == -1)
468		err(1, "set_non_blocking(sf)");
469
470	if (setsockopt(sf, SYSPROTO_CONTROL, CFIL_OPT_NECP_CONTROL_UNIT,
471		       &necp_control_unit, sizeof(uint32_t)) == -1)
472		err(1, "setsockopt(CFIL_OPT_NECP_CONTROL_UNIT, %u)", necp_control_unit);
473
474	bzero(&kv, sizeof(struct kevent));
475	kv.ident = sf;
476	kv.filter = EVFILT_READ;
477	kv.flags = EV_ADD;
478	if (kevent(kq, &kv, 1, NULL, 0, NULL) == -1)
479		err(1, "kevent(sf)");
480
481	bzero(&kv, sizeof(struct kevent));
482	kv.ident = fdin;
483	kv.filter = EVFILT_READ;
484	kv.flags = EV_ADD;
485	if (kevent(kq, &kv, 1, NULL, 0, NULL) == -1)
486		err(1, "kevent(sf)");
487
488	buffer = malloc(MAX_BUFFER);
489	if (buffer == NULL)
490		err(1, "malloc()");
491
492	gettimeofday(&now, NULL);
493
494	while (1) {
495		last_time = now;
496		if (delay_ms && timerisset(&deadline)) {
497			timersub(&deadline, &now, &delta);
498			TIMEVAL_TO_TIMESPEC(&delta, &interval);
499			timeout = &interval;
500		} else {
501			timeout = NULL;
502		}
503
504		if (kevent(kq, NULL, 0, &kv, 1, timeout) == -1) {
505			if (errno == EINTR)
506				continue;
507			err(1, "kevent()");
508		}
509		gettimeofday(&now, NULL);
510		timersub(&now, &last_time, &elapsed);
511		if (delay_ms && timerisset(&deadline)) {
512			if (timercmp(&now, &deadline, >=)) {
513				process_delayed_actions();
514				interval.tv_sec = 0;
515				interval.tv_nsec = 0;
516			}
517		}
518
519		if (kv.ident == sf && kv.filter == EVFILT_READ) {
520			while (1) {
521				ssize_t nread;
522
523				nread = recv(sf, buffer, MAX_BUFFER, 0);
524				if (nread == 0) {
525					warnx("recv(sf) returned 0, connection closed");
526					break;
527				}
528				if (nread == -1) {
529					if (errno == EINTR)
530						continue;
531					if (errno == EWOULDBLOCK)
532						break;
533					err(1, "recv()");
534
535				}
536				if (nread < sizeof(struct cfil_msg_hdr))
537					errx(1, "too small");
538				hdr = (struct cfil_msg_hdr *)buffer;
539
540
541				if (hdr->cfm_type != CFM_TYPE_EVENT) {
542					warnx("not a content filter event type %u", hdr->cfm_type);
543					continue;
544				}
545				switch (hdr->cfm_op) {
546					case CFM_OP_SOCKET_ATTACHED: {
547						struct cfil_msg_sock_attached *msg_attached = (struct cfil_msg_sock_attached *)hdr;
548
549						if (verbosity > -2)
550							print_hdr(hdr);
551						if (verbosity > -1)
552							printf(" fam %d type %d proto %d pid %u epid %u\n",
553							       msg_attached->cfs_sock_family,
554							       msg_attached->cfs_sock_type,
555							       msg_attached->cfs_sock_protocol,
556							       msg_attached->cfs_pid,
557						       msg_attached->cfs_e_pid);
558						break;
559					}
560					case CFM_OP_SOCKET_CLOSED:
561					case CFM_OP_DISCONNECT_IN:
562					case CFM_OP_DISCONNECT_OUT:
563						if (verbosity > -2)
564							print_hdr(hdr);
565						break;
566					case CFM_OP_DATA_OUT:
567					case CFM_OP_DATA_IN:
568						if (verbosity > -3)
569							print_data_req((struct cfil_msg_data_event *)hdr);
570						break;
571					default:
572						warnx("unknown content filter event op %u", hdr->cfm_op);
573						continue;
574				}
575				switch (hdr->cfm_op) {
576					case CFM_OP_SOCKET_ATTACHED:
577						sock_info = add_sock_info(hdr->cfm_sock_id);
578						if (sock_info == NULL) {
579							warnx("sock_id %llx already exists", hdr->cfm_sock_id);
580							continue;
581						}
582						break;
583					case CFM_OP_DATA_OUT:
584					case CFM_OP_DATA_IN:
585					case CFM_OP_DISCONNECT_IN:
586					case CFM_OP_DISCONNECT_OUT:
587					case CFM_OP_SOCKET_CLOSED:
588						sock_info = find_sock_info(hdr->cfm_sock_id);
589
590						if (sock_info == NULL) {
591							warnx("unexpected data message, sock_info is NULL");
592							continue;
593						}
594						break;
595					default:
596						warnx("unknown content filter event op %u", hdr->cfm_op);
597						continue;
598				}
599
600
601				switch (hdr->cfm_op) {
602					case CFM_OP_SOCKET_ATTACHED: {
603						if ((mode & MODE_PASS) || (mode & MODE_PEEK) || auto_start) {
604							sock_info->si_out_pass = default_out_pass;
605							sock_info->si_out_peek = (mode & MODE_PEEK) ? peek_inc : (mode & MODE_PASS) ? CFM_MAX_OFFSET : default_out_peek;
606							sock_info->si_in_pass = default_in_pass;
607							sock_info->si_in_peek = (mode & MODE_PEEK) ? peek_inc : (mode & MODE_PASS) ? CFM_MAX_OFFSET : default_in_peek;
608
609							send_action_message(CFM_OP_DATA_UPDATE, sock_info, 0);
610						}
611						break;
612					}
613					case CFM_OP_SOCKET_CLOSED: {
614						remove_sock_info(hdr->cfm_sock_id);
615						sock_info = NULL;
616						break;
617					}
618					case CFM_OP_DATA_OUT:
619					case CFM_OP_DATA_IN: {
620						struct cfil_msg_data_event *data_req = (struct cfil_msg_data_event *)hdr;
621
622						if (pass_loopback && is_loopback(data_req)) {
623							sock_info->si_out_pass = CFM_MAX_OFFSET;
624							sock_info->si_in_pass = CFM_MAX_OFFSET;
625						} else {
626							if (drop(sock_info))
627								continue;
628
629							if ((mode & MODE_PASS)) {
630								if (data_req->cfd_msghdr.cfm_op == CFM_OP_DATA_OUT) {
631									if (pass_offset == 0 || pass_offset == CFM_MAX_OFFSET)
632										sock_info->si_out_pass = data_req->cfd_end_offset;
633									else if (data_req->cfd_end_offset > pass_offset) {
634										sock_info->si_out_pass = CFM_MAX_OFFSET;
635										sock_info->si_in_pass = CFM_MAX_OFFSET;
636									}
637									sock_info->si_out_peek = (mode & MODE_PEEK) ?
638									data_req->cfd_end_offset + peek_inc : 0;
639								} else {
640									if (pass_offset == 0 || pass_offset == CFM_MAX_OFFSET)
641										sock_info->si_in_pass = data_req->cfd_end_offset;
642									else if (data_req->cfd_end_offset > pass_offset) {
643										sock_info->si_out_pass = CFM_MAX_OFFSET;
644										sock_info->si_in_pass = CFM_MAX_OFFSET;
645									}
646									sock_info->si_in_peek = (mode & MODE_PEEK) ?
647									data_req->cfd_end_offset + peek_inc : 0;
648								}
649							} else {
650								break;
651							}
652						}
653						send_action_message(CFM_OP_DATA_UPDATE, sock_info, 0);
654
655						break;
656					}
657					case CFM_OP_DISCONNECT_IN:
658					case CFM_OP_DISCONNECT_OUT: {
659						if (drop(sock_info))
660							continue;
661
662						if ((mode & MODE_PASS)) {
663							sock_info->si_out_pass = CFM_MAX_OFFSET;
664							sock_info->si_in_pass = CFM_MAX_OFFSET;
665
666							send_action_message(CFM_OP_DATA_UPDATE, sock_info, 0);
667						}
668						break;
669					}
670					default:
671						warnx("unkown message op %u", hdr->cfm_op);
672						break;
673				}
674				if (sock_info)
675					last_sock_id = sock_info->si_sock_id;
676			}
677		}
678		if (kv.ident == fdin && kv.filter == EVFILT_READ) {
679			ssize_t nread;
680			uint64_t offset = 0;
681			int nitems;
682			int op = 0;
683
684			nread = getline(&linep, &linecap, stdin);
685			if (nread == -1)
686				errx(1, "getline()");
687
688			if (verbosity > 2)
689				printf("linecap %lu nread %lu\n", linecap, nread);
690			if (nread > 0)
691				linep[nread - 1] = '\0';
692
693			if (verbosity > 2)
694				HexDump(linep, linecap);
695
696			if (*linep == 0)
697				continue;
698
699			if (cmdptr == NULL || argptr == NULL || linecap > cmdlen) {
700				cmdlen = linecap;
701				cmdptr = realloc(cmdptr, cmdlen);
702				argptr = realloc(argptr, cmdlen);
703			}
704
705			/*
706			 * Trick to support unisgned and hexadecimal arguments
707			 * as I can't figure out sscanf() conversions
708			 */
709			nitems = sscanf(linep, "%s %s", cmdptr, argptr);
710			if (nitems == 0) {
711				warnx("I didn't get that...");
712				continue;
713			} else if (nitems > 1) {
714				if (offset_from_str(argptr, &offset) == 0) {
715					warnx("I didn't get that either...");
716					continue;
717				}
718			}
719			if (verbosity > 2)
720				printf("nitems %d %s %s\n", nitems, cmdptr, argptr);
721
722			bzero(&action, sizeof(struct cfil_msg_action));
723			action.cfa_msghdr.cfm_len = sizeof(struct cfil_msg_action);
724			action.cfa_msghdr.cfm_version = CFM_VERSION_CURRENT;
725			action.cfa_msghdr.cfm_type = CFM_TYPE_ACTION;
726
727			if (strcasecmp(cmdptr, "passout") == 0 && nitems > 1) {
728				op = CFM_OP_DATA_UPDATE;
729				action.cfa_out_pass_offset = offset;
730			} else if (strcasecmp(cmdptr, "passin") == 0 && nitems > 1) {
731				op = CFM_OP_DATA_UPDATE;
732				action.cfa_in_pass_offset = offset;
733			} else if (strcasecmp(cmdptr, "pass") == 0 && nitems > 1) {
734				op = CFM_OP_DATA_UPDATE;
735				action.cfa_out_pass_offset = offset;
736				action.cfa_in_pass_offset = offset;
737			} else if (strcasecmp(cmdptr, "peekout") == 0 && nitems > 1) {
738				op = CFM_OP_DATA_UPDATE;
739				action.cfa_out_peek_offset = offset;
740			} else if (strcasecmp(cmdptr, "peekin") == 0 && nitems > 1) {
741				op = CFM_OP_DATA_UPDATE;
742				action.cfa_in_peek_offset = offset;
743			} else if (strcasecmp(cmdptr, "peek") == 0 && nitems > 1) {
744				op = CFM_OP_DATA_UPDATE;
745				action.cfa_out_peek_offset = offset;
746				action.cfa_in_peek_offset = offset;
747			} else if (strcasecmp(cmdptr, "start") == 0) {
748				op = CFM_OP_DATA_UPDATE;
749				action.cfa_out_pass_offset = 0;
750				action.cfa_out_peek_offset = CFM_MAX_OFFSET;
751				action.cfa_in_pass_offset = 0;
752				action.cfa_in_peek_offset = CFM_MAX_OFFSET;
753			} else if (strcasecmp(cmdptr, "peekall") == 0) {
754				op = CFM_OP_DATA_UPDATE;
755				action.cfa_out_peek_offset = CFM_MAX_OFFSET;
756				action.cfa_in_peek_offset = CFM_MAX_OFFSET;
757			} else if (strcasecmp(cmdptr, "passall") == 0) {
758				op = CFM_OP_DATA_UPDATE;
759				action.cfa_out_pass_offset = CFM_MAX_OFFSET;
760				action.cfa_out_peek_offset = CFM_MAX_OFFSET;
761				action.cfa_in_pass_offset = CFM_MAX_OFFSET;
762				action.cfa_in_peek_offset = CFM_MAX_OFFSET;
763			} else if (strcasecmp(cmdptr, "drop") == 0)
764				op = CFM_OP_DROP;
765			else if (strcasecmp(cmdptr, "sock") == 0) {
766				last_sock_id = offset;
767				printf("last_sock_id 0x%llx\n", last_sock_id);
768			} else
769				warnx("syntax error");
770
771			if (op == CFM_OP_DATA_UPDATE || op == CFM_OP_DROP) {
772				action.cfa_msghdr.cfm_op = op;
773				action.cfa_msghdr.cfm_sock_id = last_sock_id;
774				print_action_msg(&action);
775
776				if (send(sf, &action, sizeof(struct cfil_msg_action), 0) == -1)
777					warn("send()");
778			}
779		}
780	}
781
782	return 0;
783}
784
785static const char *
786basename(const char * str)
787{
788	const char *last_slash = strrchr(str, '/');
789
790	if (last_slash == NULL)
791		return (str);
792	else
793		return (last_slash + 1);
794}
795
796struct option_desc {
797	const char *option;
798	const char *description;
799	int required;
800};
801
802struct option_desc option_desc_list[] = {
803	{ "-a offset", "auto start with offset", 0 },
804	{ "-d offset value", "default offset value for passin, peekin, passout, peekout, pass, peek", 0 },
805	{ "-h", "dsiplay this help", 0 },
806	{ "-i", "interactive mode", 0 },
807	{ "-k increment", "peek mode with increment", 0 },
808	{"-l", "pass loopback", 0 },
809	{ "-m length", "max dump length", 0 },
810	{ "-p offset", "pass mode (all or after given offset if > 0)", 0 },
811	{ "-q", "decrease verbose level", 0 },
812	{ "-r random", "random drop rate", 0 },
813	{ "-s ", "display content filter statistics (all, sock, filt, cfil)", 0 },
814	{ "-t delay", "pass delay in microseconds", 0 },
815	{ "-u unit", "NECP filter control unit", 1 },
816	{ "-v", "increase verbose level", 0 },
817	{ NULL, NULL, 0 }  /* Mark end of list */
818};
819
820static void
821usage(const char *cmd)
822{
823	struct option_desc *option_desc;
824	char *usage_str = malloc(LINE_MAX);
825	size_t usage_len;
826
827	if (usage_str == NULL)
828		err(1, "%s: malloc(%d)", __func__, LINE_MAX);
829
830	usage_len = snprintf(usage_str, LINE_MAX, "# usage: %s ", basename(cmd));
831
832	for (option_desc = option_desc_list; option_desc->option != NULL; option_desc++) {
833		int len;
834
835		if (option_desc->required)
836			len = snprintf(usage_str + usage_len, LINE_MAX - usage_len, "%s ", option_desc->option);
837		else
838			len = snprintf(usage_str + usage_len, LINE_MAX - usage_len, "[%s] ", option_desc->option);
839		if (len < 0)
840			err(1, "%s: snprintf(", __func__);
841
842		usage_len += len;
843		if (usage_len > LINE_MAX)
844			break;
845	}
846	printf("%s\n", usage_str);
847	printf("options:\n");
848
849	for (option_desc = option_desc_list; option_desc->option != NULL; option_desc++) {
850		printf(" %-20s # %s\n", option_desc->option, option_desc->description);
851	}
852
853}
854
855int
856main(int argc, char * const argv[])
857{
858	int ch;
859	double d;
860	int stats_sock_list = 0;
861	int stats_filt_list = 0;
862	int stats_cfil_stats = 0;
863
864	while ((ch = getopt(argc, argv, "a:d:hik:lm:p:qr:s:t:u:v")) != -1) {
865		switch (ch) {
866			case 'a':
867				auto_start = strtoul(optarg, NULL, 0);
868				break;
869			case 'd': {
870				if (optind >= argc)
871					errx(1, "'-d' needs 2 parameters");
872				if (strcasecmp(optarg, "passout") == 0) {
873					if (offset_from_str(argv[optind], &default_out_pass) == 0)
874						errx(1, "bad %s offset: %s", optarg, argv[optind + 1]);
875				} else if (strcasecmp(optarg, "passin") == 0) {
876					if (offset_from_str(argv[optind], &default_in_pass) == 0)
877						errx(1, "bad %s offset: %s", optarg, argv[optind + 1]);
878				} else if (strcasecmp(optarg, "pass") == 0) {
879					if (offset_from_str(argv[optind], &default_out_pass) == 0)
880						errx(1, "bad %s offset: %s", optarg, argv[optind + 1]);
881					default_in_pass = default_out_pass;
882				} else if (strcasecmp(optarg, "peekout") == 0) {
883					if (offset_from_str(argv[optind], &default_out_peek) == 0)
884						errx(1, "bad %s offset: %s", optarg, argv[optind + 1]);
885				} else if (strcasecmp(optarg, "peekin") == 0) {
886					if (offset_from_str(argv[optind], &default_in_peek) == 0)
887						errx(1, "bad %s offset: %s", optarg, argv[optind + 1]);
888				} else if (strcasecmp(optarg, "peek") == 0) {
889					if (offset_from_str(argv[optind], &default_out_peek) == 0)
890						errx(1, "bad %s offset: %s", optarg, argv[optind + 1]);
891					default_in_peek = default_out_peek;
892				} else
893					errx(1, "syntax error");
894				break;
895			}
896			case 'h':
897				usage(argv[0]);
898				exit(0);
899			case 'i':
900				mode |= MODE_INTERACTIVE;
901				break;
902			case 'k':
903				mode |= MODE_PEEK;
904				if (offset_from_str(optarg, &peek_inc) == 0)
905					errx(1, "bad peek offset: %s", optarg);
906				break;
907			case 'l':
908				pass_loopback = 1;
909				break;
910			case 'm':
911				max_dump_len = strtoul(optarg, NULL, 0);
912				break;
913			case 'p':
914				mode |= MODE_PASS;
915				if (offset_from_str(optarg, &pass_offset) == 0)
916					errx(1, "bad pass offset: %s", optarg);
917				break;
918			case 'q':
919				verbosity--;
920				break;
921			case 'r':
922				d = strtod(optarg, NULL);
923				if (d < 0 || d > 1)
924					errx(1, "bad drop rate: %s -- it must be between 0 and 1", optarg);
925				random_drop = (uint32_t)(d * UINT32_MAX);
926				break;
927			case 's':
928				if (strcasecmp(optarg, "all") == 0) {
929					stats_sock_list = 1;
930					stats_filt_list = 1;
931					stats_cfil_stats = 1;
932				} else if (strcasecmp(optarg, "sock") == 0) {
933					stats_sock_list = 1;
934				} else if (strcasecmp(optarg, "filt") == 0) {
935					stats_filt_list = 1;
936				} else if (strcasecmp(optarg, "cfil") == 0) {
937					stats_cfil_stats = 1;
938				} else {
939					warnx("# Error: unknown type of statistic: %s", optarg);
940					usage(argv[0]);
941					exit(0);
942				}
943				break;
944			case 't':
945				mode |= MODE_DELAY;
946				delay_ms = strtoul(optarg, NULL, 0);
947				delay_tv.tv_sec = delay_ms / 1000;
948				delay_tv.tv_usec = (delay_ms % 1000) * 1000;
949				break;
950			case 'u':
951				necp_control_unit = (uint32_t)strtoul(optarg, NULL, 0);
952				break;
953			case 'v':
954				verbosity++;
955				break;
956			default:
957				errx(1, "# syntax error, unknow option '%d'", ch);
958				usage(argv[0]);
959				exit(0);
960		}
961	}
962
963	if (stats_filt_list)
964		print_filter_list();
965	if (stats_sock_list)
966		print_socket_list();
967	if (stats_cfil_stats)
968		print_cfil_stats();
969	if (necp_control_unit == 0 && (stats_filt_list || stats_sock_list || stats_cfil_stats))
970		return (0);
971
972	if (necp_control_unit == 0) {
973		warnx("necp filter control unit is 0");
974		usage(argv[0]);
975		exit(EX_USAGE);
976	}
977	doit();
978
979
980	return (0);
981}
982
983