Annotation of embedaddon/miniupnpd/upnpredirect.c, revision 1.1.1.3
1.1.1.3 ! misho 1: /* $Id: upnpredirect.c,v 1.80 2012/05/01 20:08:22 nanard Exp $ */
1.1 misho 2: /* MiniUPnP project
3: * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
1.1.1.3 ! misho 4: * (c) 2006-2012 Thomas Bernard
1.1 misho 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:
1.1.1.3 ! misho 21: #include "macros.h"
1.1 misho 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
45:
46: /* from <inttypes.h> */
47: #ifndef PRIu64
48: #define PRIu64 "llu"
49: #endif
50:
1.1.1.3 ! misho 51: /* proto_atoi()
1.1 misho 52: * convert the string "UDP" or "TCP" to IPPROTO_UDP and IPPROTO_UDP */
53: static int
54: proto_atoi(const char * protocol)
55: {
56: int proto = IPPROTO_TCP;
57: if(strcmp(protocol, "UDP") == 0)
58: proto = IPPROTO_UDP;
59: return proto;
60: }
61:
62: #ifdef ENABLE_LEASEFILE
1.1.1.2 misho 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)
1.1 misho 70: {
71: FILE * fd;
72:
73: if (lease_file == NULL) return 0;
74:
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;
79: }
80:
1.1.1.2 misho 81: fprintf(fd, "%s:%hu:%s:%hu:%u:%s\n",
82: ((proto==IPPROTO_TCP)?"TCP":"UDP"), eport, iaddr, iport,
83: timestamp, desc);
1.1 misho 84: fclose(fd);
1.1.1.3 ! misho 85:
1.1 misho 86: return 0;
87: }
88:
1.1.1.2 misho 89: static int
90: lease_file_remove(unsigned short eport, int proto)
1.1 misho 91: {
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;
98:
99:
100: if (lease_file == NULL) return 0;
101:
102: if (strlen( lease_file) + 7 > sizeof(tmpfilename)) {
103: syslog(LOG_ERR, "Lease filename is too long");
104: return -1;
105: }
106:
107: strncpy( tmpfilename, lease_file, sizeof(tmpfilename) );
108: strncat( tmpfilename, "XXXXXX", sizeof(tmpfilename) - strlen(tmpfilename));
109:
110: fd = fopen( lease_file, "r");
111: if (fd==NULL) {
112: return 0;
113: }
114:
115: snprintf( str, sizeof(str), "%s:%u", ((proto==IPPROTO_TCP)?"TCP":"UDP"), eport);
116: str_size = strlen(str);
117:
118: tmp = mkstemp(tmpfilename);
119: if (tmp==-1) {
120: fclose(fd);
121: syslog(LOG_ERR, "could not open temporary lease file");
122: return -1;
123: }
124: fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
125: fdt = fdopen(tmp, "a");
126:
127: buf[sizeof(buf)-1] = 0;
1.1.1.2 misho 128: while( fgets(buf, sizeof(buf)-1, fd) != NULL) {
1.1 misho 129: buf_size = strlen(buf);
130:
1.1.1.2 misho 131: if (buf_size < str_size || strncmp(str, buf, str_size)!=0) {
1.1 misho 132: fwrite(buf, buf_size, 1, fdt);
133: }
134: }
135: fclose(fdt);
136: fclose(fd);
1.1.1.3 ! misho 137:
1.1.1.2 misho 138: if (rename(tmpfilename, lease_file) < 0) {
1.1 misho 139: syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file);
1.1.1.2 misho 140: remove(tmpfilename);
1.1 misho 141: }
1.1.1.3 ! misho 142:
1.1 misho 143: return 0;
1.1.1.3 ! misho 144:
1.1 misho 145: }
146:
147: /* reload_from_lease_file()
148: * read lease_file and add the rules contained
149: */
150: int reload_from_lease_file()
151: {
152: FILE * fd;
153: char * p;
154: unsigned short eport, iport;
155: char * proto;
156: char * iaddr;
157: char * desc;
1.1.1.2 misho 158: char * rhost;
159: unsigned int leaseduration;
160: unsigned int timestamp;
161: time_t current_time;
1.1 misho 162: char line[128];
163: int r;
164:
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;
170: }
171: if(unlink(lease_file) < 0) {
172: syslog(LOG_WARNING, "could not unlink file %s : %m", lease_file);
173: }
174:
1.1.1.2 misho 175: current_time = time(NULL);
1.1 misho 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;
183: }
184: *(p++) = '\0';
185: iaddr = strchr(p, ':');
186: if(!iaddr) {
187: syslog(LOG_ERR, "unrecognized data in lease file");
188: continue;
189: }
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;
196: }
197: *(p++) = '\0';
1.1.1.3 ! misho 198: iport = (unsigned short)atoi(p);
1.1.1.2 misho 199: p = strchr(p, ':');
200: if(!p) {
201: syslog(LOG_ERR, "unrecognized data in lease file");
202: continue;
203: }
204: *(p++) = '\0';
1.1 misho 205: desc = strchr(p, ':');
206: if(!desc) {
207: syslog(LOG_ERR, "unrecognized data in lease file");
208: continue;
209: }
210: *(desc++) = '\0';
1.1.1.3 ! misho 211: /*timestamp = (unsigned int)atoi(p);*/
! 212: timestamp = (unsigned int)strtoul(p, NULL, 10);
1.1 misho 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';
221:
1.1.1.2 misho 222: if(timestamp > 0) {
1.1.1.3 ! misho 223: if(timestamp <= (unsigned int)current_time) {
1.1.1.2 misho 224: syslog(LOG_NOTICE, "already expired lease in lease file");
225: continue;
226: } else {
1.1.1.3 ! misho 227: leaseduration = timestamp - current_time;
1.1.1.2 misho 228: }
229: } else {
230: leaseduration = 0; /* default value */
231: }
232: rhost = NULL;
233: r = upnp_redirect(rhost, eport, iaddr, iport, proto, desc, leaseduration);
1.1 misho 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 */
1.1.1.2 misho 239: lease_file_add(eport, iaddr, iport, proto_atoi(proto),
240: desc, timestamp);
1.1 misho 241: }
242: }
243: fclose(fd);
1.1.1.3 ! misho 244:
1.1 misho 245: return 0;
246: }
247: #endif
248:
1.1.1.3 ! misho 249: /* upnp_redirect()
1.1 misho 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
256: */
257: int
1.1.1.3 ! misho 258: upnp_redirect(const char * rhost, unsigned short eport,
1.1 misho 259: const char * iaddr, unsigned short iport,
1.1.1.2 misho 260: const char * protocol, const char * desc,
261: unsigned int leaseduration)
1.1 misho 262: {
263: int proto, r;
264: char iaddr_old[32];
265: unsigned short iport_old;
266: struct in_addr address;
1.1.1.2 misho 267: unsigned int timestamp;
268:
1.1 misho 269: proto = proto_atoi(protocol);
270: if(inet_aton(iaddr, &address) < 0) {
271: syslog(LOG_ERR, "inet_aton(%s) : %m", iaddr);
272: return -1;
273: }
274:
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;
280: }
281: r = get_redirect_rule(ext_if_name, eport, proto,
1.1.1.2 misho 282: iaddr_old, sizeof(iaddr_old), &iport_old, 0, 0,
283: 0, 0,
284: ×tamp, 0, 0);
1.1 misho 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 */
1.1.1.2 misho 289: if(strcmp(iaddr, iaddr_old)==0 && iport==iport_old) {
1.1 misho 290: syslog(LOG_INFO, "ignoring redirect request as it matches existing redirect");
291: } else {
292:
293: syslog(LOG_INFO, "port %hu protocol %s already redirected to %s:%hu",
294: eport, protocol, iaddr_old, iport_old);
295: return -2;
296: }
297: } else {
1.1.1.2 misho 298: timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0;
1.1 misho 299: syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
1.1.1.3 ! misho 300: eport, iaddr, iport, protocol, desc);
1.1.1.2 misho 301: return upnp_redirect_internal(rhost, eport, iaddr, iport, proto,
302: desc, timestamp);
1.1 misho 303: }
304:
305: return 0;
306: }
307:
308: int
1.1.1.2 misho 309: upnp_redirect_internal(const char * rhost, unsigned short eport,
1.1 misho 310: const char * iaddr, unsigned short iport,
1.1.1.2 misho 311: int proto, const char * desc,
312: unsigned int timestamp)
1.1 misho 313: {
314: /*syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
315: eport, iaddr, iport, protocol, desc); */
1.1.1.2 misho 316: if(add_redirect_rule2(ext_if_name, rhost, eport, iaddr, iport, proto,
317: desc, timestamp) < 0) {
1.1 misho 318: return -1;
319: }
320:
321: #ifdef ENABLE_LEASEFILE
1.1.1.2 misho 322: lease_file_add( eport, iaddr, iport, proto, desc, timestamp);
1.1 misho 323: #endif
324: /* syslog(LOG_INFO, "creating pass rule to %s:%hu protocol %s for: %s",
325: iaddr, iport, protocol, desc);*/
1.1.1.2 misho 326: if(add_filter_rule2(ext_if_name, rhost, iaddr, eport, iport, proto, desc) < 0) {
1.1 misho 327: /* clean up the redirect rule */
328: #if !defined(__linux__)
329: delete_redirect_rule(ext_if_name, eport, proto);
330: #endif
331: return -1;
332: }
1.1.1.2 misho 333: if(timestamp > 0) {
334: if(!nextruletoclean_timestamp || (timestamp < nextruletoclean_timestamp))
335: nextruletoclean_timestamp = timestamp;
336: }
1.1 misho 337: #ifdef ENABLE_EVENTS
1.1.1.2 misho 338: /* the number of port mappings changed, we must
339: * inform the subscribers */
1.1 misho 340: upnp_event_var_change_notify(EWanIPC);
341: #endif
342: return 0;
343: }
344:
345:
346:
1.1.1.2 misho 347: /* Firewall independant code which call the FW dependant code. */
1.1 misho 348: int
349: upnp_get_redirection_infos(unsigned short eport, const char * protocol,
350: unsigned short * iport,
351: char * iaddr, int iaddrlen,
1.1.1.2 misho 352: char * desc, int desclen,
353: char * rhost, int rhostlen,
354: unsigned int * leaseduration)
1.1 misho 355: {
1.1.1.2 misho 356: int r;
357: unsigned int timestamp;
358: time_t current_time;
359:
1.1 misho 360: if(desc && (desclen > 0))
361: desc[0] = '\0';
1.1.1.2 misho 362: if(rhost && (rhostlen > 0))
363: rhost[0] = '\0';
364: r = get_redirect_rule(ext_if_name, eport, proto_atoi(protocol),
365: iaddr, iaddrlen, iport, desc, desclen,
366: rhost, rhostlen, ×tamp,
367: 0, 0);
1.1.1.3 ! misho 368: if(r == 0 &&
! 369: timestamp > 0 &&
! 370: timestamp > (unsigned int)(current_time = time(NULL))) {
1.1.1.2 misho 371: *leaseduration = timestamp - current_time;
372: } else {
373: *leaseduration = 0;
374: }
375: return r;
1.1 misho 376: }
377:
378: int
379: upnp_get_redirection_infos_by_index(int index,
380: unsigned short * eport, char * protocol,
1.1.1.3 ! misho 381: unsigned short * iport,
1.1 misho 382: char * iaddr, int iaddrlen,
1.1.1.2 misho 383: char * desc, int desclen,
384: char * rhost, int rhostlen,
385: unsigned int * leaseduration)
1.1 misho 386: {
387: /*char ifname[IFNAMSIZ];*/
388: int proto = 0;
1.1.1.2 misho 389: unsigned int timestamp;
390: time_t current_time;
1.1 misho 391:
392: if(desc && (desclen > 0))
393: desc[0] = '\0';
1.1.1.3 ! misho 394: if(rhost && (rhostlen > 0))
1.1.1.2 misho 395: rhost[0] = '\0';
1.1 misho 396: if(get_redirect_rule_by_index(index, 0/*ifname*/, eport, iaddr, iaddrlen,
1.1.1.2 misho 397: iport, &proto, desc, desclen,
398: rhost, rhostlen, ×tamp,
399: 0, 0) < 0)
1.1 misho 400: return -1;
401: else
402: {
1.1.1.2 misho 403: current_time = time(NULL);
1.1.1.3 ! misho 404: *leaseduration = (timestamp > (unsigned int)current_time)
1.1.1.2 misho 405: ? (timestamp - current_time)
406: : 0;
1.1 misho 407: if(proto == IPPROTO_TCP)
408: memcpy(protocol, "TCP", 4);
409: else
410: memcpy(protocol, "UDP", 4);
411: return 0;
412: }
413: }
414:
1.1.1.2 misho 415: /* called from natpmp.c too */
1.1 misho 416: int
417: _upnp_delete_redir(unsigned short eport, int proto)
418: {
419: int r;
420: #if defined(__linux__)
421: r = delete_redirect_and_filter_rules(eport, proto);
422: #else
423: r = delete_redirect_rule(ext_if_name, eport, proto);
424: delete_filter_rule(ext_if_name, eport, proto);
425: #endif
426: #ifdef ENABLE_LEASEFILE
427: lease_file_remove( eport, proto);
428: #endif
429:
430: #ifdef ENABLE_EVENTS
431: upnp_event_var_change_notify(EWanIPC);
432: #endif
433: return r;
434: }
435:
436: int
437: upnp_delete_redirection(unsigned short eport, const char * protocol)
438: {
439: syslog(LOG_INFO, "removing redirect rule port %hu %s", eport, protocol);
440: return _upnp_delete_redir(eport, proto_atoi(protocol));
441: }
442:
443: /* upnp_get_portmapping_number_of_entries()
1.1.1.2 misho 444: * TODO: improve this code. */
1.1 misho 445: int
446: upnp_get_portmapping_number_of_entries()
447: {
448: int n = 0, r = 0;
449: unsigned short eport, iport;
1.1.1.2 misho 450: char protocol[4], iaddr[32], desc[64], rhost[32];
451: unsigned int leaseduration;
1.1 misho 452: do {
453: protocol[0] = '\0'; iaddr[0] = '\0'; desc[0] = '\0';
454: r = upnp_get_redirection_infos_by_index(n, &eport, protocol, &iport,
455: iaddr, sizeof(iaddr),
1.1.1.2 misho 456: desc, sizeof(desc),
457: rhost, sizeof(rhost),
458: &leaseduration);
1.1 misho 459: n++;
460: } while(r==0);
461: return (n-1);
462: }
463:
1.1.1.2 misho 464: /* functions used to remove unused rules
465: * As a side effect, delete expired rules (based on LeaseDuration) */
1.1 misho 466: struct rule_state *
467: get_upnp_rules_state_list(int max_rules_number_target)
468: {
469: /*char ifname[IFNAMSIZ];*/
470: int proto;
471: unsigned short iport;
1.1.1.2 misho 472: unsigned int timestamp;
1.1 misho 473: struct rule_state * tmp;
474: struct rule_state * list = 0;
1.1.1.2 misho 475: struct rule_state * * p;
1.1 misho 476: int i = 0;
1.1.1.2 misho 477: time_t current_time;
478:
1.1 misho 479: /*ifname[0] = '\0';*/
480: tmp = malloc(sizeof(struct rule_state));
481: if(!tmp)
482: return 0;
1.1.1.2 misho 483: current_time = time(NULL);
484: nextruletoclean_timestamp = 0;
1.1 misho 485: while(get_redirect_rule_by_index(i, /*ifname*/0, &tmp->eport, 0, 0,
1.1.1.2 misho 486: &iport, &proto, 0, 0, 0,0, ×tamp,
1.1 misho 487: &tmp->packets, &tmp->bytes) >= 0)
488: {
1.1.1.2 misho 489: tmp->to_remove = 0;
490: if(timestamp > 0) {
491: /* need to remove this port mapping ? */
1.1.1.3 ! misho 492: if(timestamp <= (unsigned int)current_time)
1.1.1.2 misho 493: tmp->to_remove = 1;
1.1.1.3 ! misho 494: else if((nextruletoclean_timestamp <= (unsigned int)current_time)
1.1.1.2 misho 495: || (timestamp < nextruletoclean_timestamp))
496: nextruletoclean_timestamp = timestamp;
497: }
1.1 misho 498: tmp->proto = (short)proto;
499: /* add tmp to list */
500: tmp->next = list;
501: list = tmp;
502: /* prepare next iteration */
503: i++;
504: tmp = malloc(sizeof(struct rule_state));
505: if(!tmp)
506: break;
507: }
508: free(tmp);
1.1.1.2 misho 509: /* remove the redirections that need to be removed */
510: for(p = &list, tmp = list; tmp; tmp = *p)
511: {
512: if(tmp->to_remove)
513: {
514: syslog(LOG_NOTICE, "remove port mapping %hu %s because it has expired",
515: tmp->eport, (tmp->proto==IPPROTO_TCP)?"TCP":"UDP");
516: _upnp_delete_redir(tmp->eport, tmp->proto);
517: *p = tmp->next;
518: free(tmp);
519: i--;
520: } else {
521: p = &(tmp->next);
522: }
523: }
1.1 misho 524: /* return empty list if not enough redirections */
525: if(i<=max_rules_number_target)
526: while(list)
527: {
528: tmp = list;
529: list = tmp->next;
530: free(tmp);
531: }
532: /* return list */
533: return list;
534: }
535:
536: void
537: remove_unused_rules(struct rule_state * list)
538: {
539: char ifname[IFNAMSIZ];
540: unsigned short iport;
541: struct rule_state * tmp;
542: u_int64_t packets;
543: u_int64_t bytes;
1.1.1.2 misho 544: unsigned int timestamp;
1.1 misho 545: int n = 0;
1.1.1.2 misho 546:
1.1 misho 547: while(list)
548: {
549: /* remove the rule if no traffic has used it */
550: if(get_redirect_rule(ifname, list->eport, list->proto,
1.1.1.2 misho 551: 0, 0, &iport, 0, 0, 0, 0, ×tamp,
552: &packets, &bytes) >= 0)
1.1 misho 553: {
554: if(packets == list->packets && bytes == list->bytes)
555: {
556: _upnp_delete_redir(list->eport, list->proto);
557: n++;
558: }
559: }
560: tmp = list;
561: list = tmp->next;
562: free(tmp);
563: }
564: if(n>0)
565: syslog(LOG_NOTICE, "removed %d unused rules", n);
566: }
567:
1.1.1.2 misho 568: /* upnp_get_portmappings_in_range()
569: * return a list of all "external" ports for which a port
570: * mapping exists */
571: unsigned short *
572: upnp_get_portmappings_in_range(unsigned short startport,
573: unsigned short endport,
574: const char * protocol,
575: unsigned int * number)
576: {
577: int proto;
578: proto = proto_atoi(protocol);
579: if(!number)
580: return NULL;
581: return get_portmappings_in_range(startport, endport, proto, number);
582: }
583:
1.1 misho 584: /* stuff for miniupnpdctl */
585: #ifdef USE_MINIUPNPDCTL
586: void
587: write_ruleset_details(int s)
588: {
589: int proto = 0;
590: unsigned short eport, iport;
591: char desc[64];
592: char iaddr[32];
1.1.1.2 misho 593: char rhost[32];
594: unsigned int timestamp;
1.1 misho 595: u_int64_t packets;
596: u_int64_t bytes;
597: int i = 0;
598: char buffer[256];
599: int n;
1.1.1.2 misho 600:
1.1 misho 601: write(s, "Ruleset :\n", 10);
1.1.1.2 misho 602: while(get_redirect_rule_by_index(i, 0/*ifname*/, &eport, iaddr, sizeof(iaddr),
1.1 misho 603: &iport, &proto, desc, sizeof(desc),
1.1.1.2 misho 604: rhost, sizeof(rhost),
605: ×tamp,
1.1 misho 606: &packets, &bytes) >= 0)
607: {
1.1.1.2 misho 608: n = snprintf(buffer, sizeof(buffer),
609: "%2d %s %s:%hu->%s:%hu "
610: "'%s' %u %" PRIu64 " %" PRIu64 "\n",
611: /*"'%s' %llu %llu\n",*/
612: i, proto==IPPROTO_TCP?"TCP":"UDP", rhost,
613: eport, iaddr, iport, desc, timestamp, packets, bytes);
1.1 misho 614: write(s, buffer, n);
615: i++;
616: }
617: }
618: #endif
619:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>