Miniupnpd ver 1.7 (20121005)
[tomato.git] / release / src / router / miniupnpd / upnpredirect.c
blob4e9228e8cb74bded3d35a91d8f846c30c3018f36
1 /* $Id: upnpredirect.c,v 1.80 2012/05/01 20:08:22 nanard Exp $ */
2 /* MiniUPnP project
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2006-2012 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 #if defined(USE_NETFILTER)
27 #include "netfilter/iptcrdr.h"
28 #endif
29 #if defined(USE_PF)
30 #include "pf/obsdrdr.h"
31 #endif
32 #if defined(USE_IPF)
33 #include "ipf/ipfrdr.h"
34 #endif
35 #if defined(USE_IPFW)
36 #include "ipfw/ipfwrdr.h"
37 #endif
38 #ifdef USE_MINIUPNPDCTL
39 #include <stdio.h>
40 #include <unistd.h>
41 #endif
42 #ifdef ENABLE_LEASEFILE
43 #include <sys/stat.h>
44 #endif
46 /* from <inttypes.h> */
47 #ifndef PRIu64
48 #define PRIu64 "llu"
49 #endif
51 /* proto_atoi()
52 * convert the string "UDP" or "TCP" to IPPROTO_UDP and IPPROTO_UDP */
53 static int
54 proto_atoi(const char * protocol)
56 int proto = IPPROTO_TCP;
57 if(strcmp(protocol, "UDP") == 0)
58 proto = IPPROTO_UDP;
59 return proto;
62 #ifdef ENABLE_LEASEFILE
63 static int
64 lease_file_add(unsigned short eport,
65 const char * iaddr,
66 unsigned short iport,
67 int proto,
68 const char * desc,
69 unsigned int timestamp)
71 FILE * fd;
73 if (lease_file == NULL) return 0;
75 fd = fopen( lease_file, "a");
76 if (fd==NULL) {
77 syslog(LOG_ERR, "could not open lease file: %s", lease_file);
78 return -1;
81 fprintf(fd, "%s:%hu:%s:%hu:%u:%s\n",
82 ((proto==IPPROTO_TCP)?"TCP":"UDP"), eport, iaddr, iport,
83 timestamp, desc);
84 fclose(fd);
86 return 0;
89 static int
90 lease_file_remove(unsigned short eport, int proto)
92 FILE* fd, *fdt;
93 int tmp;
94 char buf[512];
95 char str[32];
96 char tmpfilename[128];
97 int str_size, buf_size;
100 if (lease_file == NULL) return 0;
102 if (strlen( lease_file) + 7 > sizeof(tmpfilename)) {
103 syslog(LOG_ERR, "Lease filename is too long");
104 return -1;
107 strncpy( tmpfilename, lease_file, sizeof(tmpfilename) );
108 strncat( tmpfilename, "XXXXXX", sizeof(tmpfilename) - strlen(tmpfilename));
110 fd = fopen( lease_file, "r");
111 if (fd==NULL) {
112 return 0;
115 snprintf( str, sizeof(str), "%s:%u", ((proto==IPPROTO_TCP)?"TCP":"UDP"), eport);
116 str_size = strlen(str);
118 tmp = mkstemp(tmpfilename);
119 if (tmp==-1) {
120 fclose(fd);
121 syslog(LOG_ERR, "could not open temporary lease file");
122 return -1;
124 fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
125 fdt = fdopen(tmp, "a");
127 buf[sizeof(buf)-1] = 0;
128 while( fgets(buf, sizeof(buf)-1, fd) != NULL) {
129 buf_size = strlen(buf);
131 if (buf_size < str_size || strncmp(str, buf, str_size)!=0) {
132 fwrite(buf, buf_size, 1, fdt);
135 fclose(fdt);
136 fclose(fd);
138 if (rename(tmpfilename, lease_file) < 0) {
139 syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file);
140 remove(tmpfilename);
143 return 0;
147 /* reload_from_lease_file()
148 * read lease_file and add the rules contained
150 int reload_from_lease_file()
152 FILE * fd;
153 char * p;
154 unsigned short eport, iport;
155 char * proto;
156 char * iaddr;
157 char * desc;
158 char * rhost;
159 unsigned int leaseduration;
160 unsigned int timestamp;
161 time_t current_time;
162 char line[128];
163 int r;
165 if(!lease_file) return -1;
166 fd = fopen( lease_file, "r");
167 if (fd==NULL) {
168 syslog(LOG_ERR, "could not open lease file: %s", lease_file);
169 return -1;
171 if(unlink(lease_file) < 0) {
172 syslog(LOG_WARNING, "could not unlink file %s : %m", lease_file);
175 current_time = time(NULL);
176 while(fgets(line, sizeof(line), fd)) {
177 syslog(LOG_DEBUG, "parsing lease file line '%s'", line);
178 proto = line;
179 p = strchr(line, ':');
180 if(!p) {
181 syslog(LOG_ERR, "unrecognized data in lease file");
182 continue;
184 *(p++) = '\0';
185 iaddr = strchr(p, ':');
186 if(!iaddr) {
187 syslog(LOG_ERR, "unrecognized data in lease file");
188 continue;
190 *(iaddr++) = '\0';
191 eport = (unsigned short)atoi(p);
192 p = strchr(iaddr, ':');
193 if(!p) {
194 syslog(LOG_ERR, "unrecognized data in lease file");
195 continue;
197 *(p++) = '\0';
198 iport = (unsigned short)atoi(p);
199 p = strchr(p, ':');
200 if(!p) {
201 syslog(LOG_ERR, "unrecognized data in lease file");
202 continue;
204 *(p++) = '\0';
205 desc = strchr(p, ':');
206 if(!desc) {
207 syslog(LOG_ERR, "unrecognized data in lease file");
208 continue;
210 *(desc++) = '\0';
211 /*timestamp = (unsigned int)atoi(p);*/
212 timestamp = (unsigned int)strtoul(p, NULL, 10);
213 /* trim description */
214 while(isspace(*desc))
215 desc++;
216 p = desc;
217 while(*(p+1))
218 p++;
219 while(isspace(*p) && (p > desc))
220 *(p--) = '\0';
222 if(timestamp > 0) {
223 if(timestamp <= (unsigned int)current_time) {
224 syslog(LOG_NOTICE, "already expired lease in lease file");
225 continue;
226 } else {
227 leaseduration = timestamp - current_time;
229 } else {
230 leaseduration = 0; /* default value */
232 rhost = NULL;
233 r = upnp_redirect(rhost, eport, iaddr, iport, proto, desc, leaseduration);
234 if(r == -1) {
235 syslog(LOG_ERR, "Failed to redirect %hu -> %s:%hu protocol %s",
236 eport, iaddr, iport, proto);
237 } else if(r == -2) {
238 /* Add the redirection again to the lease file */
239 lease_file_add(eport, iaddr, iport, proto_atoi(proto),
240 desc, timestamp);
243 fclose(fd);
245 return 0;
247 #endif
249 /* upnp_redirect()
250 * calls OS/fw dependant implementation of the redirection.
251 * protocol should be the string "TCP" or "UDP"
252 * returns: 0 on success
253 * -1 failed to redirect
254 * -2 already redirected
255 * -3 permission check failed
258 upnp_redirect(const char * rhost, unsigned short eport,
259 const char * iaddr, unsigned short iport,
260 const char * protocol, const char * desc,
261 unsigned int leaseduration)
263 int proto, r;
264 char iaddr_old[32];
265 unsigned short iport_old;
266 struct in_addr address;
267 unsigned int timestamp;
269 proto = proto_atoi(protocol);
270 if(inet_aton(iaddr, &address) < 0) {
271 syslog(LOG_ERR, "inet_aton(%s) : %m", iaddr);
272 return -1;
275 if(!check_upnp_rule_against_permissions(upnppermlist, num_upnpperm,
276 eport, address, iport)) {
277 syslog(LOG_INFO, "redirection permission check failed for "
278 "%hu->%s:%hu %s", eport, iaddr, iport, protocol);
279 return -3;
281 r = get_redirect_rule(ext_if_name, eport, proto,
282 iaddr_old, sizeof(iaddr_old), &iport_old, 0, 0,
283 0, 0,
284 &timestamp, 0, 0);
285 if(r == 0) {
286 /* if existing redirect rule matches redirect request return success
287 * xbox 360 does not keep track of the port it redirects and will
288 * redirect another port when receiving ConflictInMappingEntry */
289 if(strcmp(iaddr, iaddr_old)==0 && iport==iport_old) {
290 /* redirection allready exists */
291 syslog(LOG_INFO, "port %hu %s already redirected to %s:%hu, replacing", eport, (proto==IPPROTO_TCP)?"tcp":"udp", iaddr_old, iport_old);
292 /* remove and then add again */
293 if(_upnp_delete_redir(eport, proto) < 0) {
294 syslog(LOG_ERR, "failed to remove port mapping");
295 return 0;
297 } else {
298 syslog(LOG_INFO, "port %hu protocol %s already redirected to %s:%hu",
299 eport, protocol, iaddr_old, iport_old);
300 return -2;
303 timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0;
304 syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
305 eport, iaddr, iport, protocol, desc);
306 return upnp_redirect_internal(rhost, eport, iaddr, iport, proto,
307 desc, timestamp);
311 upnp_redirect_internal(const char * rhost, unsigned short eport,
312 const char * iaddr, unsigned short iport,
313 int proto, const char * desc,
314 unsigned int timestamp)
316 /*syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
317 eport, iaddr, iport, protocol, desc); */
318 if(add_redirect_rule2(ext_if_name, rhost, eport, iaddr, iport, proto,
319 desc, timestamp) < 0) {
320 return -1;
323 #ifdef ENABLE_LEASEFILE
324 lease_file_add( eport, iaddr, iport, proto, desc, timestamp);
325 #endif
326 /* syslog(LOG_INFO, "creating pass rule to %s:%hu protocol %s for: %s",
327 iaddr, iport, protocol, desc);*/
328 if(add_filter_rule2(ext_if_name, rhost, iaddr, eport, iport, proto, desc) < 0) {
329 /* clean up the redirect rule */
330 #if !defined(__linux__)
331 delete_redirect_rule(ext_if_name, eport, proto);
332 #endif
333 return -1;
335 if(timestamp > 0) {
336 if(!nextruletoclean_timestamp || (timestamp < nextruletoclean_timestamp))
337 nextruletoclean_timestamp = timestamp;
339 #ifdef ENABLE_EVENTS
340 /* the number of port mappings changed, we must
341 * inform the subscribers */
342 upnp_event_var_change_notify(EWanIPC);
343 #endif
344 return 0;
349 /* Firewall independant code which call the FW dependant code. */
351 upnp_get_redirection_infos(unsigned short eport, const char * protocol,
352 unsigned short * iport,
353 char * iaddr, int iaddrlen,
354 char * desc, int desclen,
355 char * rhost, int rhostlen,
356 unsigned int * leaseduration)
358 int r;
359 unsigned int timestamp;
360 time_t current_time;
362 if(desc && (desclen > 0))
363 desc[0] = '\0';
364 if(rhost && (rhostlen > 0))
365 rhost[0] = '\0';
366 r = get_redirect_rule(ext_if_name, eport, proto_atoi(protocol),
367 iaddr, iaddrlen, iport, desc, desclen,
368 rhost, rhostlen, &timestamp,
369 0, 0);
370 if(r == 0 &&
371 timestamp > 0 &&
372 timestamp > (unsigned int)(current_time = time(NULL))) {
373 *leaseduration = timestamp - current_time;
374 } else {
375 *leaseduration = 0;
377 return r;
381 upnp_get_redirection_infos_by_index(int index,
382 unsigned short * eport, char * protocol,
383 unsigned short * iport,
384 char * iaddr, int iaddrlen,
385 char * desc, int desclen,
386 char * rhost, int rhostlen,
387 unsigned int * leaseduration)
389 /*char ifname[IFNAMSIZ];*/
390 int proto = 0;
391 unsigned int timestamp;
392 time_t current_time;
394 if(desc && (desclen > 0))
395 desc[0] = '\0';
396 if(rhost && (rhostlen > 0))
397 rhost[0] = '\0';
398 if(get_redirect_rule_by_index(index, 0/*ifname*/, eport, iaddr, iaddrlen,
399 iport, &proto, desc, desclen,
400 rhost, rhostlen, &timestamp,
401 0, 0) < 0)
402 return -1;
403 else
405 current_time = time(NULL);
406 *leaseduration = (timestamp > (unsigned int)current_time)
407 ? (timestamp - current_time)
408 : 0;
409 if(proto == IPPROTO_TCP)
410 memcpy(protocol, "TCP", 4);
411 else
412 memcpy(protocol, "UDP", 4);
413 return 0;
417 /* called from natpmp.c too */
419 _upnp_delete_redir(unsigned short eport, int proto)
421 int r;
422 #if defined(__linux__)
423 r = delete_redirect_and_filter_rules(eport, proto);
424 #else
425 r = delete_redirect_rule(ext_if_name, eport, proto);
426 delete_filter_rule(ext_if_name, eport, proto);
427 #endif
428 #ifdef ENABLE_LEASEFILE
429 lease_file_remove( eport, proto);
430 #endif
432 #ifdef ENABLE_EVENTS
433 upnp_event_var_change_notify(EWanIPC);
434 #endif
435 return r;
439 upnp_delete_redirection(unsigned short eport, const char * protocol)
441 syslog(LOG_INFO, "removing redirect rule port %hu %s", eport, protocol);
442 return _upnp_delete_redir(eport, proto_atoi(protocol));
445 /* upnp_get_portmapping_number_of_entries()
446 * TODO: improve this code. */
448 upnp_get_portmapping_number_of_entries()
450 int n = 0, r = 0;
451 unsigned short eport, iport;
452 char protocol[4], iaddr[32], desc[64], rhost[32];
453 unsigned int leaseduration;
454 do {
455 protocol[0] = '\0'; iaddr[0] = '\0'; desc[0] = '\0';
456 r = upnp_get_redirection_infos_by_index(n, &eport, protocol, &iport,
457 iaddr, sizeof(iaddr),
458 desc, sizeof(desc),
459 rhost, sizeof(rhost),
460 &leaseduration);
461 n++;
462 } while(r==0);
463 return (n-1);
466 /* functions used to remove unused rules
467 * As a side effect, delete expired rules (based on LeaseDuration) */
468 struct rule_state *
469 get_upnp_rules_state_list(int max_rules_number_target)
471 /*char ifname[IFNAMSIZ];*/
472 int proto;
473 unsigned short iport;
474 unsigned int timestamp;
475 struct rule_state * tmp;
476 struct rule_state * list = 0;
477 struct rule_state * * p;
478 int i = 0;
479 time_t current_time;
481 /*ifname[0] = '\0';*/
482 tmp = malloc(sizeof(struct rule_state));
483 if(!tmp)
484 return 0;
485 current_time = time(NULL);
486 nextruletoclean_timestamp = 0;
487 while(get_redirect_rule_by_index(i, /*ifname*/0, &tmp->eport, 0, 0,
488 &iport, &proto, 0, 0, 0,0, &timestamp,
489 &tmp->packets, &tmp->bytes) >= 0)
491 tmp->to_remove = 0;
492 if(timestamp > 0) {
493 /* need to remove this port mapping ? */
494 if(timestamp <= (unsigned int)current_time)
495 tmp->to_remove = 1;
496 else if((nextruletoclean_timestamp <= (unsigned int)current_time)
497 || (timestamp < nextruletoclean_timestamp))
498 nextruletoclean_timestamp = timestamp;
500 tmp->proto = (short)proto;
501 /* add tmp to list */
502 tmp->next = list;
503 list = tmp;
504 /* prepare next iteration */
505 i++;
506 tmp = malloc(sizeof(struct rule_state));
507 if(!tmp)
508 break;
510 free(tmp);
511 /* remove the redirections that need to be removed */
512 for(p = &list, tmp = list; tmp; tmp = *p)
514 if(tmp->to_remove)
516 syslog(LOG_NOTICE, "remove port mapping %hu %s because it has expired",
517 tmp->eport, (tmp->proto==IPPROTO_TCP)?"TCP":"UDP");
518 _upnp_delete_redir(tmp->eport, tmp->proto);
519 *p = tmp->next;
520 free(tmp);
521 i--;
522 } else {
523 p = &(tmp->next);
526 /* return empty list if not enough redirections */
527 if(i<=max_rules_number_target)
528 while(list)
530 tmp = list;
531 list = tmp->next;
532 free(tmp);
534 /* return list */
535 return list;
538 void
539 remove_unused_rules(struct rule_state * list)
541 char ifname[IFNAMSIZ];
542 unsigned short iport;
543 struct rule_state * tmp;
544 u_int64_t packets;
545 u_int64_t bytes;
546 unsigned int timestamp;
547 int n = 0;
549 while(list)
551 /* remove the rule if no traffic has used it */
552 if(get_redirect_rule(ifname, list->eport, list->proto,
553 0, 0, &iport, 0, 0, 0, 0, &timestamp,
554 &packets, &bytes) >= 0)
556 if(packets == list->packets && bytes == list->bytes)
558 if(_upnp_delete_redir(list->eport, list->proto) >= 0)
559 n++;
562 tmp = list;
563 list = tmp->next;
564 free(tmp);
566 if(n>0)
567 syslog(LOG_NOTICE, "removed %d unused rules", n);
570 /* upnp_get_portmappings_in_range()
571 * return a list of all "external" ports for which a port
572 * mapping exists */
573 unsigned short *
574 upnp_get_portmappings_in_range(unsigned short startport,
575 unsigned short endport,
576 const char * protocol,
577 unsigned int * number)
579 int proto;
580 proto = proto_atoi(protocol);
581 if(!number)
582 return NULL;
583 return get_portmappings_in_range(startport, endport, proto, number);
586 /* stuff for miniupnpdctl */
587 #ifdef USE_MINIUPNPDCTL
588 void
589 write_ruleset_details(int s)
591 int proto = 0;
592 unsigned short eport, iport;
593 char desc[64];
594 char iaddr[32];
595 char rhost[32];
596 unsigned int timestamp;
597 u_int64_t packets;
598 u_int64_t bytes;
599 int i = 0;
600 char buffer[256];
601 int n;
603 write(s, "Ruleset :\n", 10);
604 while(get_redirect_rule_by_index(i, 0/*ifname*/, &eport, iaddr, sizeof(iaddr),
605 &iport, &proto, desc, sizeof(desc),
606 rhost, sizeof(rhost),
607 &timestamp,
608 &packets, &bytes) >= 0)
610 n = snprintf(buffer, sizeof(buffer),
611 "%2d %s %s:%hu->%s:%hu "
612 "'%s' %u %" PRIu64 " %" PRIu64 "\n",
613 /*"'%s' %llu %llu\n",*/
614 i, proto==IPPROTO_TCP?"TCP":"UDP", rhost,
615 eport, iaddr, iport, desc, timestamp, packets, bytes);
616 write(s, buffer, n);
617 i++;
620 #endif