1 /* $Id: upnpredirect.c,v 1.91 2016/02/16 12:15:02 nanard Exp $ */
2 /* vim: tabstop=4 shiftwidth=4 noexpandtab
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 */
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <netinet/in.h>
16 #include <arpa/inet.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"
32 #include "pf/obsdrdr.h"
35 #include "ipf/ipfrdr.h"
38 #include "ipfw/ipfwrdr.h"
40 #ifdef USE_MINIUPNPDCTL
44 #ifdef ENABLE_LEASEFILE
48 /* from <inttypes.h> */
54 * convert the string "UDP" or "TCP" to IPPROTO_UDP and IPPROTO_UDP */
56 proto_atoi(const char * protocol
)
58 int proto
= IPPROTO_TCP
;
59 if(strcasecmp(protocol
, "UDP") == 0)
61 #ifdef IPPROTO_UDPLITE
62 else if(strcasecmp(protocol
, "UDPLITE") == 0)
63 proto
= IPPROTO_UDPLITE
;
64 #endif /* IPPROTO_UDPLITE */
69 * convert IPPROTO_UDP, IPPROTO_UDP, etc. to "UDP", "TCP" */
73 const char * protocol
;
81 #ifdef IPPROTO_UDPLITE
85 #endif /* IPPROTO_UDPLITE */
87 protocol
= "*UNKNOWN*";
92 #ifdef ENABLE_LEASEFILE
94 lease_file_add(unsigned short eport
,
99 unsigned int timestamp
)
103 if (lease_file
== NULL
) return 0;
105 fd
= fopen( lease_file
, "a");
107 syslog(LOG_ERR
, "could not open lease file: %s", lease_file
);
111 fprintf(fd
, "%s:%hu:%s:%hu:%u:%s\n",
112 proto_itoa(proto
), eport
, iaddr
, iport
,
120 lease_file_remove(unsigned short eport
, int proto
)
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");
137 strncpy( tmpfilename
, lease_file
, sizeof(tmpfilename
) );
138 strncat( tmpfilename
, "XXXXXX", sizeof(tmpfilename
) - strlen(tmpfilename
));
140 fd
= fopen( lease_file
, "r");
145 snprintf( str
, sizeof(str
), "%s:%u", proto_itoa(proto
), eport
);
146 str_size
= strlen(str
);
148 tmp
= mkstemp(tmpfilename
);
151 syslog(LOG_ERR
, "could not open temporary lease file");
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
);
168 if (rename(tmpfilename
, lease_file
) < 0) {
169 syslog(LOG_ERR
, "could not rename temporary lease file to %s", lease_file
);
177 /* reload_from_lease_file()
178 * read lease_file and add the rules contained
180 int reload_from_lease_file()
184 unsigned short eport
, iport
;
189 unsigned int leaseduration
;
190 unsigned int timestamp
;
195 if(!lease_file
) return -1;
196 fd
= fopen( lease_file
, "r");
198 syslog(LOG_ERR
, "could not open lease file: %s", lease_file
);
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
);
209 p
= strchr(line
, ':');
211 syslog(LOG_ERR
, "unrecognized data in lease file");
215 iaddr
= strchr(p
, ':');
217 syslog(LOG_ERR
, "unrecognized data in lease file");
221 eport
= (unsigned short)atoi(p
);
222 p
= strchr(iaddr
, ':');
224 syslog(LOG_ERR
, "unrecognized data in lease file");
228 iport
= (unsigned short)atoi(p
);
231 syslog(LOG_ERR
, "unrecognized data in lease file");
235 desc
= strchr(p
, ':');
237 syslog(LOG_ERR
, "unrecognized data in lease file");
241 /*timestamp = (unsigned int)atoi(p);*/
242 timestamp
= (unsigned int)strtoul(p
, NULL
, 10);
243 /* trim description */
244 while(isspace(*desc
))
249 while(isspace(*p
) && (p
> desc
))
253 if(timestamp
<= (unsigned int)current_time
) {
254 syslog(LOG_NOTICE
, "already expired lease in lease file");
257 leaseduration
= timestamp
- current_time
;
260 leaseduration
= 0; /* default value */
263 r
= upnp_redirect(rhost
, eport
, iaddr
, iport
, proto
, desc
, leaseduration
);
265 syslog(LOG_ERR
, "Failed to redirect %hu -> %s:%hu protocol %s",
266 eport
, iaddr
, iport
, proto
);
268 /* Add the redirection again to the lease file */
269 lease_file_add(eport
, iaddr
, iport
, proto_atoi(proto
),
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
)
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
);
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
);
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
339 * = = ≠= Failure or success
342 * = = = = Success (overwrite)
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
),
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
);
360 r
= update_portmapping_desc_timestamp(ext_if_name
, eport
, proto
, desc
, timestamp
);
362 #ifdef ENABLE_LEASEFILE
364 lease_file_remove(eport
, proto
);
365 lease_file_add(eport
, iaddr
, iport
, proto
, desc
, timestamp
);
367 #endif /* ENABLE_LEASEFILE */
370 syslog(LOG_INFO
, "port %hu %s (rhost '%s') already redirected to %s:%hu",
371 eport
, protocol
, rhost_old
, iaddr_old
, iport_old
);
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",
379 #endif /* CHECK_PORTINUSE */
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
,
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) {
402 #ifdef ENABLE_LEASEFILE
403 lease_file_add( eport
, iaddr
, iport
, proto
, desc
, timestamp
);
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
);
415 if(!nextruletoclean_timestamp
|| (timestamp
< nextruletoclean_timestamp
))
416 nextruletoclean_timestamp
= timestamp
;
419 /* the number of port mappings changed, we must
420 * inform the subscribers */
421 upnp_event_var_change_notify(EWanIPC
);
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
)
438 unsigned int timestamp
;
441 if(desc
&& (desclen
> 0))
443 if(rhost
&& (rhostlen
> 0))
445 r
= get_redirect_rule(ext_if_name
, eport
, proto_atoi(protocol
),
446 iaddr
, iaddrlen
, iport
, desc
, desclen
,
447 rhost
, rhostlen
, ×tamp
,
451 timestamp
> (unsigned int)(current_time
= time(NULL
))) {
452 *leaseduration
= timestamp
- current_time
;
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];*/
470 unsigned int timestamp
;
473 if(desc
&& (desclen
> 0))
475 if(rhost
&& (rhostlen
> 0))
477 if(get_redirect_rule_by_index(index
, 0/*ifname*/, eport
, iaddr
, iaddrlen
,
478 iport
, &proto
, desc
, desclen
,
479 rhost
, rhostlen
, ×tamp
,
484 current_time
= time(NULL
);
485 *leaseduration
= (timestamp
> (unsigned int)current_time
)
486 ? (timestamp
- current_time
)
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 */
495 memcpy(protocol
, "UDP", 4);
500 /* called from natpmp.c too */
502 _upnp_delete_redir(unsigned short eport
, int proto
)
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
);
510 r
= delete_redirect_rule(ext_if_name
, eport
, proto
);
511 delete_filter_rule(ext_if_name
, eport
, proto
);
513 #ifdef ENABLE_LEASEFILE
514 lease_file_remove( eport
, proto
);
518 upnp_event_var_change_notify(EWanIPC
);
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()
536 unsigned short eport
, iport
;
537 char protocol
[4], iaddr
[32], desc
[64], rhost
[32];
538 unsigned int leaseduration
;
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
),
544 rhost
, sizeof(rhost
),
551 /* functions used to remove unused rules
552 * As a side effect, delete expired rules (based on LeaseDuration) */
554 get_upnp_rules_state_list(int max_rules_number_target
)
556 /*char ifname[IFNAMSIZ];*/
558 unsigned short iport
;
559 unsigned int timestamp
;
560 struct rule_state
* tmp
;
561 struct rule_state
* list
= 0;
562 struct rule_state
* * p
;
566 /*ifname[0] = '\0';*/
567 tmp
= malloc(sizeof(struct rule_state
));
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, ×tamp
,
574 &tmp
->packets
, &tmp
->bytes
) >= 0)
578 /* need to remove this port mapping ? */
579 if(timestamp
<= (unsigned int)current_time
)
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 */
589 /* prepare next iteration */
591 tmp
= malloc(sizeof(struct rule_state
));
597 while(get_peer_rule_by_index(i
, /*ifname*/0, &tmp
->eport
, 0, 0,
598 &iport
, &proto
, 0, 0, 0,0,0, ×tamp
,
599 &tmp
->packets
, &tmp
->bytes
) >= 0)
603 /* need to remove this port mapping ? */
604 if(timestamp
<= (unsigned int)current_time
)
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 */
614 /* prepare next iteration */
616 tmp
= malloc(sizeof(struct rule_state
));
622 /* remove the redirections that need to be removed */
623 for(p
= &list
, tmp
= list
; tmp
; tmp
= *p
)
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
);
637 /* return empty list if not enough redirections */
638 if(i
<=max_rules_number_target
)
650 remove_unused_rules(struct rule_state
* list
)
652 char ifname
[IFNAMSIZ
];
653 unsigned short iport
;
654 struct rule_state
* tmp
;
657 unsigned int timestamp
;
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, ×tamp
,
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
),
673 _upnp_delete_redir(list
->eport
, list
->proto
);
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
689 upnp_get_portmappings_in_range(unsigned short startport
,
690 unsigned short endport
,
691 const char * protocol
,
692 unsigned int * number
)
695 proto
= proto_atoi(protocol
);
698 return get_portmappings_in_range(startport
, endport
, proto
, number
);
701 /* stuff for miniupnpdctl */
702 #ifdef USE_MINIUPNPDCTL
704 write_ruleset_details(int s
)
707 unsigned short eport
, iport
;
711 unsigned int timestamp
;
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
),
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
);