1/* $Id: pfpinhole.c,v 1.24 2014/12/05 09:54:55 nanard Exp $ */
2/* MiniUPnP project
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2012 Thomas Bernard
5 * This software is subject to the conditions detailed
6 * in the LICENCE file provided within the distribution */
7
8#include <sys/types.h>
9#include <sys/socket.h>
10#include <sys/param.h>
11#include <net/if.h>
12#include <netinet/in.h>
13#include <netinet/tcp.h>
14#include <arpa/inet.h>
15#ifdef __DragonFly__
16#include <net/pf/pfvar.h>
17#else
18#ifdef __APPLE__
19#define PRIVATE 1
20#endif
21#include <net/pfvar.h>
22#endif
23#include <fcntl.h>
24#include <sys/ioctl.h>
25#include <unistd.h>
26#include <string.h>
27#include <syslog.h>
28#include <stdio.h>
29#include <stdlib.h>
30
31#include "../config.h"
32#include "pfpinhole.h"
33#include "../upnpglobalvars.h"
34#include "../macros.h"
35
36/* the pass rules created by add_pinhole() are as follow :
37 *
38 * pass in quick on ep0 inet6 proto udp
39 *   from any to dead:beef::42:42 port = 8080
40 *   flags S/SA keep state
41 *   label "pinhole-2 ts-4321000"
42 *
43 * with the label "pinhole-$uid ts-$timestamp"
44 */
45
46#ifdef ENABLE_UPNPPINHOLE
47
48/* /dev/pf when opened */
49extern int dev;
50
51static int next_uid = 1;
52
53#define PINEHOLE_LABEL_FORMAT "pinhole-%d ts-%u: %s"
54#define PINEHOLE_LABEL_FORMAT_SKIPDESC "pinhole-%d ts-%u: %*s"
55
56int add_pinhole(const char * ifname,
57                const char * rem_host, unsigned short rem_port,
58                const char * int_client, unsigned short int_port,
59                int proto, const char * desc, unsigned int timestamp)
60{
61	int uid;
62	struct pfioc_rule pcr;
63#ifndef PF_NEWSTYLE
64	struct pfioc_pooladdr pp;
65#endif
66
67	if(dev<0) {
68		syslog(LOG_ERR, "pf device is not open");
69		return -1;
70	}
71	memset(&pcr, 0, sizeof(pcr));
72	strlcpy(pcr.anchor, anchor_name, MAXPATHLEN);
73
74#ifndef PF_NEWSTYLE
75	memset(&pp, 0, sizeof(pp));
76	strlcpy(pp.anchor, anchor_name, MAXPATHLEN);
77	if(ioctl(dev, DIOCBEGINADDRS, &pp) < 0) {
78		syslog(LOG_ERR, "ioctl(dev, DIOCBEGINADDRS, ...): %m");
79		return -1;
80	} else {
81		pcr.pool_ticket = pp.ticket;
82#else
83	{
84#endif
85		pcr.rule.direction = PF_IN;
86		pcr.rule.action = PF_PASS;
87		pcr.rule.af = AF_INET6;
88#ifdef PF_NEWSTYLE
89		pcr.rule.nat.addr.type = PF_ADDR_NONE;
90		pcr.rule.rdr.addr.type = PF_ADDR_NONE;
91#endif
92#ifdef USE_IFNAME_IN_RULES
93		if(ifname)
94			strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ);
95#endif
96		pcr.rule.proto = proto;
97
98		pcr.rule.quick = 1;/*(GETFLAG(PFNOQUICKRULESMASK))?0:1;*/
99		pcr.rule.log = (GETFLAG(LOGPACKETSMASK))?1:0;	/*logpackets;*/
100/* see the discussion on the forum :
101 * http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=638 */
102		pcr.rule.flags = TH_SYN;
103		pcr.rule.flagset = (TH_SYN|TH_ACK);
104#ifdef PFRULE_HAS_RTABLEID
105		pcr.rule.rtableid = -1;	/* first appeared in OpenBSD 4.0 */
106#endif
107#ifdef PFRULE_HAS_ONRDOMAIN
108		pcr.rule.onrdomain = -1;	/* first appeared in OpenBSD 5.0 */
109#endif
110		pcr.rule.keep_state = 1;
111		uid = next_uid;
112		snprintf(pcr.rule.label, PF_RULE_LABEL_SIZE,
113		         PINEHOLE_LABEL_FORMAT, uid, timestamp, desc);
114		if(queue)
115			strlcpy(pcr.rule.qname, queue, PF_QNAME_SIZE);
116		if(tag)
117			strlcpy(pcr.rule.tagname, tag, PF_TAG_NAME_SIZE);
118
119		if(rem_port) {
120			pcr.rule.src.port_op = PF_OP_EQ;
121			pcr.rule.src.port[0] = htons(rem_port);
122		}
123		if(rem_host && rem_host[0] != '\0' && rem_host[0] != '*') {
124			pcr.rule.src.addr.type = PF_ADDR_ADDRMASK;
125			if(inet_pton(AF_INET6, rem_host, &pcr.rule.src.addr.v.a.addr.v6) != 1) {
126				syslog(LOG_ERR, "inet_pton(%s) failed", rem_host);
127			}
128			memset(&pcr.rule.src.addr.v.a.mask.addr8, 255, 16);
129		}
130
131		pcr.rule.dst.port_op = PF_OP_EQ;
132		pcr.rule.dst.port[0] = htons(int_port);
133		pcr.rule.dst.addr.type = PF_ADDR_ADDRMASK;
134		if(inet_pton(AF_INET6, int_client, &pcr.rule.dst.addr.v.a.addr.v6) != 1) {
135			syslog(LOG_ERR, "inet_pton(%s) failed", int_client);
136		}
137		memset(&pcr.rule.dst.addr.v.a.mask.addr8, 255, 16);
138
139		if(ifname)
140			strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ);
141
142		pcr.action = PF_CHANGE_GET_TICKET;
143		if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) {
144			syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m");
145			return -1;
146		} else {
147			pcr.action = PF_CHANGE_ADD_TAIL;
148			if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) {
149				syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_ADD_TAIL: %m");
150				return -1;
151			}
152		}
153	}
154
155	if(++next_uid >= 65535) {
156		next_uid = 1;
157	}
158	return uid;
159}
160
161int delete_pinhole(unsigned short uid)
162{
163	int i, n;
164	struct pfioc_rule pr;
165	char label_start[PF_RULE_LABEL_SIZE];
166	char tmp_label[PF_RULE_LABEL_SIZE];
167
168	if(dev<0) {
169		syslog(LOG_ERR, "pf device is not open");
170		return -1;
171	}
172	snprintf(label_start, sizeof(label_start),
173	         "pinhole-%hu", uid);
174	memset(&pr, 0, sizeof(pr));
175	strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
176#ifndef PF_NEWSTYLE
177	pr.rule.action = PF_PASS;
178#endif
179	if(ioctl(dev, DIOCGETRULES, &pr) < 0) {
180		syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m");
181		return -1;
182	}
183	n = pr.nr;
184	for(i=0; i<n; i++) {
185		pr.nr = i;
186		if(ioctl(dev, DIOCGETRULE, &pr) < 0) {
187			syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m");
188			return -1;
189		}
190		strlcpy(tmp_label, pr.rule.label, sizeof(tmp_label));
191		strtok(tmp_label, " ");
192		if(0 == strcmp(tmp_label, label_start)) {
193			pr.action = PF_CHANGE_GET_TICKET;
194			if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) {
195				syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m");
196				return -1;
197			}
198			pr.action = PF_CHANGE_REMOVE;
199			pr.nr = i;
200			if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) {
201				syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_REMOVE: %m");
202				return -1;
203			}
204			return 0;
205		}
206	}
207	/* not found */
208	return -2;
209}
210
211int
212get_pinhole_info(unsigned short uid,
213                 char * rem_host, int rem_hostlen, unsigned short * rem_port,
214                 char * int_client, int int_clientlen, unsigned short * int_port,
215                 int * proto, char * desc, int desclen,
216                 unsigned int * timestamp,
217                 u_int64_t * packets, u_int64_t * bytes)
218{
219	int i, n;
220	struct pfioc_rule pr;
221	char label_start[PF_RULE_LABEL_SIZE];
222	char tmp_label[PF_RULE_LABEL_SIZE];
223	char * p;
224
225	if(dev<0) {
226		syslog(LOG_ERR, "pf device is not open");
227		return -1;
228	}
229	snprintf(label_start, sizeof(label_start),
230	         "pinhole-%hu", uid);
231	memset(&pr, 0, sizeof(pr));
232	strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
233#ifndef PF_NEWSTYLE
234	pr.rule.action = PF_PASS;
235#endif
236	if(ioctl(dev, DIOCGETRULES, &pr) < 0) {
237		syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m");
238		return -1;
239	}
240	n = pr.nr;
241	for(i=0; i<n; i++) {
242		pr.nr = i;
243		if(ioctl(dev, DIOCGETRULE, &pr) < 0) {
244			syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m");
245			return -1;
246		}
247		strlcpy(tmp_label, pr.rule.label, sizeof(tmp_label));
248		p = tmp_label;
249		strsep(&p, " ");
250		if(0 == strcmp(tmp_label, label_start)) {
251			if(rem_host && (inet_ntop(AF_INET6, &pr.rule.src.addr.v.a.addr.v6, rem_host, rem_hostlen) == NULL)) {
252				return -1;
253			}
254			if(rem_port)
255				*rem_port = ntohs(pr.rule.src.port[0]);
256			if(int_client && (inet_ntop(AF_INET6, &pr.rule.dst.addr.v.a.addr.v6, int_client, int_clientlen) == NULL)) {
257				return -1;
258			}
259			if(int_port)
260				*int_port = ntohs(pr.rule.dst.port[0]);
261			if(proto)
262				*proto = pr.rule.proto;
263			if(timestamp)
264				sscanf(p, "ts-%u", timestamp);
265			if(desc) {
266				strsep(&p, " ");
267				if(p) {
268					strlcpy(desc, p, desclen);
269				} else {
270					desc[0] = '\0';
271				}
272			}
273#ifdef PFRULE_INOUT_COUNTS
274			if(packets)
275				*packets = pr.rule.packets[0] + pr.rule.packets[1];
276			if(bytes)
277				*bytes = pr.rule.bytes[0] + pr.rule.bytes[1];
278#else
279			if(packets)
280				*packets = pr.rule.packets;
281			if(bytes)
282				*bytes = pr.rule.bytes;
283#endif
284			return 0;
285		}
286	}
287	/* not found */
288	return -2;
289}
290
291int update_pinhole(unsigned short uid, unsigned int timestamp)
292{
293	/* TODO :
294	 * As it is not possible to change rule label, we should :
295	 * 1 - delete
296	 * 2 - Add new
297	 * the stats of the rule will then be reset :( */
298	UNUSED(uid); UNUSED(timestamp);
299	return -42; /* not implemented */
300}
301
302/* return the number of rules removed
303 * or a negative integer in case of error */
304int clean_pinhole_list(unsigned int * next_timestamp)
305{
306	int i;
307	struct pfioc_rule pr;
308	time_t current_time;
309	unsigned int ts;
310	int uid;
311	unsigned int min_ts = UINT_MAX;
312	int min_uid = INT_MAX, max_uid = -1;
313	int n = 0;
314
315	if(dev<0) {
316		syslog(LOG_ERR, "pf device is not open");
317		return -1;
318	}
319	current_time = time(NULL);
320	memset(&pr, 0, sizeof(pr));
321	strlcpy(pr.anchor, anchor_name, MAXPATHLEN);
322#ifndef PF_NEWSTYLE
323	pr.rule.action = PF_PASS;
324#endif
325	if(ioctl(dev, DIOCGETRULES, &pr) < 0) {
326		syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m");
327		return -1;
328	}
329	for(i = pr.nr - 1; i >= 0; i--) {
330		pr.nr = i;
331		if(ioctl(dev, DIOCGETRULE, &pr) < 0) {
332			syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m");
333			return -1;
334		}
335		if(sscanf(pr.rule.label, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) {
336			syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", pr.rule.label);
337			continue;
338		}
339		if(ts <= (unsigned int)current_time) {
340			syslog(LOG_INFO, "removing expired pinhole '%s'", pr.rule.label);
341			pr.action = PF_CHANGE_GET_TICKET;
342			if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) {
343				syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m");
344				return -1;
345			}
346			pr.action = PF_CHANGE_REMOVE;
347			pr.nr = i;
348			if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) {
349				syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_REMOVE: %m");
350				return -1;
351			}
352			n++;
353#ifndef PF_NEWSTYLE
354			pr.rule.action = PF_PASS;
355#endif
356			if(ioctl(dev, DIOCGETRULES, &pr) < 0) {
357				syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m");
358				return -1;
359			}
360		} else {
361			if(uid > max_uid)
362				max_uid = uid;
363			else if(uid < min_uid)
364				min_uid = uid;
365			if(ts < min_ts)
366				min_ts = ts;
367		}
368	}
369	if(next_timestamp && (min_ts != UINT_MAX))
370		*next_timestamp = min_ts;
371	if(max_uid > 0) {
372		if(((min_uid - 32000) <= next_uid) && (next_uid <= max_uid)) {
373			next_uid = max_uid + 1;
374		}
375		if(next_uid >= 65535) {
376			next_uid = 1;
377		}
378	}
379	return n;	/* number of rules removed */
380}
381
382#endif /* ENABLE_UPNPPINHOLE */
383