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