1/* $Id: obsdrdr.c,v 1.84 2015/02/08 08:55:55 nanard Exp $ */
2/* MiniUPnP project
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2006-2015 Thomas Bernard
5 * This software is subject to the conditions detailed
6 * in the LICENCE file provided within the distribution */
7
8/*
9 * pf rules created (with ext_if = xl1)
10 * - OpenBSD up to version 4.6 :
11 *     rdr pass on xl1 inet proto udp from any to any port = 54321 \
12 *         keep state label "test label" -> 192.168.0.42 port 12345
13 *   or a rdr rule + a pass rule :
14 *     rdr quick on xl1 inet proto udp from any to any port = 54321 \
15 *         keep state label "test label" -> 192.168.0.42 port 12345
16 *     pass in quick on xl1 inet proto udp from any to 192.168.0.42 port = 12345 \
17 *          flags S/SA keep state label "test label"
18 *
19 * - OpenBSD starting from version 4.7
20 *     match in on xl1 inet proto udp from any to any port 54321 \
21 *            label "test label" rdr-to 192.168.0.42 port 12345
22 *   or
23 *     pass in quick on xl1 inet proto udp from any to any port 54321 \
24 *            label "test label" rdr-to 192.168.0.42 port 12345
25 *
26 *
27 *
28 * Macros/#defines :
29 * - PF_ENABLE_FILTER_RULES
30 *   If set, two rules are created : rdr + pass. Else a rdr/pass rule
31 *   is created.
32 * - USE_IFNAME_IN_RULES
33 *   If set the interface name is set in the rule.
34 * - PFRULE_INOUT_COUNTS
35 *   Must be set with OpenBSD version 3.8 and up.
36 * - PFRULE_HAS_RTABLEID
37 *   Must be set with OpenBSD version 4.0 and up.
38 * - PF_NEWSSTYLE
39 *   Must be set with OpenBSD version 4.7 and up.
40 */
41
42#include <sys/types.h>
43#include <sys/socket.h>
44#include <sys/param.h>
45#include <net/if.h>
46#include <netinet/in.h>
47#include <netinet/tcp.h>
48#include <arpa/inet.h>
49#ifdef __DragonFly__
50#include <net/pf/pfvar.h>
51#else
52#ifdef __APPLE__
53#define PRIVATE 1
54#endif
55#include <net/pfvar.h>
56#endif
57#include <fcntl.h>
58#include <sys/ioctl.h>
59#include <unistd.h>
60#include <string.h>
61#include <syslog.h>
62#include <stdio.h>
63#include <stdlib.h>
64
65#include "../macros.h"
66#include "../config.h"
67#include "obsdrdr.h"
68#include "../upnpglobalvars.h"
69
70#ifndef USE_PF
71#error "USE_PF macro is undefined, check consistency between config.h and Makefile"
72#else
73
74/* list too keep timestamps for port mappings having a lease duration */
75struct timestamp_entry {
76	struct timestamp_entry * next;
77	unsigned int timestamp;
78	unsigned short eport;
79	short protocol;
80};
81
82static struct timestamp_entry * timestamp_list = NULL;
83
84static unsigned int
85get_timestamp(unsigned short eport, int proto)
86{
87	struct timestamp_entry * e;
88	e = timestamp_list;
89	while(e) {
90		if(e->eport == eport && e->protocol == (short)proto)
91			return e->timestamp;
92		e = e->next;
93	}
94	return 0;
95}
96
97static void
98remove_timestamp_entry(unsigned short eport, int proto)
99{
100	struct timestamp_entry * e;
101	struct timestamp_entry * * p;
102	p = &timestamp_list;
103	e = *p;
104	while(e) {
105		if(e->eport == eport && e->protocol == (short)proto) {
106			/* remove the entry */
107			*p = e->next;
108			free(e);
109			return;
110		}
111		p = &(e->next);
112		e = *p;
113	}
114}
115
116/* /dev/pf when opened */
117int dev = -1;
118
119/* shutdown_redirect() :
120 * close the /dev/pf device */
121void
122shutdown_redirect(void)
123{
124	if(close(dev)<0)
125		syslog(LOG_ERR, "close(\"/dev/pf\"): %m");
126	dev = -1;
127}
128
129/* open the device */
130int
131init_redirect(void)
132{
133	struct pf_status status;
134	if(dev>=0)
135		shutdown_redirect();
136	dev = open("/dev/pf", O_RDWR);
137	if(dev<0) {
138		syslog(LOG_ERR, "open(\"/dev/pf\"): %m");
139		return -1;
140	}
141	if(ioctl(dev, DIOCGETSTATUS, &status)<0) {
142		syslog(LOG_ERR, "DIOCGETSTATUS: %m");
143		return -1;
144	}
145	if(!status.running) {
146		syslog(LOG_ERR, "pf is disabled");
147		return -1;
148	}
149	return 0;
150}
151
152#if TEST
153/* for debug */
154int
155clear_redirect_rules(void)
156{
157	struct pfioc_trans io;
158	struct pfioc_trans_e ioe;
159	if(dev<0) {
160		syslog(LOG_ERR, "pf device is not open");
161		return -1;
162	}
163	memset(&ioe, 0, sizeof(ioe));
164	io.size = 1;
165	io.esize = sizeof(ioe);
166	io.array = &ioe;
167#ifndef PF_NEWSTYLE
168	ioe.rs_num = PF_RULESET_RDR;
169#else
170	ioe.type = PF_TRANS_RULESET;
171#endif
172	strlcpy(ioe.anchor, anchor_name, MAXPATHLEN);
173	if(ioctl(dev, DIOCXBEGIN, &io) < 0)
174	{
175		syslog(LOG_ERR, "ioctl(dev, DIOCXBEGIN, ...): %m");
176		goto error;
177	}
178	if(ioctl(dev, DIOCXCOMMIT, &io) < 0)
179	{
180		syslog(LOG_ERR, "ioctl(dev, DIOCXCOMMIT, ...): %m");
181		goto error;
182	}
183	return 0;
184error:
185	return -1;
186}
187
188int
189clear_filter_rules(void)
190{
191#ifndef PF_ENABLE_FILTER_RULES
192	return 0;
193#else
194	struct pfioc_trans io;
195	struct pfioc_trans_e ioe;
196	if(dev<0) {
197		syslog(LOG_ERR, "pf device is not open");
198		return -1;
199	}
200	memset(&ioe, 0, sizeof(ioe));
201	io.size = 1;
202	io.esize = sizeof(ioe);
203	io.array = &ioe;
204#ifndef PF_NEWSTYLE
205	ioe.rs_num = PF_RULESET_FILTER;
206#else
207	/* ? */
208	ioe.type = PF_TRANS_RULESET;
209#endif
210	strlcpy(ioe.anchor, anchor_name, MAXPATHLEN);
211	if(ioctl(dev, DIOCXBEGIN, &io) < 0)
212	{
213		syslog(LOG_ERR, "ioctl(dev, DIOCXBEGIN, ...): %m");
214		goto error;
215	}
216	if(ioctl(dev, DIOCXCOMMIT, &io) < 0)
217	{
218		syslog(LOG_ERR, "ioctl(dev, DIOCXCOMMIT, ...): %m");
219		goto error;
220	}
221	return 0;
222error:
223	return -1;
224#endif
225}
226#endif
227
228/* add_redirect_rule2() :
229 * create a rdr rule */
230int
231add_redirect_rule2(const char * ifname,
232                   const char * rhost, unsigned short eport,
233                   const char * iaddr, unsigned short iport, int proto,
234                   const char * desc, unsigned int timestamp)
235{
236	int r;
237	struct pfioc_rule pcr;
238#ifndef PF_NEWSTYLE
239	struct pfioc_pooladdr pp;
240	struct pf_pooladdr *a;
241#endif
242	if(dev<0) {
243		syslog(LOG_ERR, "pf device is not open");
244		return -1;
245	}
246	r = 0;
247	memset(&pcr, 0, sizeof(pcr));
248	strlcpy(pcr.anchor, anchor_name, MAXPATHLEN);
249
250#ifndef PF_NEWSTYLE
251	memset(&pp, 0, sizeof(pp));
252	strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
253	if(ioctl(dev, DIOCBEGINADDRS, &pp) < 0)
254	{
255		syslog(LOG_ERR, "ioctl(dev, DIOCBEGINADDRS, ...): %m");
256		r = -1;
257	}
258	else
259	{
260		pcr.pool_ticket = pp.ticket;
261#else
262	if(1)
263	{
264		pcr.rule.direction = PF_IN;
265		/*pcr.rule.src.addr.type = PF_ADDR_NONE;*/
266		pcr.rule.src.addr.type = PF_ADDR_ADDRMASK;
267		pcr.rule.dst.addr.type = PF_ADDR_ADDRMASK;
268		pcr.rule.nat.addr.type = PF_ADDR_NONE;
269		pcr.rule.rdr.addr.type = PF_ADDR_ADDRMASK;
270#endif
271
272#ifdef __APPLE__
273		pcr.rule.dst.xport.range.op = PF_OP_EQ;
274		pcr.rule.dst.xport.range.port[0] = htons(eport);
275		pcr.rule.dst.xport.range.port[1] = htons(eport);
276#else
277		pcr.rule.dst.port_op = PF_OP_EQ;
278		pcr.rule.dst.port[0] = htons(eport);
279		pcr.rule.dst.port[1] = htons(eport);
280#endif
281#ifndef PF_NEWSTYLE
282		pcr.rule.action = PF_RDR;
283#ifndef PF_ENABLE_FILTER_RULES
284		pcr.rule.natpass = 1;
285#else
286		pcr.rule.natpass = 0;
287#endif
288#else
289#ifndef PF_ENABLE_FILTER_RULES
290		pcr.rule.action = PF_PASS;
291#else
292		pcr.rule.action = PF_MATCH;
293#endif
294#endif
295		pcr.rule.af = AF_INET;
296#ifdef USE_IFNAME_IN_RULES
297		if(ifname)
298			strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ);
299#endif
300		pcr.rule.proto = proto;
301		pcr.rule.log = (GETFLAG(LOGPACKETSMASK))?1:0;	/*logpackets;*/
302#ifdef PFRULE_HAS_RTABLEID
303		pcr.rule.rtableid = -1;	/* first appeared in OpenBSD 4.0 */
304#endif
305#ifdef PFRULE_HAS_ONRDOMAIN
306		pcr.rule.onrdomain = -1;	/* first appeared in OpenBSD 5.0 */
307#endif
308		pcr.rule.quick = 1;
309		pcr.rule.keep_state = PF_STATE_NORMAL;
310		if(tag)
311			strlcpy(pcr.rule.tagname, tag, PF_TAG_NAME_SIZE);
312		strlcpy(pcr.rule.label, desc, PF_RULE_LABEL_SIZE);
313		if(rhost && rhost[0] != '\0' && rhost[0] != '*')
314		{
315			inet_pton(AF_INET, rhost, &pcr.rule.src.addr.v.a.addr.v4.s_addr);
316			pcr.rule.src.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE);
317		}
318#ifndef PF_NEWSTYLE
319		pcr.rule.rpool.proxy_port[0] = iport;
320		pcr.rule.rpool.proxy_port[1] = iport;
321		TAILQ_INIT(&pcr.rule.rpool.list);
322		a = calloc(1, sizeof(struct pf_pooladdr));
323		inet_pton(AF_INET, iaddr, &a->addr.v.a.addr.v4.s_addr);
324		a->addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE);
325		TAILQ_INSERT_TAIL(&pcr.rule.rpool.list, a, entries);
326
327		memcpy(&pp.addr, a, sizeof(struct pf_pooladdr));
328		if(ioctl(dev, DIOCADDADDR, &pp) < 0)
329		{
330			syslog(LOG_ERR, "ioctl(dev, DIOCADDADDR, ...): %m");
331			r = -1;
332		}
333		else
334		{
335#else
336		pcr.rule.rdr.proxy_port[0] = iport;
337		pcr.rule.rdr.proxy_port[1] = iport;
338		inet_pton(AF_INET, iaddr, &pcr.rule.rdr.addr.v.a.addr.v4.s_addr);
339		pcr.rule.rdr.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE);
340		if(1)
341		{
342#endif
343			pcr.action = PF_CHANGE_GET_TICKET;
344        	if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0)
345			{
346            	syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m");
347				r = -1;
348			}
349			else
350			{
351				pcr.action = PF_CHANGE_ADD_TAIL;
352				if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0)
353				{
354					syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_ADD_TAIL: %m");
355					r = -1;
356				}
357			}
358		}
359#ifndef PF_NEWSTYLE
360		free(a);
361#endif
362	}
363	if(r == 0 && timestamp > 0)
364	{
365		struct timestamp_entry * tmp;
366		tmp = malloc(sizeof(struct timestamp_entry));
367		if(tmp)
368		{
369			tmp->next = timestamp_list;
370			tmp->timestamp = timestamp;
371			tmp->eport = eport;
372			tmp->protocol = (short)proto;
373			timestamp_list = tmp;
374		}
375	}
376	return r;
377}
378
379/* thanks to Seth Mos for this function */
380int
381add_filter_rule2(const char * ifname,
382                 const char * rhost, const char * iaddr,
383                 unsigned short eport, unsigned short iport,
384				 int proto, const char * desc)
385{
386#ifndef PF_ENABLE_FILTER_RULES
387	UNUSED(ifname);
388	UNUSED(rhost); UNUSED(iaddr);
389	UNUSED(eport); UNUSED(iport);
390	UNUSED(proto); UNUSED(desc);
391	return 0;
392#else
393	int r;
394	struct pfioc_rule pcr;
395#ifndef PF_NEWSTYLE
396	struct pfioc_pooladdr pp;
397#endif
398#ifndef USE_IFNAME_IN_RULES
399	UNUSED(ifname);
400#endif
401	UNUSED(eport);
402	if(dev<0) {
403		syslog(LOG_ERR, "pf device is not open");
404		return -1;
405	}
406	r = 0;
407	memset(&pcr, 0, sizeof(pcr));
408	strlcpy(pcr.anchor, anchor_name, MAXPATHLEN);
409
410#ifndef PF_NEWSTYLE
411	memset(&pp, 0, sizeof(pp));
412	strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
413	if(ioctl(dev, DIOCBEGINADDRS, &pp) < 0)
414	{
415		syslog(LOG_ERR, "ioctl(dev, DIOCBEGINADDRS, ...): %m");
416		r = -1;
417	}
418	else
419	{
420		pcr.pool_ticket = pp.ticket;
421#else
422	if(1)
423	{
424#endif
425		pcr.rule.dst.port_op = PF_OP_EQ;
426		pcr.rule.dst.port[0] = htons(iport);
427		pcr.rule.direction = PF_IN;
428		pcr.rule.action = PF_PASS;
429		pcr.rule.af = AF_INET;
430#ifdef USE_IFNAME_IN_RULES
431		if(ifname)
432			strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ);
433#endif
434		pcr.rule.proto = proto;
435		pcr.rule.quick = (GETFLAG(PFNOQUICKRULESMASK))?0:1;
436		pcr.rule.log = (GETFLAG(LOGPACKETSMASK))?1:0;	/*logpackets;*/
437/* see the discussion on the forum :
438 * http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=638 */
439		pcr.rule.flags = TH_SYN;
440		pcr.rule.flagset = (TH_SYN|TH_ACK);
441#ifdef PFRULE_HAS_RTABLEID
442		pcr.rule.rtableid = -1;	/* first appeared in OpenBSD 4.0 */
443#endif
444#ifdef PFRULE_HAS_ONRDOMAIN
445		pcr.rule.onrdomain = -1;	/* first appeared in OpenBSD 5.0 */
446#endif
447		pcr.rule.keep_state = 1;
448		strlcpy(pcr.rule.label, desc, PF_RULE_LABEL_SIZE);
449		if(queue)
450			strlcpy(pcr.rule.qname, queue, PF_QNAME_SIZE);
451		if(tag)
452			strlcpy(pcr.rule.tagname, tag, PF_TAG_NAME_SIZE);
453
454		if(rhost && rhost[0] != '\0' && rhost[0] != '*')
455		{
456			inet_pton(AF_INET, rhost, &pcr.rule.src.addr.v.a.addr.v4.s_addr);
457			pcr.rule.src.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE);
458		}
459		/* we want any - iaddr port = # keep state label */
460		inet_pton(AF_INET, iaddr, &pcr.rule.dst.addr.v.a.addr.v4.s_addr);
461		pcr.rule.dst.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE);
462#ifndef PF_NEWSTYLE
463		pcr.rule.rpool.proxy_port[0] = iport;
464		pcr.rule.rpool.proxy_port[1] = iport;
465		TAILQ_INIT(&pcr.rule.rpool.list);
466#endif
467		if(1)
468		{
469			pcr.action = PF_CHANGE_GET_TICKET;
470        	if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0)
471			{
472            	syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m");
473				r = -1;
474			}
475			else
476			{
477				pcr.action = PF_CHANGE_ADD_TAIL;
478				if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0)
479				{
480					syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_ADD_TAIL: %m");
481					r = -1;
482				}
483			}
484		}
485	}
486	return r;
487#endif
488}
489
490/* get_redirect_rule()
491 * return value : 0 success (found)
492 * -1 = error or rule not found */
493int
494get_redirect_rule(const char * ifname, unsigned short eport, int proto,
495                  char * iaddr, int iaddrlen, unsigned short * iport,
496                  char * desc, int desclen,
497                  char * rhost, int rhostlen,
498                  unsigned int * timestamp,
499                  u_int64_t * packets, u_int64_t * bytes)
500{
501	int i, n;
502	struct pfioc_rule pr;
503#ifndef PF_NEWSTYLE
504	struct pfioc_pooladdr pp;
505#endif
506	UNUSED(ifname);
507
508	if(dev<0) {
509		syslog(LOG_ERR, "pf device is not open");
510		return -1;
511	}
512	memset(&pr, 0, sizeof(pr));
513	strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
514#ifndef PF_NEWSTYLE
515	pr.rule.action = PF_RDR;
516#endif
517	if(ioctl(dev, DIOCGETRULES, &pr) < 0)
518	{
519		syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m");
520		goto error;
521	}
522	n = pr.nr;
523	for(i=0; i<n; i++)
524	{
525		pr.nr = i;
526		if(ioctl(dev, DIOCGETRULE, &pr) < 0)
527		{
528			syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m");
529			goto error;
530		}
531#ifdef __APPLE__
532		if( (eport == ntohs(pr.rule.dst.xport.range.port[0]))
533		  && (eport == ntohs(pr.rule.dst.xport.range.port[1]))
534#else
535		if( (eport == ntohs(pr.rule.dst.port[0]))
536		  && (eport == ntohs(pr.rule.dst.port[1]))
537#endif
538		  && (pr.rule.proto == proto) )
539		{
540#ifndef PF_NEWSTYLE
541			*iport = pr.rule.rpool.proxy_port[0];
542#else
543			*iport = pr.rule.rdr.proxy_port[0];
544#endif
545			if(desc)
546				strlcpy(desc, pr.rule.label, desclen);
547#ifdef PFRULE_INOUT_COUNTS
548			if(packets)
549				*packets = pr.rule.packets[0] + pr.rule.packets[1];
550			if(bytes)
551				*bytes = pr.rule.bytes[0] + pr.rule.bytes[1];
552#else
553			if(packets)
554				*packets = pr.rule.packets;
555			if(bytes)
556				*bytes = pr.rule.bytes;
557#endif
558#ifndef PF_NEWSTYLE
559			memset(&pp, 0, sizeof(pp));
560			strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
561			pp.r_action = PF_RDR;
562			pp.r_num = i;
563			pp.ticket = pr.ticket;
564			if(ioctl(dev, DIOCGETADDRS, &pp) < 0)
565			{
566				syslog(LOG_ERR, "ioctl(dev, DIOCGETADDRS, ...): %m");
567				goto error;
568			}
569			if(pp.nr != 1)
570			{
571				syslog(LOG_NOTICE, "No address associated with pf rule");
572				goto error;
573			}
574			pp.nr = 0;	/* first */
575			if(ioctl(dev, DIOCGETADDR, &pp) < 0)
576			{
577				syslog(LOG_ERR, "ioctl(dev, DIOCGETADDR, ...): %m");
578				goto error;
579			}
580			inet_ntop(AF_INET, &pp.addr.addr.v.a.addr.v4.s_addr,
581			          iaddr, iaddrlen);
582#else
583			inet_ntop(AF_INET, &pr.rule.rdr.addr.v.a.addr.v4.s_addr,
584			          iaddr, iaddrlen);
585#endif
586			if(rhost && rhostlen > 0)
587			{
588				if (pr.rule.src.addr.v.a.addr.v4.s_addr == 0)
589				{
590					rhost[0] = '\0'; /* empty string */
591				}
592				else
593				{
594					inet_ntop(AF_INET, &pr.rule.src.addr.v.a.addr.v4.s_addr,
595					          rhost, rhostlen);
596				}
597			}
598			if(timestamp)
599				*timestamp = get_timestamp(eport, proto);
600			return 0;
601		}
602	}
603error:
604	return -1;
605}
606
607static int
608priv_delete_redirect_rule(const char * ifname, unsigned short eport,
609                          int proto, unsigned short * iport,
610                          in_addr_t * iaddr)
611{
612	int i, n;
613	struct pfioc_rule pr;
614	UNUSED(ifname);
615
616	if(dev<0) {
617		syslog(LOG_ERR, "pf device is not open");
618		return -1;
619	}
620	memset(&pr, 0, sizeof(pr));
621	strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
622#ifndef PF_NEWSTYLE
623	pr.rule.action = PF_RDR;
624#endif
625	if(ioctl(dev, DIOCGETRULES, &pr) < 0)
626	{
627		syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m");
628		goto error;
629	}
630	n = pr.nr;
631	for(i=0; i<n; i++)
632	{
633		pr.nr = i;
634		if(ioctl(dev, DIOCGETRULE, &pr) < 0)
635		{
636			syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m");
637			goto error;
638		}
639#ifdef __APPLE__
640		if( (eport == ntohs(pr.rule.dst.xport.range.port[0]))
641		  && (eport == ntohs(pr.rule.dst.xport.range.port[1]))
642#else
643		if( (eport == ntohs(pr.rule.dst.port[0]))
644		  && (eport == ntohs(pr.rule.dst.port[1]))
645#endif
646		  && (pr.rule.proto == proto) )
647		{
648			/* retrieve iport in order to remove filter rule */
649#ifndef PF_NEWSTYLE
650			if(iport) *iport = pr.rule.rpool.proxy_port[0];
651			if(iaddr)
652			{
653				/* retrieve internal address */
654				struct pfioc_pooladdr pp;
655				memset(&pp, 0, sizeof(pp));
656				strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
657				pp.r_action = PF_RDR;
658				pp.r_num = i;
659				pp.ticket = pr.ticket;
660				if(ioctl(dev, DIOCGETADDRS, &pp) < 0)
661				{
662					syslog(LOG_ERR, "ioctl(dev, DIOCGETADDRS, ...): %m");
663					goto error;
664				}
665				if(pp.nr != 1)
666				{
667					syslog(LOG_NOTICE, "No address associated with pf rule");
668					goto error;
669				}
670				pp.nr = 0;	/* first */
671				if(ioctl(dev, DIOCGETADDR, &pp) < 0)
672				{
673					syslog(LOG_ERR, "ioctl(dev, DIOCGETADDR, ...): %m");
674					goto error;
675				}
676				*iaddr = pp.addr.addr.v.a.addr.v4.s_addr;
677			}
678#else
679			if(iport) *iport = pr.rule.rdr.proxy_port[0];
680			if(iaddr)
681			{
682				/* retrieve internal address */
683				*iaddr = pr.rule.rdr.addr.v.a.addr.v4.s_addr;
684			}
685#endif
686			pr.action = PF_CHANGE_GET_TICKET;
687        	if(ioctl(dev, DIOCCHANGERULE, &pr) < 0)
688			{
689            	syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m");
690				goto error;
691			}
692			pr.action = PF_CHANGE_REMOVE;
693			pr.nr = i;
694			if(ioctl(dev, DIOCCHANGERULE, &pr) < 0)
695			{
696				syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_REMOVE: %m");
697				goto error;
698			}
699			remove_timestamp_entry(eport, proto);
700			return 0;
701		}
702	}
703error:
704	return -1;
705}
706
707int
708delete_redirect_rule(const char * ifname, unsigned short eport,
709                    int proto)
710{
711	return priv_delete_redirect_rule(ifname, eport, proto, NULL, NULL);
712}
713
714static int
715priv_delete_filter_rule(const char * ifname, unsigned short iport,
716                        int proto, in_addr_t iaddr)
717{
718#ifndef PF_ENABLE_FILTER_RULES
719	UNUSED(ifname); UNUSED(iport); UNUSED(proto); UNUSED(iaddr);
720	return 0;
721#else
722	int i, n;
723	struct pfioc_rule pr;
724	UNUSED(ifname);
725	if(dev<0) {
726		syslog(LOG_ERR, "pf device is not open");
727		return -1;
728	}
729	memset(&pr, 0, sizeof(pr));
730	strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
731	pr.rule.action = PF_PASS;
732	if(ioctl(dev, DIOCGETRULES, &pr) < 0)
733	{
734		syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m");
735		goto error;
736	}
737	n = pr.nr;
738	for(i=0; i<n; i++)
739	{
740		pr.nr = i;
741		if(ioctl(dev, DIOCGETRULE, &pr) < 0)
742		{
743			syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m");
744			goto error;
745		}
746#ifdef TEST
747syslog(LOG_DEBUG, "%2d port=%hu proto=%d addr=%8x",
748       i, ntohs(pr.rule.dst.port[0]), pr.rule.proto,
749       pr.rule.dst.addr.v.a.addr.v4.s_addr);
750/*pr.rule.dst.addr.v.a.mask.v4.s_addr*/
751#endif
752		if( (iport == ntohs(pr.rule.dst.port[0]))
753		  && (pr.rule.proto == proto) &&
754		   (iaddr == pr.rule.dst.addr.v.a.addr.v4.s_addr)
755		  )
756		{
757			pr.action = PF_CHANGE_GET_TICKET;
758        	if(ioctl(dev, DIOCCHANGERULE, &pr) < 0)
759			{
760            	syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m");
761				goto error;
762			}
763			pr.action = PF_CHANGE_REMOVE;
764			pr.nr = i;
765			if(ioctl(dev, DIOCCHANGERULE, &pr) < 0)
766			{
767				syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_REMOVE: %m");
768				goto error;
769			}
770			return 0;
771		}
772	}
773error:
774	return -1;
775#endif
776}
777
778int
779delete_redirect_and_filter_rules(const char * ifname, unsigned short eport,
780                                 int proto)
781{
782	int r;
783	unsigned short iport;
784	in_addr_t iaddr;
785	r = priv_delete_redirect_rule(ifname, eport, proto, &iport, &iaddr);
786	if(r == 0)
787	{
788		r = priv_delete_filter_rule(ifname, iport, proto, iaddr);
789	}
790	return r;
791}
792
793int
794get_redirect_rule_by_index(int index,
795                           char * ifname, unsigned short * eport,
796                           char * iaddr, int iaddrlen, unsigned short * iport,
797                           int * proto, char * desc, int desclen,
798                           char * rhost, int rhostlen,
799                           unsigned int * timestamp,
800                           u_int64_t * packets, u_int64_t * bytes)
801{
802	int n;
803	struct pfioc_rule pr;
804#ifndef PF_NEWSTYLE
805	struct pfioc_pooladdr pp;
806#endif
807	if(index < 0)
808		return -1;
809	if(dev<0) {
810		syslog(LOG_ERR, "pf device is not open");
811		return -1;
812	}
813	memset(&pr, 0, sizeof(pr));
814	strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
815#ifndef PF_NEWSTYLE
816	pr.rule.action = PF_RDR;
817#endif
818	if(ioctl(dev, DIOCGETRULES, &pr) < 0)
819	{
820		syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m");
821		goto error;
822	}
823	n = pr.nr;
824	if(index >= n)
825		goto error;
826	pr.nr = index;
827	if(ioctl(dev, DIOCGETRULE, &pr) < 0)
828	{
829		syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m");
830		goto error;
831	}
832	*proto = pr.rule.proto;
833#ifdef __APPLE__
834	*eport = ntohs(pr.rule.dst.xport.range.port[0]);
835#else
836	*eport = ntohs(pr.rule.dst.port[0]);
837#endif
838#ifndef PF_NEWSTYLE
839	*iport = pr.rule.rpool.proxy_port[0];
840#else
841	*iport = pr.rule.rdr.proxy_port[0];
842#endif
843	if(ifname)
844		strlcpy(ifname, pr.rule.ifname, IFNAMSIZ);
845	if(desc)
846		strlcpy(desc, pr.rule.label, desclen);
847#ifdef PFRULE_INOUT_COUNTS
848	if(packets)
849		*packets = pr.rule.packets[0] + pr.rule.packets[1];
850	if(bytes)
851		*bytes = pr.rule.bytes[0] + pr.rule.bytes[1];
852#else
853	if(packets)
854		*packets = pr.rule.packets;
855	if(bytes)
856		*bytes = pr.rule.bytes;
857#endif
858#ifndef PF_NEWSTYLE
859	memset(&pp, 0, sizeof(pp));
860	strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
861	pp.r_action = PF_RDR;
862	pp.r_num = index;
863	pp.ticket = pr.ticket;
864	if(ioctl(dev, DIOCGETADDRS, &pp) < 0)
865	{
866		syslog(LOG_ERR, "ioctl(dev, DIOCGETADDRS, ...): %m");
867		goto error;
868	}
869	if(pp.nr != 1)
870	{
871		syslog(LOG_NOTICE, "No address associated with pf rule");
872		goto error;
873	}
874	pp.nr = 0;	/* first */
875	if(ioctl(dev, DIOCGETADDR, &pp) < 0)
876	{
877		syslog(LOG_ERR, "ioctl(dev, DIOCGETADDR, ...): %m");
878		goto error;
879	}
880	inet_ntop(AF_INET, &pp.addr.addr.v.a.addr.v4.s_addr,
881	          iaddr, iaddrlen);
882#else
883	inet_ntop(AF_INET, &pr.rule.rdr.addr.v.a.addr.v4.s_addr,
884	          iaddr, iaddrlen);
885#endif
886	if(rhost && rhostlen > 0)
887	{
888		if (pr.rule.src.addr.v.a.addr.v4.s_addr == 0)
889		{
890			rhost[0] = '\0'; /* empty string */
891		}
892		else
893		{
894			inet_ntop(AF_INET, &pr.rule.src.addr.v.a.addr.v4.s_addr,
895			          rhost, rhostlen);
896		}
897	}
898	if(timestamp)
899		*timestamp = get_timestamp(*eport, *proto);
900	return 0;
901error:
902	return -1;
903}
904
905/* return an (malloc'ed) array of "external" port for which there is
906 * a port mapping. number is the size of the array */
907unsigned short *
908get_portmappings_in_range(unsigned short startport, unsigned short endport,
909                          int proto, unsigned int * number)
910{
911	unsigned short * array;
912	unsigned int capacity;
913	int i, n;
914	unsigned short eport;
915	struct pfioc_rule pr;
916
917	*number = 0;
918	if(dev<0) {
919		syslog(LOG_ERR, "pf device is not open");
920		return NULL;
921	}
922	capacity = 128;
923	array = calloc(capacity, sizeof(unsigned short));
924	if(!array)
925	{
926		syslog(LOG_ERR, "get_portmappings_in_range() : calloc error");
927		return NULL;
928	}
929	memset(&pr, 0, sizeof(pr));
930	strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
931#ifndef PF_NEWSTYLE
932	pr.rule.action = PF_RDR;
933#endif
934	if(ioctl(dev, DIOCGETRULES, &pr) < 0)
935	{
936		syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m");
937		free(array);
938		return NULL;
939	}
940	n = pr.nr;
941	for(i=0; i<n; i++)
942	{
943		pr.nr = i;
944		if(ioctl(dev, DIOCGETRULE, &pr) < 0)
945		{
946			syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m");
947			continue;
948		}
949#ifdef __APPLE__
950		eport = ntohs(pr.rule.dst.xport.range.port[0]);
951		if( (eport == ntohs(pr.rule.dst.xport.range.port[1]))
952#else
953		eport = ntohs(pr.rule.dst.port[0]);
954		if( (eport == ntohs(pr.rule.dst.port[1]))
955#endif
956		  && (pr.rule.proto == proto)
957		  && (startport <= eport) && (eport <= endport) )
958		{
959			if(*number >= capacity)
960			{
961				/* need to increase the capacity of the array */
962				unsigned short * tmp;
963				capacity += 128;
964				tmp = realloc(array, sizeof(unsigned short)*capacity);
965				if(!tmp)
966				{
967					syslog(LOG_ERR, "get_portmappings_in_range() : realloc(%lu) error", sizeof(unsigned short)*capacity);
968					*number = 0;
969					free(array);
970					return NULL;
971				}
972				array = tmp;
973			}
974			array[*number] = eport;
975			(*number)++;
976		}
977	}
978	return array;
979}
980
981/* this function is only for testing */
982#if TEST
983void
984list_rules(void)
985{
986	char buf[32];
987	int i, n;
988	struct pfioc_rule pr;
989#ifndef PF_NEWSTYLE
990	struct pfioc_pooladdr pp;
991#endif
992
993	if(dev<0)
994	{
995		perror("pf dev not open");
996		return ;
997	}
998	memset(&pr, 0, sizeof(pr));
999	strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
1000	pr.rule.action = PF_RDR;
1001	if(ioctl(dev, DIOCGETRULES, &pr) < 0)
1002		perror("DIOCGETRULES");
1003	printf("ticket = %d, nr = %d\n", pr.ticket, pr.nr);
1004	n = pr.nr;
1005	for(i=0; i<n; i++)
1006	{
1007		printf("-- rule %d --\n", i);
1008		pr.nr = i;
1009		if(ioctl(dev, DIOCGETRULE, &pr) < 0)
1010			perror("DIOCGETRULE");
1011		printf(" %s %s %d:%d -> %d:%d  proto %d keep_state=%d action=%d\n",
1012			pr.rule.ifname,
1013			inet_ntop(AF_INET, &pr.rule.src.addr.v.a.addr.v4.s_addr, buf, 32),
1014			(int)ntohs(pr.rule.dst.port[0]),
1015			(int)ntohs(pr.rule.dst.port[1]),
1016#ifndef PF_NEWSTYLE
1017			(int)pr.rule.rpool.proxy_port[0],
1018			(int)pr.rule.rpool.proxy_port[1],
1019#else
1020			(int)pr.rule.rdr.proxy_port[0],
1021			(int)pr.rule.rdr.proxy_port[1],
1022#endif
1023			(int)pr.rule.proto,
1024			(int)pr.rule.keep_state,
1025			(int)pr.rule.action);
1026		printf("  description: \"%s\"\n", pr.rule.label);
1027#ifndef PF_NEWSTYLE
1028		memset(&pp, 0, sizeof(pp));
1029		strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
1030		pp.r_action = PF_RDR;
1031		pp.r_num = i;
1032		pp.ticket = pr.ticket;
1033		if(ioctl(dev, DIOCGETADDRS, &pp) < 0)
1034			perror("DIOCGETADDRS");
1035		printf("  nb pool addr = %d ticket=%d\n", pp.nr, pp.ticket);
1036		/*if(ioctl(dev, DIOCGETRULE, &pr) < 0)
1037			perror("DIOCGETRULE"); */
1038		pp.nr = 0;	/* first */
1039		if(ioctl(dev, DIOCGETADDR, &pp) < 0)
1040			perror("DIOCGETADDR");
1041		/* addr.v.a.addr.v4.s_addr */
1042		printf("  %s\n", inet_ntop(AF_INET, &pp.addr.addr.v.a.addr.v4.s_addr, buf, 32));
1043#else
1044		printf("  rule_flag=%08x action=%d direction=%d log=%d logif=%d "
1045		       "quick=%d ifnot=%d af=%d type=%d code=%d rdr.port_op=%d rdr.opts=%d\n",
1046		       pr.rule.rule_flag, pr.rule.action, pr.rule.direction,
1047		       pr.rule.log, pr.rule.logif, pr.rule.quick, pr.rule.ifnot,
1048		       pr.rule.af, pr.rule.type, pr.rule.code,
1049		       pr.rule.rdr.port_op, pr.rule.rdr.opts);
1050		printf("  %s\n", inet_ntop(AF_INET, &pr.rule.rdr.addr.v.a.addr.v4.s_addr, buf, 32));
1051#endif
1052	}
1053}
1054#endif /* TEST */
1055
1056#endif /* USE_PF */
1057