Miniupnpd: update to 2.0
[tomato.git] / release / src-rt-6.x.4708 / router / miniupnpd / upnpredirect.c
blob9511273576437e25f57df6bcb0e6c150155aa008
1 /* $Id: upnpredirect.c,v 1.91 2016/02/16 12:15:02 nanard Exp $ */
2 /* vim: tabstop=4 shiftwidth=4 noexpandtab
3 * MiniUPnP project
4 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
5 * (c) 2006-2016 Thomas Bernard
6 * This software is subject to the conditions detailed
7 * in the LICENCE file provided within the distribution */
9 #include <stdlib.h>
10 #include <string.h>
11 #include <syslog.h>
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <net/if.h>
16 #include <arpa/inet.h>
18 #include <stdio.h>
19 #include <ctype.h>
20 #include <unistd.h>
22 #include "macros.h"
23 #include "config.h"
24 #include "upnpredirect.h"
25 #include "upnpglobalvars.h"
26 #include "upnpevents.h"
27 #include "portinuse.h"
28 #if defined(USE_NETFILTER)
29 #include "netfilter/iptcrdr.h"
30 #endif
31 #if defined(USE_PF)
32 #include "pf/obsdrdr.h"
33 #endif
34 #if defined(USE_IPF)
35 #include "ipf/ipfrdr.h"
36 #endif
37 #if defined(USE_IPFW)
38 #include "ipfw/ipfwrdr.h"
39 #endif
40 #ifdef USE_MINIUPNPDCTL
41 #include <stdio.h>
42 #include <unistd.h>
43 #endif
44 #ifdef ENABLE_LEASEFILE
45 #include <sys/stat.h>
46 #endif
48 /* from <inttypes.h> */
49 #ifndef PRIu64
50 #define PRIu64 "llu"
51 #endif
53 /* proto_atoi()
54 * convert the string "UDP" or "TCP" to IPPROTO_UDP and IPPROTO_UDP */
55 static int
56 proto_atoi(const char * protocol)
58 int proto = IPPROTO_TCP;
59 if(strcasecmp(protocol, "UDP") == 0)
60 proto = IPPROTO_UDP;
61 #ifdef IPPROTO_UDPLITE
62 else if(strcasecmp(protocol, "UDPLITE") == 0)
63 proto = IPPROTO_UDPLITE;
64 #endif /* IPPROTO_UDPLITE */
65 return proto;
68 /* proto_itoa()
69 * convert IPPROTO_UDP, IPPROTO_UDP, etc. to "UDP", "TCP" */
70 static const char *
71 proto_itoa(int proto)
73 const char * protocol;
74 switch(proto) {
75 case IPPROTO_UDP:
76 protocol = "UDP";
77 break;
78 case IPPROTO_TCP:
79 protocol = "TCP";
80 break;
81 #ifdef IPPROTO_UDPLITE
82 case IPPROTO_UDPLITE:
83 protocol = "UDPLITE";
84 break;
85 #endif /* IPPROTO_UDPLITE */
86 default:
87 protocol = "*UNKNOWN*";
89 return protocol;
92 #ifdef ENABLE_LEASEFILE
93 static int
94 lease_file_add(unsigned short eport,
95 const char * iaddr,
96 unsigned short iport,
97 int proto,
98 const char * desc,
99 unsigned int timestamp)
101 FILE * fd;
103 if (lease_file == NULL) return 0;
105 fd = fopen( lease_file, "a");
106 if (fd==NULL) {
107 syslog(LOG_ERR, "could not open lease file: %s", lease_file);
108 return -1;
111 fprintf(fd, "%s:%hu:%s:%hu:%u:%s\n",
112 proto_itoa(proto), eport, iaddr, iport,
113 timestamp, desc);
114 fclose(fd);
116 return 0;
119 static int
120 lease_file_remove(unsigned short eport, int proto)
122 FILE* fd, *fdt;
123 int tmp;
124 char buf[512];
125 char str[32];
126 char tmpfilename[128];
127 int str_size, buf_size;
130 if (lease_file == NULL) return 0;
132 if (strlen( lease_file) + 7 > sizeof(tmpfilename)) {
133 syslog(LOG_ERR, "Lease filename is too long");
134 return -1;
137 strncpy( tmpfilename, lease_file, sizeof(tmpfilename) );
138 strncat( tmpfilename, "XXXXXX", sizeof(tmpfilename) - strlen(tmpfilename));
140 fd = fopen( lease_file, "r");
141 if (fd==NULL) {
142 return 0;
145 snprintf( str, sizeof(str), "%s:%u", proto_itoa(proto), eport);
146 str_size = strlen(str);
148 tmp = mkstemp(tmpfilename);
149 if (tmp==-1) {
150 fclose(fd);
151 syslog(LOG_ERR, "could not open temporary lease file");
152 return -1;
154 fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
155 fdt = fdopen(tmp, "a");
157 buf[sizeof(buf)-1] = 0;
158 while( fgets(buf, sizeof(buf)-1, fd) != NULL) {
159 buf_size = strlen(buf);
161 if (buf_size < str_size || strncmp(str, buf, str_size)!=0) {
162 fwrite(buf, buf_size, 1, fdt);
165 fclose(fdt);
166 fclose(fd);
168 if (rename(tmpfilename, lease_file) < 0) {
169 syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file);
170 remove(tmpfilename);
173 return 0;
177 /* reload_from_lease_file()
178 * read lease_file and add the rules contained
180 int reload_from_lease_file()
182 FILE * fd;
183 char * p;
184 unsigned short eport, iport;
185 char * proto;
186 char * iaddr;
187 char * desc;
188 char * rhost;
189 unsigned int leaseduration;
190 unsigned int timestamp;
191 time_t current_time;
192 char line[128];
193 int r;
195 if(!lease_file) return -1;
196 fd = fopen( lease_file, "r");
197 if (fd==NULL) {
198 syslog(LOG_ERR, "could not open lease file: %s", lease_file);
199 return -1;
201 if(unlink(lease_file) < 0) {
202 syslog(LOG_WARNING, "could not unlink file %s : %m", lease_file);
205 current_time = time(NULL);
206 while(fgets(line, sizeof(line), fd)) {
207 syslog(LOG_DEBUG, "parsing lease file line '%s'", line);
208 proto = line;
209 p = strchr(line, ':');
210 if(!p) {
211 syslog(LOG_ERR, "unrecognized data in lease file");
212 continue;
214 *(p++) = '\0';
215 iaddr = strchr(p, ':');
216 if(!iaddr) {
217 syslog(LOG_ERR, "unrecognized data in lease file");
218 continue;
220 *(iaddr++) = '\0';
221 eport = (unsigned short)atoi(p);
222 p = strchr(iaddr, ':');
223 if(!p) {
224 syslog(LOG_ERR, "unrecognized data in lease file");
225 continue;
227 *(p++) = '\0';
228 iport = (unsigned short)atoi(p);
229 p = strchr(p, ':');
230 if(!p) {
231 syslog(LOG_ERR, "unrecognized data in lease file");
232 continue;
234 *(p++) = '\0';
235 desc = strchr(p, ':');
236 if(!desc) {
237 syslog(LOG_ERR, "unrecognized data in lease file");
238 continue;
240 *(desc++) = '\0';
241 /*timestamp = (unsigned int)atoi(p);*/
242 timestamp = (unsigned int)strtoul(p, NULL, 10);
243 /* trim description */
244 while(isspace(*desc))
245 desc++;
246 p = desc;
247 while(*(p+1))
248 p++;
249 while(isspace(*p) && (p > desc))
250 *(p--) = '\0';
252 if(timestamp > 0) {
253 if(timestamp <= (unsigned int)current_time) {
254 syslog(LOG_NOTICE, "already expired lease in lease file");
255 continue;
256 } else {
257 leaseduration = timestamp - current_time;
259 } else {
260 leaseduration = 0; /* default value */
262 rhost = NULL;
263 r = upnp_redirect(rhost, eport, iaddr, iport, proto, desc, leaseduration);
264 if(r == -1) {
265 syslog(LOG_ERR, "Failed to redirect %hu -> %s:%hu protocol %s",
266 eport, iaddr, iport, proto);
267 } else if(r == -2) {
268 /* Add the redirection again to the lease file */
269 lease_file_add(eport, iaddr, iport, proto_atoi(proto),
270 desc, timestamp);
273 fclose(fd);
275 return 0;
277 #endif
279 /* upnp_redirect()
280 * calls OS/fw dependant implementation of the redirection.
281 * protocol should be the string "TCP" or "UDP"
282 * returns: 0 on success
283 * -1 failed to redirect
284 * -2 already redirected
285 * -3 permission check failed
286 * -4 already redirected (other mechanism)
289 upnp_redirect(const char * rhost, unsigned short eport,
290 const char * iaddr, unsigned short iport,
291 const char * protocol, const char * desc,
292 unsigned int leaseduration)
294 int proto, r;
295 char iaddr_old[32];
296 char rhost_old[32];
297 unsigned short iport_old;
298 struct in_addr address;
299 unsigned int timestamp;
301 proto = proto_atoi(protocol);
302 if(inet_aton(iaddr, &address) <= 0) {
303 syslog(LOG_ERR, "inet_aton(%s) FAILED", iaddr);
304 return -1;
307 if(!check_upnp_rule_against_permissions(upnppermlist, num_upnpperm,
308 eport, address, iport)) {
309 syslog(LOG_INFO, "redirection permission check failed for "
310 "%hu->%s:%hu %s", eport, iaddr, iport, protocol);
311 return -3;
313 /* IGDv1 (WANIPConnection:1 Service Template Version 1.01 / Nov 12, 2001)
314 * - 2.2.20.PortMappingDescription :
315 * Overwriting Previous / Existing Port Mappings:
316 * If the RemoteHost, ExternalPort, PortMappingProtocol and InternalClient
317 * are exactly the same as an existing mapping, the existing mapping values
318 * for InternalPort, PortMappingDescription, PortMappingEnabled and
319 * PortMappingLeaseDuration are overwritten.
320 * Rejecting a New Port Mapping:
321 * In cases where the RemoteHost, ExternalPort and PortMappingProtocol
322 * are the same as an existing mapping, but the InternalClient is
323 * different, the action is rejected with an appropriate error.
324 * Add or Reject New Port Mapping behavior based on vendor implementation:
325 * In cases where the ExternalPort, PortMappingProtocol and InternalClient
326 * are the same, but RemoteHost is different, the vendor can choose to
327 * support both mappings simultaneously, or reject the second mapping
328 * with an appropriate error.
330 * - 2.4.16.AddPortMapping
331 * This action creates a new port mapping or overwrites an existing
332 * mapping with the same internal client. If the ExternalPort and
333 * PortMappingProtocol pair is already mapped to another internal client,
334 * an error is returned.
336 * IGDv2 (WANIPConnection:2 Service Standardized DCP (SDCP) Sep 10, 2010)
337 * Protocol ExternalPort RemoteHost InternalClient Result
338 * = = ≠ ≠ Failure
339 * = = ≠ = Failure or success
340 * (vendor specific)
341 * = = = ≠ Failure
342 * = = = = Success (overwrite)
344 rhost_old[0] = '\0';
345 r = get_redirect_rule(ext_if_name, eport, proto,
346 iaddr_old, sizeof(iaddr_old), &iport_old, 0, 0,
347 rhost_old, sizeof(rhost_old),
348 &timestamp, 0, 0);
349 if(r == 0) {
350 if(strcmp(iaddr, iaddr_old)==0 &&
351 ((rhost == NULL && rhost_old[0]=='\0') ||
352 (rhost && (strcmp(rhost, "*") == 0) && rhost_old[0]=='\0') ||
353 (rhost && (strcmp(rhost, rhost_old) == 0)))) {
354 syslog(LOG_INFO, "updating existing port mapping %hu %s (rhost '%s') => %s:%hu",
355 eport, protocol, rhost_old, iaddr_old, iport_old);
356 timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0;
357 if(iport != iport_old) {
358 r = update_portmapping(ext_if_name, eport, proto, iport, desc, timestamp);
359 } else {
360 r = update_portmapping_desc_timestamp(ext_if_name, eport, proto, desc, timestamp);
362 #ifdef ENABLE_LEASEFILE
363 if(r == 0) {
364 lease_file_remove(eport, proto);
365 lease_file_add(eport, iaddr, iport, proto, desc, timestamp);
367 #endif /* ENABLE_LEASEFILE */
368 return r;
369 } else {
370 syslog(LOG_INFO, "port %hu %s (rhost '%s') already redirected to %s:%hu",
371 eport, protocol, rhost_old, iaddr_old, iport_old);
372 return -2;
374 #ifdef CHECK_PORTINUSE
375 } else if (port_in_use(ext_if_name, eport, proto, iaddr, iport) > 0) {
376 syslog(LOG_INFO, "port %hu protocol %s already in use",
377 eport, protocol);
378 return -4;
379 #endif /* CHECK_PORTINUSE */
380 } else {
381 timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0;
382 syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
383 eport, iaddr, iport, protocol, desc);
384 return upnp_redirect_internal(rhost, eport, iaddr, iport, proto,
385 desc, timestamp);
390 upnp_redirect_internal(const char * rhost, unsigned short eport,
391 const char * iaddr, unsigned short iport,
392 int proto, const char * desc,
393 unsigned int timestamp)
395 /*syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
396 eport, iaddr, iport, protocol, desc); */
397 if(add_redirect_rule2(ext_if_name, rhost, eport, iaddr, iport, proto,
398 desc, timestamp) < 0) {
399 return -1;
402 #ifdef ENABLE_LEASEFILE
403 lease_file_add( eport, iaddr, iport, proto, desc, timestamp);
404 #endif
405 /* syslog(LOG_INFO, "creating pass rule to %s:%hu protocol %s for: %s",
406 iaddr, iport, protocol, desc);*/
407 if(add_filter_rule2(ext_if_name, rhost, iaddr, eport, iport, proto, desc) < 0) {
408 /* clean up the redirect rule */
409 #if !defined(__linux__)
410 delete_redirect_rule(ext_if_name, eport, proto);
411 #endif
412 return -1;
414 if(timestamp > 0) {
415 if(!nextruletoclean_timestamp || (timestamp < nextruletoclean_timestamp))
416 nextruletoclean_timestamp = timestamp;
418 #ifdef ENABLE_EVENTS
419 /* the number of port mappings changed, we must
420 * inform the subscribers */
421 upnp_event_var_change_notify(EWanIPC);
422 #endif
423 return 0;
428 /* Firewall independant code which call the FW dependant code. */
430 upnp_get_redirection_infos(unsigned short eport, const char * protocol,
431 unsigned short * iport,
432 char * iaddr, int iaddrlen,
433 char * desc, int desclen,
434 char * rhost, int rhostlen,
435 unsigned int * leaseduration)
437 int r;
438 unsigned int timestamp;
439 time_t current_time;
441 if(desc && (desclen > 0))
442 desc[0] = '\0';
443 if(rhost && (rhostlen > 0))
444 rhost[0] = '\0';
445 r = get_redirect_rule(ext_if_name, eport, proto_atoi(protocol),
446 iaddr, iaddrlen, iport, desc, desclen,
447 rhost, rhostlen, &timestamp,
448 0, 0);
449 if(r == 0 &&
450 timestamp > 0 &&
451 timestamp > (unsigned int)(current_time = time(NULL))) {
452 *leaseduration = timestamp - current_time;
453 } else {
454 *leaseduration = 0;
456 return r;
460 upnp_get_redirection_infos_by_index(int index,
461 unsigned short * eport, char * protocol,
462 unsigned short * iport,
463 char * iaddr, int iaddrlen,
464 char * desc, int desclen,
465 char * rhost, int rhostlen,
466 unsigned int * leaseduration)
468 /*char ifname[IFNAMSIZ];*/
469 int proto = 0;
470 unsigned int timestamp;
471 time_t current_time;
473 if(desc && (desclen > 0))
474 desc[0] = '\0';
475 if(rhost && (rhostlen > 0))
476 rhost[0] = '\0';
477 if(get_redirect_rule_by_index(index, 0/*ifname*/, eport, iaddr, iaddrlen,
478 iport, &proto, desc, desclen,
479 rhost, rhostlen, &timestamp,
480 0, 0) < 0)
481 return -1;
482 else
484 current_time = time(NULL);
485 *leaseduration = (timestamp > (unsigned int)current_time)
486 ? (timestamp - current_time)
487 : 0;
488 if(proto == IPPROTO_TCP)
489 memcpy(protocol, "TCP", 4);
490 #ifdef IPPROTO_UDPLITE
491 else if(proto == IPPROTO_UDPLITE)
492 memcpy(protocol, "UDPLITE", 8);
493 #endif /* IPPROTO_UDPLITE */
494 else
495 memcpy(protocol, "UDP", 4);
496 return 0;
500 /* called from natpmp.c too */
502 _upnp_delete_redir(unsigned short eport, int proto)
504 int r;
505 #if defined(__linux__)
506 r = delete_redirect_and_filter_rules(eport, proto);
507 #elif defined(USE_PF)
508 r = delete_redirect_and_filter_rules(ext_if_name, eport, proto);
509 #else
510 r = delete_redirect_rule(ext_if_name, eport, proto);
511 delete_filter_rule(ext_if_name, eport, proto);
512 #endif
513 #ifdef ENABLE_LEASEFILE
514 lease_file_remove( eport, proto);
515 #endif
517 #ifdef ENABLE_EVENTS
518 upnp_event_var_change_notify(EWanIPC);
519 #endif
520 return r;
524 upnp_delete_redirection(unsigned short eport, const char * protocol)
526 syslog(LOG_INFO, "removing redirect rule port %hu %s", eport, protocol);
527 return _upnp_delete_redir(eport, proto_atoi(protocol));
530 /* upnp_get_portmapping_number_of_entries()
531 * TODO: improve this code. */
533 upnp_get_portmapping_number_of_entries()
535 int n = 0, r = 0;
536 unsigned short eport, iport;
537 char protocol[4], iaddr[32], desc[64], rhost[32];
538 unsigned int leaseduration;
539 do {
540 protocol[0] = '\0'; iaddr[0] = '\0'; desc[0] = '\0';
541 r = upnp_get_redirection_infos_by_index(n, &eport, protocol, &iport,
542 iaddr, sizeof(iaddr),
543 desc, sizeof(desc),
544 rhost, sizeof(rhost),
545 &leaseduration);
546 n++;
547 } while(r==0);
548 return (n-1);
551 /* functions used to remove unused rules
552 * As a side effect, delete expired rules (based on LeaseDuration) */
553 struct rule_state *
554 get_upnp_rules_state_list(int max_rules_number_target)
556 /*char ifname[IFNAMSIZ];*/
557 int proto;
558 unsigned short iport;
559 unsigned int timestamp;
560 struct rule_state * tmp;
561 struct rule_state * list = 0;
562 struct rule_state * * p;
563 int i = 0;
564 time_t current_time;
566 /*ifname[0] = '\0';*/
567 tmp = malloc(sizeof(struct rule_state));
568 if(!tmp)
569 return 0;
570 current_time = time(NULL);
571 nextruletoclean_timestamp = 0;
572 while(get_redirect_rule_by_index(i, /*ifname*/0, &tmp->eport, 0, 0,
573 &iport, &proto, 0, 0, 0,0, &timestamp,
574 &tmp->packets, &tmp->bytes) >= 0)
576 tmp->to_remove = 0;
577 if(timestamp > 0) {
578 /* need to remove this port mapping ? */
579 if(timestamp <= (unsigned int)current_time)
580 tmp->to_remove = 1;
581 else if((nextruletoclean_timestamp <= (unsigned int)current_time)
582 || (timestamp < nextruletoclean_timestamp))
583 nextruletoclean_timestamp = timestamp;
585 tmp->proto = (short)proto;
586 /* add tmp to list */
587 tmp->next = list;
588 list = tmp;
589 /* prepare next iteration */
590 i++;
591 tmp = malloc(sizeof(struct rule_state));
592 if(!tmp)
593 break;
595 #ifdef PCP_PEER
596 i=0;
597 while(get_peer_rule_by_index(i, /*ifname*/0, &tmp->eport, 0, 0,
598 &iport, &proto, 0, 0, 0,0,0, &timestamp,
599 &tmp->packets, &tmp->bytes) >= 0)
601 tmp->to_remove = 0;
602 if(timestamp > 0) {
603 /* need to remove this port mapping ? */
604 if(timestamp <= (unsigned int)current_time)
605 tmp->to_remove = 1;
606 else if((nextruletoclean_timestamp <= (unsigned int)current_time)
607 || (timestamp < nextruletoclean_timestamp))
608 nextruletoclean_timestamp = timestamp;
610 tmp->proto = (short)proto;
611 /* add tmp to list */
612 tmp->next = list;
613 list = tmp;
614 /* prepare next iteration */
615 i++;
616 tmp = malloc(sizeof(struct rule_state));
617 if(!tmp)
618 break;
620 #endif
621 free(tmp);
622 /* remove the redirections that need to be removed */
623 for(p = &list, tmp = list; tmp; tmp = *p)
625 if(tmp->to_remove)
627 syslog(LOG_NOTICE, "remove port mapping %hu %s because it has expired",
628 tmp->eport, proto_itoa(tmp->proto));
629 _upnp_delete_redir(tmp->eport, tmp->proto);
630 *p = tmp->next;
631 free(tmp);
632 i--;
633 } else {
634 p = &(tmp->next);
637 /* return empty list if not enough redirections */
638 if(i<=max_rules_number_target)
639 while(list)
641 tmp = list;
642 list = tmp->next;
643 free(tmp);
645 /* return list */
646 return list;
649 void
650 remove_unused_rules(struct rule_state * list)
652 char ifname[IFNAMSIZ];
653 unsigned short iport;
654 struct rule_state * tmp;
655 u_int64_t packets;
656 u_int64_t bytes;
657 unsigned int timestamp;
658 int n = 0;
660 while(list)
662 /* remove the rule if no traffic has used it */
663 if(get_redirect_rule(ifname, list->eport, list->proto,
664 0, 0, &iport, 0, 0, 0, 0, &timestamp,
665 &packets, &bytes) >= 0)
667 if(packets == list->packets && bytes == list->bytes)
669 syslog(LOG_DEBUG, "removing unused mapping %hu %s : still "
670 "%" PRIu64 "packets %" PRIu64 "bytes",
671 list->eport, proto_itoa(list->proto),
672 packets, bytes);
673 _upnp_delete_redir(list->eport, list->proto);
674 n++;
677 tmp = list;
678 list = tmp->next;
679 free(tmp);
681 if(n>0)
682 syslog(LOG_NOTICE, "removed %d unused rules", n);
685 /* upnp_get_portmappings_in_range()
686 * return a list of all "external" ports for which a port
687 * mapping exists */
688 unsigned short *
689 upnp_get_portmappings_in_range(unsigned short startport,
690 unsigned short endport,
691 const char * protocol,
692 unsigned int * number)
694 int proto;
695 proto = proto_atoi(protocol);
696 if(!number)
697 return NULL;
698 return get_portmappings_in_range(startport, endport, proto, number);
701 /* stuff for miniupnpdctl */
702 #ifdef USE_MINIUPNPDCTL
703 void
704 write_ruleset_details(int s)
706 int proto = 0;
707 unsigned short eport, iport;
708 char desc[64];
709 char iaddr[32];
710 char rhost[32];
711 unsigned int timestamp;
712 u_int64_t packets;
713 u_int64_t bytes;
714 int i = 0;
715 char buffer[256];
716 int n;
718 write(s, "Ruleset :\n", 10);
719 while(get_redirect_rule_by_index(i, 0/*ifname*/, &eport, iaddr, sizeof(iaddr),
720 &iport, &proto, desc, sizeof(desc),
721 rhost, sizeof(rhost),
722 &timestamp,
723 &packets, &bytes) >= 0)
725 n = snprintf(buffer, sizeof(buffer),
726 "%2d %s %s:%hu->%s:%hu "
727 "'%s' %u %" PRIu64 " %" PRIu64 "\n",
728 /*"'%s' %llu %llu\n",*/
729 i, proto_itoa(proto), rhost,
730 eport, iaddr, iport, desc, timestamp, packets, bytes);
731 write(s, buffer, n);
732 i++;
735 #endif