Miniupnpd: update from 1.8 (20140422) to 1.9 (20141209)
[tomato.git] / release / src / router / miniupnpd / upnpredirect.c
blob28560ab729efb28ca73353ed566754d82e2d6e1d
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 */
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>
17 #include <stdio.h>
18 #include <ctype.h>
19 #include <unistd.h>
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
47 /* from <inttypes.h> */
48 #ifndef PRIu64
49 #define PRIu64 "llu"
50 #endif
52 /* proto_atoi()
53 * convert the string "UDP" or "TCP" to IPPROTO_UDP and IPPROTO_UDP */
54 static int
55 proto_atoi(const char * protocol)
57 int proto = IPPROTO_TCP;
58 if(strcmp(protocol, "UDP") == 0)
59 proto = IPPROTO_UDP;
60 return proto;
63 #ifdef ENABLE_LEASEFILE
64 static int
65 lease_file_add(unsigned short eport,
66 const char * iaddr,
67 unsigned short iport,
68 int proto,
69 const char * desc,
70 unsigned int timestamp)
72 FILE * fd;
74 if (lease_file == NULL) return 0;
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;
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);
87 return 0;
90 static int
91 lease_file_remove(unsigned short eport, int proto)
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;
101 if (lease_file == NULL) return 0;
103 if (strlen( lease_file) + 7 > sizeof(tmpfilename)) {
104 syslog(LOG_ERR, "Lease filename is too long");
105 return -1;
108 strncpy( tmpfilename, lease_file, sizeof(tmpfilename) );
109 strncat( tmpfilename, "XXXXXX", sizeof(tmpfilename) - strlen(tmpfilename));
111 fd = fopen( lease_file, "r");
112 if (fd==NULL) {
113 return 0;
116 snprintf( str, sizeof(str), "%s:%u", ((proto==IPPROTO_TCP)?"TCP":"UDP"), eport);
117 str_size = strlen(str);
119 tmp = mkstemp(tmpfilename);
120 if (tmp==-1) {
121 fclose(fd);
122 syslog(LOG_ERR, "could not open temporary lease file");
123 return -1;
125 fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
126 fdt = fdopen(tmp, "a");
128 buf[sizeof(buf)-1] = 0;
129 while( fgets(buf, sizeof(buf)-1, fd) != NULL) {
130 buf_size = strlen(buf);
132 if (buf_size < str_size || strncmp(str, buf, str_size)!=0) {
133 fwrite(buf, buf_size, 1, fdt);
136 fclose(fdt);
137 fclose(fd);
139 if (rename(tmpfilename, lease_file) < 0) {
140 syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file);
141 remove(tmpfilename);
144 return 0;
148 /* reload_from_lease_file()
149 * read lease_file and add the rules contained
151 int reload_from_lease_file()
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;
166 if(!lease_file) return -1;
167 fd = fopen( lease_file, "r");
168 if (fd==NULL) {
169 syslog(LOG_ERR, "could not open lease file: %s", lease_file);
170 return -1;
172 if(unlink(lease_file) < 0) {
173 syslog(LOG_WARNING, "could not unlink file %s : %m", lease_file);
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;
185 *(p++) = '\0';
186 iaddr = strchr(p, ':');
187 if(!iaddr) {
188 syslog(LOG_ERR, "unrecognized data in lease file");
189 continue;
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;
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;
205 *(p++) = '\0';
206 desc = strchr(p, ':');
207 if(!desc) {
208 syslog(LOG_ERR, "unrecognized data in lease file");
209 continue;
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';
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;
230 } else {
231 leaseduration = 0; /* default value */
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);
244 fclose(fd);
246 return 0;
248 #endif
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
259 upnp_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)
264 int proto, r;
265 char iaddr_old[32];
266 unsigned short iport_old;
267 struct in_addr address;
268 unsigned int timestamp;
270 proto = proto_atoi(protocol);
271 if(inet_aton(iaddr, &address) <= 0) {
272 syslog(LOG_ERR, "inet_aton(%s) FAILED", iaddr);
273 return -1;
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;
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 syslog(LOG_INFO, "ignoring redirect request as it matches existing redirect");
292 } else {
294 syslog(LOG_INFO, "port %hu protocol %s already redirected to %s:%hu",
295 eport, protocol, iaddr_old, iport_old);
296 return -2;
298 #ifdef CHECK_PORTINUSE
299 } else if (port_in_use(ext_if_name, eport, proto, iaddr, iport) > 0) {
300 syslog(LOG_INFO, "port %hu protocol %s already in use",
301 eport, protocol);
302 return -2;
303 #endif /* CHECK_PORTINUSE */
304 } else {
305 timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0;
306 syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
307 eport, iaddr, iport, protocol, desc);
308 return upnp_redirect_internal(rhost, eport, iaddr, iport, proto,
309 desc, timestamp);
312 return 0;
316 upnp_redirect_internal(const char * rhost, unsigned short eport,
317 const char * iaddr, unsigned short iport,
318 int proto, const char * desc,
319 unsigned int timestamp)
321 /*syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
322 eport, iaddr, iport, protocol, desc); */
323 if(add_redirect_rule2(ext_if_name, rhost, eport, iaddr, iport, proto,
324 desc, timestamp) < 0) {
325 return -1;
328 #ifdef ENABLE_LEASEFILE
329 lease_file_add( eport, iaddr, iport, proto, desc, timestamp);
330 #endif
331 /* syslog(LOG_INFO, "creating pass rule to %s:%hu protocol %s for: %s",
332 iaddr, iport, protocol, desc);*/
333 if(add_filter_rule2(ext_if_name, rhost, iaddr, eport, iport, proto, desc) < 0) {
334 /* clean up the redirect rule */
335 #if !defined(__linux__)
336 delete_redirect_rule(ext_if_name, eport, proto);
337 #endif
338 return -1;
340 if(timestamp > 0) {
341 if(!nextruletoclean_timestamp || (timestamp < nextruletoclean_timestamp))
342 nextruletoclean_timestamp = timestamp;
344 #ifdef ENABLE_EVENTS
345 /* the number of port mappings changed, we must
346 * inform the subscribers */
347 upnp_event_var_change_notify(EWanIPC);
348 #endif
349 return 0;
354 /* Firewall independant code which call the FW dependant code. */
356 upnp_get_redirection_infos(unsigned short eport, const char * protocol,
357 unsigned short * iport,
358 char * iaddr, int iaddrlen,
359 char * desc, int desclen,
360 char * rhost, int rhostlen,
361 unsigned int * leaseduration)
363 int r;
364 unsigned int timestamp;
365 time_t current_time;
367 if(desc && (desclen > 0))
368 desc[0] = '\0';
369 if(rhost && (rhostlen > 0))
370 rhost[0] = '\0';
371 r = get_redirect_rule(ext_if_name, eport, proto_atoi(protocol),
372 iaddr, iaddrlen, iport, desc, desclen,
373 rhost, rhostlen, &timestamp,
374 0, 0);
375 if(r == 0 &&
376 timestamp > 0 &&
377 timestamp > (unsigned int)(current_time = time(NULL))) {
378 *leaseduration = timestamp - current_time;
379 } else {
380 *leaseduration = 0;
382 return r;
386 upnp_get_redirection_infos_by_index(int index,
387 unsigned short * eport, char * protocol,
388 unsigned short * iport,
389 char * iaddr, int iaddrlen,
390 char * desc, int desclen,
391 char * rhost, int rhostlen,
392 unsigned int * leaseduration)
394 /*char ifname[IFNAMSIZ];*/
395 int proto = 0;
396 unsigned int timestamp;
397 time_t current_time;
399 if(desc && (desclen > 0))
400 desc[0] = '\0';
401 if(rhost && (rhostlen > 0))
402 rhost[0] = '\0';
403 if(get_redirect_rule_by_index(index, 0/*ifname*/, eport, iaddr, iaddrlen,
404 iport, &proto, desc, desclen,
405 rhost, rhostlen, &timestamp,
406 0, 0) < 0)
407 return -1;
408 else
410 current_time = time(NULL);
411 *leaseduration = (timestamp > (unsigned int)current_time)
412 ? (timestamp - current_time)
413 : 0;
414 if(proto == IPPROTO_TCP)
415 memcpy(protocol, "TCP", 4);
416 else
417 memcpy(protocol, "UDP", 4);
418 return 0;
422 /* called from natpmp.c too */
424 _upnp_delete_redir(unsigned short eport, int proto)
426 int r;
427 #if defined(__linux__)
428 r = delete_redirect_and_filter_rules(eport, proto);
429 #elif defined(USE_PF)
430 r = delete_redirect_and_filter_rules(ext_if_name, eport, proto);
431 #else
432 r = delete_redirect_rule(ext_if_name, eport, proto);
433 delete_filter_rule(ext_if_name, eport, proto);
434 #endif
435 #ifdef ENABLE_LEASEFILE
436 lease_file_remove( eport, proto);
437 #endif
439 #ifdef ENABLE_EVENTS
440 upnp_event_var_change_notify(EWanIPC);
441 #endif
442 return r;
446 upnp_delete_redirection(unsigned short eport, const char * protocol)
448 syslog(LOG_INFO, "removing redirect rule port %hu %s", eport, protocol);
449 return _upnp_delete_redir(eport, proto_atoi(protocol));
452 /* upnp_get_portmapping_number_of_entries()
453 * TODO: improve this code. */
455 upnp_get_portmapping_number_of_entries()
457 int n = 0, r = 0;
458 unsigned short eport, iport;
459 char protocol[4], iaddr[32], desc[64], rhost[32];
460 unsigned int leaseduration;
461 do {
462 protocol[0] = '\0'; iaddr[0] = '\0'; desc[0] = '\0';
463 r = upnp_get_redirection_infos_by_index(n, &eport, protocol, &iport,
464 iaddr, sizeof(iaddr),
465 desc, sizeof(desc),
466 rhost, sizeof(rhost),
467 &leaseduration);
468 n++;
469 } while(r==0);
470 return (n-1);
473 /* functions used to remove unused rules
474 * As a side effect, delete expired rules (based on LeaseDuration) */
475 struct rule_state *
476 get_upnp_rules_state_list(int max_rules_number_target)
478 /*char ifname[IFNAMSIZ];*/
479 int proto;
480 unsigned short iport;
481 unsigned int timestamp;
482 struct rule_state * tmp;
483 struct rule_state * list = 0;
484 struct rule_state * * p;
485 int i = 0;
486 time_t current_time;
488 /*ifname[0] = '\0';*/
489 tmp = malloc(sizeof(struct rule_state));
490 if(!tmp)
491 return 0;
492 current_time = time(NULL);
493 nextruletoclean_timestamp = 0;
494 while(get_redirect_rule_by_index(i, /*ifname*/0, &tmp->eport, 0, 0,
495 &iport, &proto, 0, 0, 0,0, &timestamp,
496 &tmp->packets, &tmp->bytes) >= 0)
498 tmp->to_remove = 0;
499 if(timestamp > 0) {
500 /* need to remove this port mapping ? */
501 if(timestamp <= (unsigned int)current_time)
502 tmp->to_remove = 1;
503 else if((nextruletoclean_timestamp <= (unsigned int)current_time)
504 || (timestamp < nextruletoclean_timestamp))
505 nextruletoclean_timestamp = timestamp;
507 tmp->proto = (short)proto;
508 /* add tmp to list */
509 tmp->next = list;
510 list = tmp;
511 /* prepare next iteration */
512 i++;
513 tmp = malloc(sizeof(struct rule_state));
514 if(!tmp)
515 break;
517 #ifdef PCP_PEER
518 i=0;
519 while(get_peer_rule_by_index(i, /*ifname*/0, &tmp->eport, 0, 0,
520 &iport, &proto, 0, 0, 0,0,0, &timestamp,
521 &tmp->packets, &tmp->bytes) >= 0)
523 tmp->to_remove = 0;
524 if(timestamp > 0) {
525 /* need to remove this port mapping ? */
526 if(timestamp <= (unsigned int)current_time)
527 tmp->to_remove = 1;
528 else if((nextruletoclean_timestamp <= (unsigned int)current_time)
529 || (timestamp < nextruletoclean_timestamp))
530 nextruletoclean_timestamp = timestamp;
532 tmp->proto = (short)proto;
533 /* add tmp to list */
534 tmp->next = list;
535 list = tmp;
536 /* prepare next iteration */
537 i++;
538 tmp = malloc(sizeof(struct rule_state));
539 if(!tmp)
540 break;
542 #endif
543 free(tmp);
544 /* remove the redirections that need to be removed */
545 for(p = &list, tmp = list; tmp; tmp = *p)
547 if(tmp->to_remove)
549 syslog(LOG_NOTICE, "remove port mapping %hu %s because it has expired",
550 tmp->eport, (tmp->proto==IPPROTO_TCP)?"TCP":"UDP");
551 _upnp_delete_redir(tmp->eport, tmp->proto);
552 *p = tmp->next;
553 free(tmp);
554 i--;
555 } else {
556 p = &(tmp->next);
559 /* return empty list if not enough redirections */
560 if(i<=max_rules_number_target)
561 while(list)
563 tmp = list;
564 list = tmp->next;
565 free(tmp);
567 /* return list */
568 return list;
571 void
572 remove_unused_rules(struct rule_state * list)
574 char ifname[IFNAMSIZ];
575 unsigned short iport;
576 struct rule_state * tmp;
577 u_int64_t packets;
578 u_int64_t bytes;
579 unsigned int timestamp;
580 int n = 0;
582 while(list)
584 /* remove the rule if no traffic has used it */
585 if(get_redirect_rule(ifname, list->eport, list->proto,
586 0, 0, &iport, 0, 0, 0, 0, &timestamp,
587 &packets, &bytes) >= 0)
589 if(packets == list->packets && bytes == list->bytes)
591 _upnp_delete_redir(list->eport, list->proto);
592 n++;
595 tmp = list;
596 list = tmp->next;
597 free(tmp);
599 if(n>0)
600 syslog(LOG_NOTICE, "removed %d unused rules", n);
603 /* upnp_get_portmappings_in_range()
604 * return a list of all "external" ports for which a port
605 * mapping exists */
606 unsigned short *
607 upnp_get_portmappings_in_range(unsigned short startport,
608 unsigned short endport,
609 const char * protocol,
610 unsigned int * number)
612 int proto;
613 proto = proto_atoi(protocol);
614 if(!number)
615 return NULL;
616 return get_portmappings_in_range(startport, endport, proto, number);
619 /* stuff for miniupnpdctl */
620 #ifdef USE_MINIUPNPDCTL
621 void
622 write_ruleset_details(int s)
624 int proto = 0;
625 unsigned short eport, iport;
626 char desc[64];
627 char iaddr[32];
628 char rhost[32];
629 unsigned int timestamp;
630 u_int64_t packets;
631 u_int64_t bytes;
632 int i = 0;
633 char buffer[256];
634 int n;
636 write(s, "Ruleset :\n", 10);
637 while(get_redirect_rule_by_index(i, 0/*ifname*/, &eport, iaddr, sizeof(iaddr),
638 &iport, &proto, desc, sizeof(desc),
639 rhost, sizeof(rhost),
640 &timestamp,
641 &packets, &bytes) >= 0)
643 n = snprintf(buffer, sizeof(buffer),
644 "%2d %s %s:%hu->%s:%hu "
645 "'%s' %u %" PRIu64 " %" PRIu64 "\n",
646 /*"'%s' %llu %llu\n",*/
647 i, proto==IPPROTO_TCP?"TCP":"UDP", rhost,
648 eport, iaddr, iport, desc, timestamp, packets, bytes);
649 write(s, buffer, n);
650 i++;
653 #endif