1/* $Id: ipfwrdr.c,v 1.12 2012/02/11 13:10:57 nanard Exp $ */
2/*
3 * MiniUPnP project
4 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
5 * (c) 2009 Jardel Weyrich
6 * (c) 2011-2012 Thomas Bernard
7 * This software is subject to the conditions detailed
8 * in the LICENCE file provided within the distribution
9 */
10
11#include "../config.h"
12
13#include <sys/param.h>
14#include <sys/types.h>
15#include <sys/file.h>
16
17/*
18This is a workaround for <sys/uio.h> troubles on FreeBSD, HPUX, OpenBSD.
19Needed here because on some systems <sys/uio.h> gets included by things
20like <sys/socket.h>
21*/
22#ifndef _KERNEL
23#  define ADD_KERNEL
24#  define _KERNEL
25#  define KERNEL
26#endif
27#ifdef __OpenBSD__
28struct file;
29#endif
30#include <sys/uio.h>
31#ifdef ADD_KERNEL
32#  undef _KERNEL
33#  undef KERNEL
34#endif
35
36#include <sys/time.h>
37#include <sys/socket.h>
38#include <sys/syslog.h>
39#include <sys/ioctl.h>
40#include <net/if.h>
41#if __FreeBSD_version >= 300000
42#  include <net/if_var.h>
43#endif
44#include <netinet/in.h>
45#include <netinet/in_systm.h>
46#include <netinet/ip.h>
47#include <netinet/ip_icmp.h>
48#include <netinet/tcp.h>
49#include <netinet/udp.h>
50#include <arpa/inet.h>
51
52#include <sys/types.h>
53#include <sys/queue.h>
54#include <sys/socket.h>
55#include <errno.h>
56#include <limits.h>
57#include <netdb.h>
58#include <stdlib.h>
59#include <fcntl.h>
60#include <syslog.h>
61#include <stddef.h>
62#include <stdio.h>
63#include <strings.h>
64#include <string.h>
65#include <unistd.h>
66#include <netinet/ip_fw.h>
67#include "ipfwaux.h"
68#include "ipfwrdr.h"
69
70#include "../upnpglobalvars.h"
71
72/* init and shutdown functions */
73
74int init_redirect(void) {
75	return ipfw_exec(IP_FW_INIT, NULL, 0);
76}
77
78void shutdown_redirect(void) {
79	ipfw_exec(IP_FW_TERM, NULL, 0);
80}
81
82/* ipfw cannot store descriptions and timestamp for port mappings so we keep
83 * our own list in memory */
84struct mapping_desc_time {
85	struct mapping_desc_time * next;
86	unsigned int timestamp;
87	unsigned short eport;
88	short proto;
89	char desc[];
90};
91
92static struct mapping_desc_time * mappings_list = NULL;
93
94/* add an element to the port mappings descriptions & timestamp list */
95static void
96add_desc_time(unsigned short eport, int proto,
97              const char * desc, unsigned int timestamp)
98{
99	struct mapping_desc_time * tmp;
100	size_t l;
101	if(!desc)
102		desc = "miniupnpd";
103	l = strlen(desc) + 1;
104	tmp = malloc(sizeof(struct mapping_desc_time) + l);
105	if(tmp) {
106		/* fill the element and insert it as head of the list */
107		tmp->next = mappings_list;
108		tmp->timestamp = timestamp;
109		tmp->eport = eport;
110		tmp->proto = (short)proto;
111		memcpy(tmp->desc, desc, l);
112		mappings_list = tmp;
113	}
114}
115
116/* remove an element to the port mappings descriptions & timestamp list */
117static void
118del_desc_time(unsigned short eport, int proto)
119{
120	struct mapping_desc_time * e;
121	struct mapping_desc_time * * p;
122	p = &mappings_list;
123	e = *p;
124	while(e) {
125		if(e->eport == eport && e->proto == (short)proto) {
126			*p = e->next;
127			free(e);
128			return;
129		} else {
130			p = &e->next;
131			e = *p;
132		}
133	}
134}
135
136/* go through the list and find the description and timestamp */
137static void
138get_desc_time(unsigned short eport, int proto,
139              char * desc, int desclen,
140              unsigned int * timestamp)
141{
142	struct mapping_desc_time * e;
143
144	for(e = mappings_list; e; e = e->next) {
145		if(e->eport == eport && e->proto == (short)proto) {
146			if(desc)
147				strlcpy(desc, e->desc, desclen);
148			if(timestamp)
149				*timestamp = e->timestamp;
150			return;
151		}
152	}
153}
154
155/* --- */
156int add_redirect_rule2(
157	const char * ifname,
158	const char * rhost,
159	unsigned short eport,
160	const char * iaddr,
161	unsigned short iport,
162	int proto,
163	const char * desc,
164	unsigned int timestamp)
165{
166	struct ip_fw rule;
167	int r;
168
169	if (ipfw_validate_protocol(proto) < 0)
170		return -1;
171	if (ipfw_validate_ifname(ifname) < 0)
172		return -1;
173
174	memset(&rule, 0, sizeof(struct ip_fw));
175	rule.version = IP_FW_CURRENT_API_VERSION;
176#if 0
177	rule.fw_number = 1000; /* rule number */
178	rule.context = (void *)desc; /* The description is kept in a separate list */
179#endif
180	rule.fw_prot = proto; /* protocol */
181	rule.fw_flg |= IP_FW_F_IIFACE; /* interfaces to check */
182	rule.fw_flg |= IP_FW_F_IIFNAME; /* interfaces to check by name */
183	rule.fw_flg |= (IP_FW_F_IN | IP_FW_F_OUT); /* packet direction */
184	rule.fw_flg |= IP_FW_F_FWD; /* forward action */
185#ifdef USE_IFNAME_IN_RULES
186	if (ifname != NULL) {
187		strlcpy(rule.fw_in_if.fu_via_if.name, ifname, IFNAMSIZ); /* src interface */
188		rule.fw_in_if.fu_via_if.unit = -1;
189	}
190#endif
191	if (inet_aton(iaddr, &rule.fw_out_if.fu_via_ip) == 0) {
192		syslog(LOG_ERR, "inet_aton(): %m");
193		return -1;
194	}
195	memcpy(&rule.fw_dst,  &rule.fw_out_if.fu_via_ip, sizeof(struct in_addr));
196	memcpy(&rule.fw_fwd_ip.sin_addr, &rule.fw_out_if.fu_via_ip, sizeof(struct in_addr));
197	rule.fw_dmsk.s_addr = INADDR_BROADCAST;	/* TODO check this */
198	IP_FW_SETNDSTP(&rule, 1); /* number of external ports */
199	rule.fw_uar.fw_pts[0] = eport; /* external port */
200	rule.fw_fwd_ip.sin_port = iport; /* internal port */
201	if (rhost && rhost[0] != '\0') {
202		inet_aton(rhost, &rule.fw_src);
203		rule.fw_smsk.s_addr = htonl(INADDR_NONE);
204	}
205
206	r = ipfw_exec(IP_FW_ADD, &rule, sizeof(rule));
207	if(r >= 0)
208		add_desc_time(eport, proto, desc, timestamp);
209	return r;
210}
211
212/* get_redirect_rule()
213 * return value : 0 success (found)
214 * -1 = error or rule not found */
215int get_redirect_rule(
216	const char * ifname,
217	unsigned short eport,
218	int proto,
219	char * iaddr,
220	int iaddrlen,
221	unsigned short * iport,
222	char * desc,
223	int desclen,
224	char * rhost,
225	int rhostlen,
226	unsigned int * timestamp,
227	u_int64_t * packets,
228	u_int64_t * bytes)
229{
230	int i, count_rules, total_rules = 0;
231	struct ip_fw * rules = NULL;
232
233	if (ipfw_validate_protocol(proto) < 0)
234		return -1;
235	if (ipfw_validate_ifname(ifname) < 0)
236		return -1;
237	if (timestamp)
238		*timestamp = 0;
239
240	do {
241		count_rules = ipfw_fetch_ruleset(&rules, &total_rules, 10);
242		if (count_rules < 0)
243			goto error;
244	} while (count_rules == 10);
245
246	for (i=0; i<total_rules-1; i++) {
247		const struct ip_fw const * ptr = &rules[i];
248		if (proto == ptr->fw_prot && eport == ptr->fw_uar.fw_pts[0]) {
249			if (packets != NULL)
250				*packets = ptr->fw_pcnt;
251			if (bytes != NULL)
252				*bytes = ptr->fw_bcnt;
253			if (iport != NULL)
254				*iport = ptr->fw_fwd_ip.sin_port;
255			if (iaddr != NULL && iaddrlen > 0) {
256				/* looks like fw_out_if.fu_via_ip is zero */
257				/*if (inet_ntop(AF_INET, &ptr->fw_out_if.fu_via_ip, iaddr, iaddrlen) == NULL) {*/
258				if (inet_ntop(AF_INET, &ptr->fw_fwd_ip.sin_addr, iaddr, iaddrlen) == NULL) {
259					syslog(LOG_ERR, "inet_ntop(): %m");
260					goto error;
261				}
262			}
263			if (rhost != NULL && rhostlen > 0) {
264				if (ptr->fw_src.s_addr == 0)
265					rhost[0] = '\0';
266				else if (inet_ntop(AF_INET, &ptr->fw_src.s_addr, rhost, rhostlen) == NULL) {
267					syslog(LOG_ERR, "inet_ntop(): %m");
268					goto error;
269				}
270			}
271			/* And what if we found more than 1 matching rule? */
272			ipfw_free_ruleset(&rules);
273			get_desc_time(eport, proto, desc, desclen, timestamp);
274			return 0;
275		}
276	}
277
278error:
279	if (rules != NULL)
280		ipfw_free_ruleset(&rules);
281	return -1;
282}
283
284int delete_redirect_rule(
285	const char * ifname,
286	unsigned short eport,
287	int proto)
288{
289	int i, count_rules, total_rules = 0;
290	struct ip_fw * rules = NULL;
291
292	if (ipfw_validate_protocol(proto) < 0)
293		return -1;
294	if (ipfw_validate_ifname(ifname) < 0)
295		return -1;
296
297	do {
298		count_rules = ipfw_fetch_ruleset(&rules, &total_rules, 10);
299		if (count_rules < 0)
300			goto error;
301	} while (count_rules == 10);
302
303	for (i=0; i<total_rules-1; i++) {
304		const struct ip_fw const * ptr = &rules[i];
305		if (proto == ptr->fw_prot && eport == ptr->fw_uar.fw_pts[0]) {
306			if (ipfw_exec(IP_FW_DEL, (struct ip_fw *)ptr, sizeof(*ptr)) < 0)
307				goto error;
308			/* And what if we found more than 1 matching rule? */
309			ipfw_free_ruleset(&rules);
310			del_desc_time(eport, proto);
311			return 0;
312		}
313	}
314
315error:
316	if (rules != NULL)
317		ipfw_free_ruleset(&rules);
318	return -1;
319}
320
321int add_filter_rule2(
322	const char * ifname,
323	const char * rhost,
324	const char * iaddr,
325	unsigned short eport,
326	unsigned short iport,
327	int proto,
328	const char * desc)
329{
330	return 0; /* nothing to do, always success */
331}
332
333int delete_filter_rule(
334	const char * ifname,
335	unsigned short eport,
336	int proto)
337{
338	return 0; /* nothing to do, always success */
339}
340
341int get_redirect_rule_by_index(
342	int index,
343	char * ifname,
344	unsigned short * eport,
345	char * iaddr,
346	int iaddrlen,
347	unsigned short * iport,
348	int * proto,
349	char * desc,
350	int desclen,
351	char * rhost,
352	int rhostlen,
353	unsigned int * timestamp,
354	u_int64_t * packets,
355	u_int64_t * bytes)
356{
357	int total_rules = 0;
358	struct ip_fw * rules = NULL;
359
360	if (index < 0) /* TODO shouldn't we also validate the maximum? */
361		return -1;
362
363	if(timestamp)
364		*timestamp = 0;
365
366	ipfw_fetch_ruleset(&rules, &total_rules, index + 1);
367
368	if (total_rules > index) {
369		const struct ip_fw const * ptr = &rules[index];
370		if (ptr->fw_prot == 0)	/* invalid rule */
371			goto error;
372		if (proto != NULL)
373			*proto = ptr->fw_prot;
374		if (eport != NULL)
375			*eport = ptr->fw_uar.fw_pts[0];
376		if (iport != NULL)
377			*iport = ptr->fw_fwd_ip.sin_port;
378		if (ifname != NULL)
379			strlcpy(ifname, ptr->fw_in_if.fu_via_if.name, IFNAMSIZ);
380		if (packets != NULL)
381			*packets = ptr->fw_pcnt;
382		if (bytes != NULL)
383			*bytes = ptr->fw_bcnt;
384		if (iport != NULL)
385			*iport = ptr->fw_fwd_ip.sin_port;
386		if (iaddr != NULL && iaddrlen > 0) {
387			/* looks like fw_out_if.fu_via_ip is zero */
388			/*if (inet_ntop(AF_INET, &ptr->fw_out_if.fu_via_ip, iaddr, iaddrlen) == NULL) {*/
389			if (inet_ntop(AF_INET, &ptr->fw_fwd_ip.sin_addr, iaddr, iaddrlen) == NULL) {
390				syslog(LOG_ERR, "inet_ntop(): %m");
391				goto error;
392			}
393		}
394		if (rhost != NULL && rhostlen > 0) {
395			if (ptr->fw_src.s_addr == 0)
396				rhost[0] = '\0';
397			else if (inet_ntop(AF_INET, &ptr->fw_src.s_addr, rhost, rhostlen) == NULL) {
398				syslog(LOG_ERR, "inet_ntop(): %m");
399				goto error;
400			}
401		}
402		ipfw_free_ruleset(&rules);
403		get_desc_time(*eport, *proto, desc, desclen, timestamp);
404		return 0;
405	}
406
407error:
408	if (rules != NULL)
409		ipfw_free_ruleset(&rules);
410	return -1;
411}
412
413/* upnp_get_portmappings_in_range()
414 * return a list of all "external" ports for which a port
415 * mapping exists */
416unsigned short *
417get_portmappings_in_range(unsigned short startport,
418                          unsigned short endport,
419                          int proto,
420                          unsigned int * number)
421{
422	unsigned short * array = NULL;
423	unsigned int capacity = 128;
424	int i, count_rules, total_rules = 0;
425	struct ip_fw * rules = NULL;
426
427	if (ipfw_validate_protocol(proto) < 0)
428		return NULL;
429
430	do {
431		count_rules = ipfw_fetch_ruleset(&rules, &total_rules, 10);
432		if (count_rules < 0)
433			goto error;
434	} while (count_rules == 10);
435
436	array = calloc(capacity, sizeof(unsigned short));
437	if(!array) {
438		syslog(LOG_ERR, "get_portmappings_in_range() : calloc error");
439                goto error;
440	}
441	*number = 0;
442
443	for (i=0; i<total_rules-1; i++) {
444		const struct ip_fw const * ptr = &rules[i];
445		unsigned short eport = ptr->fw_uar.fw_pts[0];
446		if (proto == ptr->fw_prot
447		    && startport <= eport
448		    && eport <= endport) {
449			if(*number >= capacity) {
450				capacity += 128;
451				array = realloc(array, sizeof(unsigned short)*capacity);
452				if(!array) {
453					syslog(LOG_ERR, "get_portmappings_in_range() : realloc(%lu) error", sizeof(unsigned short)*capacity);
454					*number = 0;
455					goto error;
456				}
457			}
458			array[*number] = eport;
459			(*number)++;
460		}
461	}
462error:
463	if (rules != NULL)
464		ipfw_free_ruleset(&rules);
465	return array;
466}
467
468