1/* $Id: upnpredirect.c,v 1.85 2014/12/09 09:17:54 nanard Exp $ */
2/* MiniUPnP project
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2006-2014 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 <sys/types.h>
12#include <sys/socket.h>
13#include <netinet/in.h>
14#include <net/if.h>
15#include <arpa/inet.h>
16
17#include <stdio.h>
18#include <ctype.h>
19#include <unistd.h>
20
21#include "macros.h"
22#include "config.h"
23#include "upnpredirect.h"
24#include "upnpglobalvars.h"
25#include "upnpevents.h"
26#include "portinuse.h"
27#if defined(USE_NETFILTER)
28#include "netfilter/iptcrdr.h"
29#endif
30#if defined(USE_PF)
31#include "pf/obsdrdr.h"
32#endif
33#if defined(USE_IPF)
34#include "ipf/ipfrdr.h"
35#endif
36#if defined(USE_IPFW)
37#include "ipfw/ipfwrdr.h"
38#endif
39#ifdef USE_MINIUPNPDCTL
40#include <stdio.h>
41#include <unistd.h>
42#endif
43#ifdef ENABLE_LEASEFILE
44#include <sys/stat.h>
45#endif
46
47/* from <inttypes.h> */
48#ifndef PRIu64
49#define PRIu64 "llu"
50#endif
51
52/* proto_atoi()
53 * convert the string "UDP" or "TCP" to IPPROTO_UDP and IPPROTO_UDP */
54static int
55proto_atoi(const char * protocol)
56{
57	int proto = IPPROTO_TCP;
58	if(strcmp(protocol, "UDP") == 0)
59		proto = IPPROTO_UDP;
60	return proto;
61}
62
63#ifdef ENABLE_LEASEFILE
64static int
65lease_file_add(unsigned short eport,
66               const char * iaddr,
67               unsigned short iport,
68               int proto,
69               const char * desc,
70               unsigned int timestamp)
71{
72	FILE * fd;
73
74	if (lease_file == NULL) return 0;
75
76	fd = fopen( lease_file, "a");
77	if (fd==NULL) {
78		syslog(LOG_ERR, "could not open lease file: %s", lease_file);
79		return -1;
80	}
81
82	fprintf(fd, "%s:%hu:%s:%hu:%u:%s\n",
83	        ((proto==IPPROTO_TCP)?"TCP":"UDP"), eport, iaddr, iport,
84	        timestamp, desc);
85	fclose(fd);
86
87	return 0;
88}
89
90static int
91lease_file_remove(unsigned short eport, int proto)
92{
93	FILE* fd, *fdt;
94	int tmp;
95	char buf[512];
96	char str[32];
97	char tmpfilename[128];
98	int str_size, buf_size;
99
100
101	if (lease_file == NULL) return 0;
102
103	if (strlen( lease_file) + 7 > sizeof(tmpfilename)) {
104		syslog(LOG_ERR, "Lease filename is too long");
105		return -1;
106	}
107
108	strncpy( tmpfilename, lease_file, sizeof(tmpfilename) );
109	strncat( tmpfilename, "XXXXXX", sizeof(tmpfilename) - strlen(tmpfilename));
110
111	fd = fopen( lease_file, "r");
112	if (fd==NULL) {
113		return 0;
114	}
115
116	snprintf( str, sizeof(str), "%s:%u", ((proto==IPPROTO_TCP)?"TCP":"UDP"), eport);
117	str_size = strlen(str);
118
119	tmp = mkstemp(tmpfilename);
120	if (tmp==-1) {
121		fclose(fd);
122		syslog(LOG_ERR, "could not open temporary lease file");
123		return -1;
124	}
125	fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
126	fdt = fdopen(tmp, "a");
127
128	buf[sizeof(buf)-1] = 0;
129	while( fgets(buf, sizeof(buf)-1, fd) != NULL) {
130		buf_size = strlen(buf);
131
132		if (buf_size < str_size || strncmp(str, buf, str_size)!=0) {
133			fwrite(buf, buf_size, 1, fdt);
134		}
135	}
136	fclose(fdt);
137	fclose(fd);
138
139	if (rename(tmpfilename, lease_file) < 0) {
140		syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file);
141		remove(tmpfilename);
142	}
143
144	return 0;
145
146}
147
148/* reload_from_lease_file()
149 * read lease_file and add the rules contained
150 */
151int reload_from_lease_file()
152{
153	FILE * fd;
154	char * p;
155	unsigned short eport, iport;
156	char * proto;
157	char * iaddr;
158	char * desc;
159	char * rhost;
160	unsigned int leaseduration;
161	unsigned int timestamp;
162	time_t current_time;
163	char line[128];
164	int r;
165
166	if(!lease_file) return -1;
167	fd = fopen( lease_file, "r");
168	if (fd==NULL) {
169		syslog(LOG_DEBUG, "could not open lease file: %s", lease_file);
170		return -1;
171	}
172	if(unlink(lease_file) < 0) {
173		syslog(LOG_WARNING, "could not unlink file %s : %m", lease_file);
174	}
175
176	current_time = time(NULL);
177	while(fgets(line, sizeof(line), fd)) {
178		syslog(LOG_DEBUG, "parsing lease file line '%s'", line);
179		proto = line;
180		p = strchr(line, ':');
181		if(!p) {
182			syslog(LOG_ERR, "unrecognized data in lease file");
183			continue;
184		}
185		*(p++) = '\0';
186		iaddr = strchr(p, ':');
187		if(!iaddr) {
188			syslog(LOG_ERR, "unrecognized data in lease file");
189			continue;
190		}
191		*(iaddr++) = '\0';
192		eport = (unsigned short)atoi(p);
193		p = strchr(iaddr, ':');
194		if(!p) {
195			syslog(LOG_ERR, "unrecognized data in lease file");
196			continue;
197		}
198		*(p++) = '\0';
199		iport = (unsigned short)atoi(p);
200		p = strchr(p, ':');
201		if(!p) {
202			syslog(LOG_ERR, "unrecognized data in lease file");
203			continue;
204		}
205		*(p++) = '\0';
206		desc = strchr(p, ':');
207		if(!desc) {
208			syslog(LOG_ERR, "unrecognized data in lease file");
209			continue;
210		}
211		*(desc++) = '\0';
212		/*timestamp = (unsigned int)atoi(p);*/
213		timestamp = (unsigned int)strtoul(p, NULL, 10);
214		/* trim description */
215		while(isspace(*desc))
216			desc++;
217		p = desc;
218		while(*(p+1))
219			p++;
220		while(isspace(*p) && (p > desc))
221			*(p--) = '\0';
222
223		if(timestamp > 0) {
224			if(timestamp <= (unsigned int)current_time) {
225				syslog(LOG_NOTICE, "already expired lease in lease file");
226				continue;
227			} else {
228				leaseduration = timestamp - current_time;
229			}
230		} else {
231			leaseduration = 0;	/* default value */
232		}
233		rhost = NULL;
234		r = upnp_redirect(rhost, eport, iaddr, iport, proto, desc, leaseduration);
235		if(r == -1) {
236			syslog(LOG_ERR, "Failed to redirect %hu -> %s:%hu protocol %s",
237			       eport, iaddr, iport, proto);
238		} else if(r == -2) {
239			/* Add the redirection again to the lease file */
240			lease_file_add(eport, iaddr, iport, proto_atoi(proto),
241			               desc, timestamp);
242		}
243	}
244	fclose(fd);
245
246	return 0;
247}
248#endif
249
250/* upnp_redirect()
251 * calls OS/fw dependant implementation of the redirection.
252 * protocol should be the string "TCP" or "UDP"
253 * returns: 0 on success
254 *          -1 failed to redirect
255 *          -2 already redirected
256 *          -3 permission check failed
257 */
258int
259upnp_redirect(const char * rhost, unsigned short eport,
260              const char * iaddr, unsigned short iport,
261              const char * protocol, const char * desc,
262              unsigned int leaseduration)
263{
264	int proto, r;
265	char iaddr_old[32];
266	unsigned short iport_old;
267	struct in_addr address;
268	unsigned int timestamp;
269
270	proto = proto_atoi(protocol);
271	if(inet_aton(iaddr, &address) <= 0) {
272		syslog(LOG_ERR, "inet_aton(%s) FAILED", iaddr);
273		return -1;
274	}
275
276	if(!check_upnp_rule_against_permissions(upnppermlist, num_upnpperm,
277	                                        eport, address, iport)) {
278		syslog(LOG_INFO, "redirection permission check failed for "
279		                 "%hu->%s:%hu %s", eport, iaddr, iport, protocol);
280		return -3;
281	}
282	r = get_redirect_rule(ext_if_name, eport, proto,
283	                      iaddr_old, sizeof(iaddr_old), &iport_old, 0, 0,
284	                      0, 0,
285	                      &timestamp, 0, 0);
286	if(r == 0) {
287		/* if existing redirect rule matches redirect request return success
288		 * xbox 360 does not keep track of the port it redirects and will
289		 * redirect another port when receiving ConflictInMappingEntry */
290		if(strcmp(iaddr, iaddr_old)==0 && iport==iport_old) {
291			/* redirection already existing */
292			syslog(LOG_INFO, "port %hu %s already redirected to %s:%hu, replacing",
293			       eport, (proto==IPPROTO_TCP)?"tcp":"udp", iaddr_old, iport_old);
294			/* remove and then add again */
295			if(_upnp_delete_redir(eport, proto) < 0) {
296				syslog(LOG_ERR, "failed to remove port mapping");
297			} else
298				goto redirect;
299		} else {
300
301			syslog(LOG_INFO, "port %hu protocol %s already redirected to %s:%hu",
302				eport, protocol, iaddr_old, iport_old);
303			return -2;
304		}
305#ifdef CHECK_PORTINUSE
306	} else if (port_in_use(ext_if_name, eport, proto, iaddr, iport) > 0) {
307		syslog(LOG_INFO, "port %hu protocol %s already in use",
308		       eport, protocol);
309		return -2;
310#endif /* CHECK_PORTINUSE */
311	} else {
312redirect:
313		timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0;
314		syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
315			eport, iaddr, iport, protocol, desc);
316		return upnp_redirect_internal(rhost, eport, iaddr, iport, proto,
317		                              desc, timestamp);
318	}
319
320	return 0;
321}
322
323int
324upnp_redirect_internal(const char * rhost, unsigned short eport,
325                       const char * iaddr, unsigned short iport,
326                       int proto, const char * desc,
327                       unsigned int timestamp)
328{
329	/*syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
330		eport, iaddr, iport, protocol, desc);			*/
331	if(add_redirect_rule2(ext_if_name, rhost, eport, iaddr, iport, proto,
332	                      desc, timestamp) < 0) {
333		return -1;
334	}
335
336#ifdef ENABLE_LEASEFILE
337	lease_file_add( eport, iaddr, iport, proto, desc, timestamp);
338#endif
339/*	syslog(LOG_INFO, "creating pass rule to %s:%hu protocol %s for: %s",
340		iaddr, iport, protocol, desc);*/
341	if(add_filter_rule2(ext_if_name, rhost, iaddr, eport, iport, proto, desc) < 0) {
342		/* clean up the redirect rule */
343#if !defined(__linux__)
344		delete_redirect_rule(ext_if_name, eport, proto);
345#endif
346		return -1;
347	}
348	if(timestamp > 0) {
349		if(!nextruletoclean_timestamp || (timestamp < nextruletoclean_timestamp))
350			nextruletoclean_timestamp = timestamp;
351	}
352#ifdef ENABLE_EVENTS
353	/* the number of port mappings changed, we must
354	 * inform the subscribers */
355	upnp_event_var_change_notify(EWanIPC);
356#endif
357	return 0;
358}
359
360
361
362/* Firewall independant code which call the FW dependant code. */
363int
364upnp_get_redirection_infos(unsigned short eport, const char * protocol,
365                           unsigned short * iport,
366                           char * iaddr, int iaddrlen,
367                           char * desc, int desclen,
368                           char * rhost, int rhostlen,
369                           unsigned int * leaseduration)
370{
371	int r;
372	unsigned int timestamp;
373	time_t current_time;
374
375	if(desc && (desclen > 0))
376		desc[0] = '\0';
377	if(rhost && (rhostlen > 0))
378		rhost[0] = '\0';
379	r = get_redirect_rule(ext_if_name, eport, proto_atoi(protocol),
380	                      iaddr, iaddrlen, iport, desc, desclen,
381	                      rhost, rhostlen, &timestamp,
382	                      0, 0);
383	if(r == 0 &&
384	   timestamp > 0 &&
385	   timestamp > (unsigned int)(current_time = time(NULL))) {
386		*leaseduration = timestamp - current_time;
387	} else {
388		*leaseduration = 0;
389	}
390	return r;
391}
392
393int
394upnp_get_redirection_infos_by_index(int index,
395                                    unsigned short * eport, char * protocol,
396                                    unsigned short * iport,
397                                    char * iaddr, int iaddrlen,
398                                    char * desc, int desclen,
399                                    char * rhost, int rhostlen,
400                                    unsigned int * leaseduration)
401{
402	/*char ifname[IFNAMSIZ];*/
403	int proto = 0;
404	unsigned int timestamp;
405	time_t current_time;
406
407	if(desc && (desclen > 0))
408		desc[0] = '\0';
409	if(rhost && (rhostlen > 0))
410		rhost[0] = '\0';
411	if(get_redirect_rule_by_index(index, 0/*ifname*/, eport, iaddr, iaddrlen,
412	                              iport, &proto, desc, desclen,
413	                              rhost, rhostlen, &timestamp,
414	                              0, 0) < 0)
415		return -1;
416	else
417	{
418		current_time = time(NULL);
419		*leaseduration = (timestamp > (unsigned int)current_time)
420		                 ? (timestamp - current_time)
421		                 : 0;
422		if(proto == IPPROTO_TCP)
423			memcpy(protocol, "TCP", 4);
424		else
425			memcpy(protocol, "UDP", 4);
426		return 0;
427	}
428}
429
430/* called from natpmp.c too */
431int
432_upnp_delete_redir(unsigned short eport, int proto)
433{
434	int r;
435#if defined(__linux__)
436	r = delete_redirect_and_filter_rules(eport, proto);
437#elif defined(USE_PF)
438	r = delete_redirect_and_filter_rules(ext_if_name, eport, proto);
439#else
440	r = delete_redirect_rule(ext_if_name, eport, proto);
441	delete_filter_rule(ext_if_name, eport, proto);
442#endif
443#ifdef ENABLE_LEASEFILE
444	lease_file_remove( eport, proto);
445#endif
446
447#ifdef ENABLE_EVENTS
448	upnp_event_var_change_notify(EWanIPC);
449#endif
450	return r;
451}
452
453int
454upnp_delete_redirection(unsigned short eport, const char * protocol)
455{
456	syslog(LOG_INFO, "removing redirect rule port %hu %s", eport, protocol);
457	return _upnp_delete_redir(eport, proto_atoi(protocol));
458}
459
460/* upnp_get_portmapping_number_of_entries()
461 * TODO: improve this code. */
462int
463upnp_get_portmapping_number_of_entries()
464{
465	int n = 0, r = 0;
466	unsigned short eport, iport;
467	char protocol[4], iaddr[32], desc[64], rhost[32];
468	unsigned int leaseduration;
469	do {
470		protocol[0] = '\0'; iaddr[0] = '\0'; desc[0] = '\0';
471		r = upnp_get_redirection_infos_by_index(n, &eport, protocol, &iport,
472		                                        iaddr, sizeof(iaddr),
473		                                        desc, sizeof(desc),
474		                                        rhost, sizeof(rhost),
475		                                        &leaseduration);
476		n++;
477	} while(r==0);
478	return (n-1);
479}
480
481/* functions used to remove unused rules
482 * As a side effect, delete expired rules (based on LeaseDuration) */
483struct rule_state *
484get_upnp_rules_state_list(int max_rules_number_target)
485{
486	/*char ifname[IFNAMSIZ];*/
487	int proto;
488	unsigned short iport;
489	unsigned int timestamp;
490	struct rule_state * tmp;
491	struct rule_state * list = 0;
492	struct rule_state * * p;
493	int i = 0;
494	time_t current_time;
495
496	/*ifname[0] = '\0';*/
497	tmp = malloc(sizeof(struct rule_state));
498	if(!tmp)
499		return 0;
500	current_time = time(NULL);
501	nextruletoclean_timestamp = 0;
502	while(get_redirect_rule_by_index(i, /*ifname*/0, &tmp->eport, 0, 0,
503	                              &iport, &proto, 0, 0, 0,0, &timestamp,
504								  &tmp->packets, &tmp->bytes) >= 0)
505	{
506		tmp->to_remove = 0;
507		if(timestamp > 0) {
508			/* need to remove this port mapping ? */
509			if(timestamp <= (unsigned int)current_time)
510				tmp->to_remove = 1;
511			else if((nextruletoclean_timestamp <= (unsigned int)current_time)
512			       || (timestamp < nextruletoclean_timestamp))
513				nextruletoclean_timestamp = timestamp;
514		}
515		tmp->proto = (short)proto;
516		/* add tmp to list */
517		tmp->next = list;
518		list = tmp;
519		/* prepare next iteration */
520		i++;
521		tmp = malloc(sizeof(struct rule_state));
522		if(!tmp)
523			break;
524	}
525#ifdef PCP_PEER
526	i=0;
527	while(get_peer_rule_by_index(i, /*ifname*/0, &tmp->eport, 0, 0,
528		                              &iport, &proto, 0, 0, 0,0,0, &timestamp,
529									  &tmp->packets, &tmp->bytes) >= 0)
530	{
531		tmp->to_remove = 0;
532		if(timestamp > 0) {
533			/* need to remove this port mapping ? */
534			if(timestamp <= (unsigned int)current_time)
535				tmp->to_remove = 1;
536			else if((nextruletoclean_timestamp <= (unsigned int)current_time)
537				   || (timestamp < nextruletoclean_timestamp))
538				nextruletoclean_timestamp = timestamp;
539		}
540		tmp->proto = (short)proto;
541		/* add tmp to list */
542		tmp->next = list;
543		list = tmp;
544		/* prepare next iteration */
545		i++;
546		tmp = malloc(sizeof(struct rule_state));
547		if(!tmp)
548			break;
549	}
550#endif
551	free(tmp);
552	/* remove the redirections that need to be removed */
553	for(p = &list, tmp = list; tmp; tmp = *p)
554	{
555		if(tmp->to_remove)
556		{
557			syslog(LOG_DEBUG, "remove port mapping %hu %s because it has expired",
558			       tmp->eport, (tmp->proto==IPPROTO_TCP)?"TCP":"UDP");
559			_upnp_delete_redir(tmp->eport, tmp->proto);
560			*p = tmp->next;
561			free(tmp);
562			i--;
563		} else {
564			p = &(tmp->next);
565		}
566	}
567	/* return empty list if not enough redirections */
568	if(i<=max_rules_number_target)
569		while(list)
570		{
571			tmp = list;
572			list = tmp->next;
573			free(tmp);
574		}
575	/* return list */
576	return list;
577}
578
579void
580remove_unused_rules(struct rule_state * list)
581{
582	char ifname[IFNAMSIZ];
583	unsigned short iport;
584	struct rule_state * tmp;
585	u_int64_t packets;
586	u_int64_t bytes;
587	unsigned int timestamp;
588	int n = 0;
589
590	while(list)
591	{
592		/* remove the rule if no traffic has used it */
593		if(get_redirect_rule(ifname, list->eport, list->proto,
594	                         0, 0, &iport, 0, 0, 0, 0, &timestamp,
595		                     &packets, &bytes) >= 0)
596		{
597			if(packets == list->packets && bytes == list->bytes)
598			{
599				if(_upnp_delete_redir(list->eport, list->proto) >= 0)
600					n++;
601			}
602		}
603		tmp = list;
604		list = tmp->next;
605		free(tmp);
606	}
607	if(n>0)
608		syslog(LOG_DEBUG, "removed %d unused rules", n);
609}
610
611/* upnp_get_portmappings_in_range()
612 * return a list of all "external" ports for which a port
613 * mapping exists */
614unsigned short *
615upnp_get_portmappings_in_range(unsigned short startport,
616                               unsigned short endport,
617                               const char * protocol,
618                               unsigned int * number)
619{
620	int proto;
621	proto = proto_atoi(protocol);
622	if(!number)
623		return NULL;
624	return get_portmappings_in_range(startport, endport, proto, number);
625}
626
627/* stuff for miniupnpdctl */
628#ifdef USE_MINIUPNPDCTL
629void
630write_ruleset_details(int s)
631{
632	int proto = 0;
633	unsigned short eport, iport;
634	char desc[64];
635	char iaddr[32];
636	char rhost[32];
637	unsigned int timestamp;
638	u_int64_t packets;
639	u_int64_t bytes;
640	int i = 0;
641	char buffer[256];
642	int n;
643
644	write(s, "Ruleset :\n", 10);
645	while(get_redirect_rule_by_index(i, 0/*ifname*/, &eport, iaddr, sizeof(iaddr),
646	                                 &iport, &proto, desc, sizeof(desc),
647	                                 rhost, sizeof(rhost),
648	                                 &timestamp,
649	                                 &packets, &bytes) >= 0)
650	{
651		n = snprintf(buffer, sizeof(buffer),
652		             "%2d %s %s:%hu->%s:%hu "
653		             "'%s' %u %" PRIu64 " %" PRIu64 "\n",
654		             /*"'%s' %llu %llu\n",*/
655		             i, proto==IPPROTO_TCP?"TCP":"UDP", rhost,
656		             eport, iaddr, iport, desc, timestamp, packets, bytes);
657		write(s, buffer, n);
658		i++;
659	}
660}
661#endif
662
663