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