1/* $Id: iptpinhole.c,v 1.14 2015/02/10 15:01:03 nanard Exp $ */
2/* MiniUPnP project
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2012-2015 Thomas Bernard
5 * This software is subject to the conditions detailed
6 * in the LICENCE file provided within the distribution */
7
8#include <stdlib.h>
9#include <string.h>
10#include <syslog.h>
11#include <errno.h>
12#include <arpa/inet.h>
13#include <sys/queue.h>
14
15#include "../config.h"
16#include "iptpinhole.h"
17#include "../upnpglobalvars.h"
18
19#ifdef ENABLE_UPNPPINHOLE
20
21#include <iptables.h>
22#include <libiptc/libip6tc.h>
23#include "tiny_nf_nat.h"
24
25#define IP6TC_HANDLE struct ip6tc_handle *
26
27static int next_uid = 1;
28
29static LIST_HEAD(pinhole_list_t, pinhole_t) pinhole_list;
30
31static struct pinhole_t *
32get_pinhole(unsigned short uid);
33
34struct pinhole_t {
35	struct in6_addr saddr;
36	struct in6_addr daddr;
37	LIST_ENTRY(pinhole_t) entries;
38	unsigned int timestamp;
39	unsigned short sport;
40	unsigned short dport;
41	unsigned short uid;
42	unsigned char proto;
43	char desc[];
44};
45
46void init_iptpinhole(void)
47{
48	LIST_INIT(&pinhole_list);
49}
50
51void shutdown_iptpinhole(void)
52{
53	/* TODO empty list */
54}
55
56/* return uid */
57static int
58add_to_pinhole_list(struct in6_addr * saddr, unsigned short sport,
59                    struct in6_addr * daddr, unsigned short dport,
60                    int proto, const char *desc, unsigned int timestamp)
61{
62	struct pinhole_t * p;
63
64	p = calloc(1, sizeof(struct pinhole_t) + strlen(desc) + 1);
65	if(!p) {
66		syslog(LOG_ERR, "add_to_pinhole_list calloc() error");
67		return -1;
68	}
69	strcpy(p->desc, desc);
70	memcpy(&p->saddr, saddr, sizeof(struct in6_addr));
71	p->sport = sport;
72	memcpy(&p->daddr, daddr, sizeof(struct in6_addr));
73	p->dport = dport;
74	p->timestamp = timestamp;
75	p->proto = (unsigned char)proto;
76	LIST_INSERT_HEAD(&pinhole_list, p, entries);
77	while(get_pinhole(next_uid) != NULL) {
78		next_uid++;
79		if(next_uid > 65535)
80			next_uid = 1;
81	}
82	p->uid = next_uid;
83	next_uid++;
84	if(next_uid > 65535)
85		next_uid = 1;
86	return p->uid;
87}
88
89static struct pinhole_t *
90get_pinhole(unsigned short uid)
91{
92	struct pinhole_t * p;
93
94	for(p = pinhole_list.lh_first; p != NULL; p = p->entries.le_next) {
95		if(p->uid == uid)
96			return p;
97	}
98	return NULL;	/* not found */
99}
100
101/* new_match()
102 * Allocate and set a new ip6t_entry_match structure
103 * The caller must free() it after usage */
104static struct ip6t_entry_match *
105new_match(int proto, unsigned short sport, unsigned short dport)
106{
107	struct ip6t_entry_match *match;
108	struct ip6t_tcp *info;	/* TODO : use ip6t_udp if needed */
109	size_t size;
110	const char * name;
111	size =   XT_ALIGN(sizeof(struct ip6t_entry_match))
112	       + XT_ALIGN(sizeof(struct ip6t_tcp));
113	match = calloc(1, size);
114	match->u.user.match_size = size;
115	switch(proto) {
116	case IPPROTO_TCP:
117		name = "tcp";
118		break;
119	case IPPROTO_UDP:
120		name = "udp";
121		break;
122	case IPPROTO_UDPLITE:
123		name = "udplite";
124		break;
125	default:
126		name = NULL;
127	}
128	if(name)
129		strncpy(match->u.user.name, name, sizeof(match->u.user.name));
130	else
131		syslog(LOG_WARNING, "no name for protocol %d", proto);
132	info = (struct ip6t_tcp *)match->data;
133	if(sport) {
134		info->spts[0] = sport;	/* specified source port */
135		info->spts[1] = sport;
136	} else {
137		info->spts[0] = 0;		/* all source ports */
138		info->spts[1] = 0xFFFF;
139	}
140	info->dpts[0] = dport;	/* specified destination port */
141	info->dpts[1] = dport;
142	return match;
143}
144
145static struct ip6t_entry_target *
146get_accept_target(void)
147{
148	struct ip6t_entry_target * target = NULL;
149	size_t size;
150	size =   XT_ALIGN(sizeof(struct ip6t_entry_target))
151	       + XT_ALIGN(sizeof(int));
152	target = calloc(1, size);
153	target->u.user.target_size = size;
154	strncpy(target->u.user.name, "ACCEPT", sizeof(target->u.user.name));
155	return target;
156}
157
158static int
159ip6tc_init_verify_append(const char * table,
160                         const char * chain,
161                         struct ip6t_entry * e)
162{
163	IP6TC_HANDLE h;
164
165	h = ip6tc_init(table);
166	if(!h) {
167		syslog(LOG_ERR, "ip6tc_init error : %s", ip6tc_strerror(errno));
168		return -1;
169	}
170	if(!ip6tc_is_chain(chain, h)) {
171		syslog(LOG_ERR, "chain %s not found", chain);
172		goto error;
173	}
174	if(!ip6tc_append_entry(chain, e, h)) {
175		syslog(LOG_ERR, "ip6tc_append_entry() error : %s", ip6tc_strerror(errno));
176		goto error;
177	}
178	if(!ip6tc_commit(h)) {
179		syslog(LOG_ERR, "ip6tc_commit() error : %s", ip6tc_strerror(errno));
180		goto error;
181	}
182	return 0;	/* ok */
183error:
184	ip6tc_free(h);
185	return -1;
186}
187
188/*
189ip6tables -I %s %d -p %s -i %s -s %s --sport %hu -d %s --dport %hu -j ACCEPT
190ip6tables -I %s %d -p %s -i %s --sport %hu -d %s --dport %hu -j ACCEPT
191
192miniupnpd_forward_chain, line_number, proto, ext_if_name, raddr, rport, iaddr, iport
193
194ip6tables -t raw -I PREROUTING %d -p %s -i %s -s %s --sport %hu -d %s --dport %hu -j TRACE
195ip6tables -t raw -I PREROUTING %d -p %s -i %s --sport %hu -d %s --dport %hu -j TRACE
196*/
197int add_pinhole(const char * ifname,
198                const char * rem_host, unsigned short rem_port,
199                const char * int_client, unsigned short int_port,
200                int proto, const char * desc, unsigned int timestamp)
201{
202	int uid;
203	struct ip6t_entry * e;
204	struct ip6t_entry * tmp;
205	struct ip6t_entry_match *match = NULL;
206	struct ip6t_entry_target *target = NULL;
207
208	e = calloc(1, sizeof(struct ip6t_entry));
209	if(!e) {
210		syslog(LOG_ERR, "%s: calloc(%d) failed",
211		       "add_pinhole", (int)sizeof(struct ip6t_entry));
212		return -1;
213	}
214	e->ipv6.proto = proto;
215	if (proto)
216		e->ipv6.flags |= IP6T_F_PROTO;
217
218	if(ifname)
219		strncpy(e->ipv6.iniface, ifname, IFNAMSIZ);
220	if(rem_host && (rem_host[0] != '\0')) {
221		inet_pton(AF_INET6, rem_host, &e->ipv6.src);
222		memset(&e->ipv6.smsk, 0xff, sizeof(e->ipv6.smsk));
223	}
224	inet_pton(AF_INET6, int_client, &e->ipv6.dst);
225	memset(&e->ipv6.dmsk, 0xff, sizeof(e->ipv6.dmsk));
226
227	/*e->nfcache = NFC_IP_DST_PT;*/
228	/*e->nfcache |= NFC_UNKNOWN;*/
229
230	match = new_match(proto, rem_port, int_port);
231	target = get_accept_target();
232	tmp = realloc(e, sizeof(struct ip6t_entry)
233	               + match->u.match_size
234	               + target->u.target_size);
235	if(!tmp) {
236		syslog(LOG_ERR, "%s: realloc(%d) failed",
237		       "add_pinhole", (int)(sizeof(struct ip6t_entry) + match->u.match_size + target->u.target_size));
238		free(e);
239		free(match);
240		free(target);
241		return -1;
242	}
243	e = tmp;
244	memcpy(e->elems, match, match->u.match_size);
245	memcpy(e->elems + match->u.match_size, target, target->u.target_size);
246	e->target_offset = sizeof(struct ip6t_entry)
247	                   + match->u.match_size;
248	e->next_offset = sizeof(struct ip6t_entry)
249	                 + match->u.match_size
250					 + target->u.target_size;
251	free(match);
252	free(target);
253
254	if(ip6tc_init_verify_append("filter", miniupnpd_v6_filter_chain, e) < 0) {
255		free(e);
256		return -1;
257	}
258	uid = add_to_pinhole_list(&e->ipv6.src, rem_port,
259	                          &e->ipv6.dst, int_port,
260	                          proto, desc, timestamp);
261	free(e);
262	return uid;
263}
264
265int
266delete_pinhole(unsigned short uid)
267{
268	struct pinhole_t * p;
269	IP6TC_HANDLE h;
270	const struct ip6t_entry * e;
271	const struct ip6t_entry_match *match = NULL;
272	/*const struct ip6t_entry_target *target = NULL;*/
273	unsigned int index;
274
275	p = get_pinhole(uid);
276	if(!p)
277		return -2;	/* not found */
278
279	h = ip6tc_init("filter");
280	if(!h) {
281		syslog(LOG_ERR, "ip6tc_init error : %s", ip6tc_strerror(errno));
282		return -1;
283	}
284	if(!ip6tc_is_chain(miniupnpd_v6_filter_chain, h)) {
285		syslog(LOG_ERR, "chain %s not found", miniupnpd_v6_filter_chain);
286		goto error;
287	}
288	index = 0;
289	for(e = ip6tc_first_rule(miniupnpd_v6_filter_chain, h);
290	    e;
291	    e = ip6tc_next_rule(e, h)) {
292		if((e->ipv6.proto == p->proto) &&
293		   (0 == memcmp(&e->ipv6.src, &p->saddr, sizeof(e->ipv6.src))) &&
294		   (0 == memcmp(&e->ipv6.dst, &p->daddr, sizeof(e->ipv6.dst)))) {
295			const struct ip6t_tcp * info;
296			match = (const struct ip6t_entry_match *)&e->elems;
297			info = (const struct ip6t_tcp *)&match->data;
298			if((info->spts[0] == p->sport) && (info->dpts[0] == p->dport)) {
299				if(!ip6tc_delete_num_entry(miniupnpd_v6_filter_chain, index, h)) {
300					syslog(LOG_ERR, "ip6tc_delete_num_entry(%s,%d,...): %s",
301					       miniupnpd_v6_filter_chain, index, ip6tc_strerror(errno));
302					goto error;
303				}
304				if(!ip6tc_commit(h)) {
305					syslog(LOG_ERR, "ip6tc_commit(): %s",
306					       ip6tc_strerror(errno));
307					goto error;
308				}
309				ip6tc_free(h);
310				LIST_REMOVE(p, entries);
311				return 0;	/* ok */
312			}
313		}
314		index++;
315	}
316	ip6tc_free(h);
317	syslog(LOG_WARNING, "delete_pinhole() rule with PID=%hu not found", uid);
318	return -2;	/* not found */
319error:
320	ip6tc_free(h);
321	return -1;
322}
323
324int
325update_pinhole(unsigned short uid, unsigned int timestamp)
326{
327	struct pinhole_t * p;
328
329	p = get_pinhole(uid);
330	if(p) {
331		p->timestamp = timestamp;
332		return 0;
333	} else {
334		return -2;	/* Not found */
335	}
336}
337
338int
339get_pinhole_info(unsigned short uid,
340                 char * rem_host, int rem_hostlen,
341                 unsigned short * rem_port,
342                 char * int_client, int int_clientlen,
343                 unsigned short * int_port,
344                 int * proto, char * desc, int desclen,
345                 unsigned int * timestamp,
346                 u_int64_t * packets, u_int64_t * bytes)
347{
348	struct pinhole_t * p;
349
350	p = get_pinhole(uid);
351	if(!p)
352		return -2;	/* Not found */
353	if(rem_host && (rem_host[0] != '\0')) {
354		if(inet_ntop(AF_INET6, &p->saddr, rem_host, rem_hostlen) == NULL)
355			return -1;
356	}
357	if(rem_port)
358		*rem_port = p->sport;
359	if(int_client) {
360		if(inet_ntop(AF_INET6, &p->daddr, int_client, int_clientlen) == NULL)
361			return -1;
362	}
363	if(int_port)
364		*int_port = p->dport;
365	if(proto)
366		*proto = p->proto;
367	if(timestamp)
368		*timestamp = p->timestamp;
369	if (desc)
370		strncpy(desc, p->desc, desclen);
371	if(packets || bytes) {
372		/* theses informations need to be read from netfilter */
373		IP6TC_HANDLE h;
374		const struct ip6t_entry * e;
375		const struct ip6t_entry_match * match;
376		h = ip6tc_init("filter");
377		if(!h) {
378			syslog(LOG_ERR, "ip6tc_init error : %s", ip6tc_strerror(errno));
379			return -1;
380		}
381		for(e = ip6tc_first_rule(miniupnpd_v6_filter_chain, h);
382		    e;
383		    e = ip6tc_next_rule(e, h)) {
384			if((e->ipv6.proto == p->proto) &&
385			   (0 == memcmp(&e->ipv6.src, &p->saddr, sizeof(e->ipv6.src))) &&
386			   (0 == memcmp(&e->ipv6.dst, &p->daddr, sizeof(e->ipv6.dst)))) {
387				const struct ip6t_tcp * info;
388				match = (const struct ip6t_entry_match *)&e->elems;
389				info = (const struct ip6t_tcp *)&match->data;
390				if((info->spts[0] == p->sport) && (info->dpts[0] == p->dport)) {
391					if(packets)
392						*packets = e->counters.pcnt;
393					if(bytes)
394						*bytes = e->counters.bcnt;
395					break;
396				}
397			}
398		}
399		ip6tc_free(h);
400	}
401	return 0;
402}
403
404int get_pinhole_uid_by_index(int index)
405{
406	struct pinhole_t * p;
407
408	for(p = pinhole_list.lh_first; p != NULL; p = p->entries.le_next)
409		if (!index--)
410			return p->uid;
411	return -1;
412}
413
414int
415clean_pinhole_list(unsigned int * next_timestamp)
416{
417	unsigned int min_ts = UINT_MAX;
418	struct pinhole_t * p;
419	time_t current_time;
420	int n = 0;
421
422	current_time = time(NULL);
423	p = pinhole_list.lh_first;
424	while(p != NULL) {
425		if(p->timestamp <= (unsigned int)current_time) {
426			unsigned short uid = p->uid;
427			syslog(LOG_INFO, "removing expired pinhole with uid=%hu", uid);
428			p = p->entries.le_next;
429			if(delete_pinhole(uid) == 0)
430				n++;
431			else
432				break;
433		} else {
434			if(p->timestamp < min_ts)
435				min_ts = p->timestamp;
436			p = p->entries.le_next;
437		}
438	}
439	if(next_timestamp && (min_ts != UINT_MAX))
440		*next_timestamp = min_ts;
441	return n;
442}
443
444#endif /* ENABLE_UPNPPINHOLE */
445