1: /* $Id: upnpredirect.c,v 1.1.1.3 2013/07/22 00:32:35 misho 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 */
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:
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
45:
46: /* from <inttypes.h> */
47: #ifndef PRIu64
48: #define PRIu64 "llu"
49: #endif
50:
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)
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
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)
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:
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);
85:
86: return 0;
87: }
88:
89: static int
90: lease_file_remove(unsigned short eport, int proto)
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;
128: while( fgets(buf, sizeof(buf)-1, fd) != NULL) {
129: buf_size = strlen(buf);
130:
131: if (buf_size < str_size || strncmp(str, buf, str_size)!=0) {
132: fwrite(buf, buf_size, 1, fdt);
133: }
134: }
135: fclose(fdt);
136: fclose(fd);
137:
138: if (rename(tmpfilename, lease_file) < 0) {
139: syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file);
140: remove(tmpfilename);
141: }
142:
143: return 0;
144:
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;
158: char * rhost;
159: unsigned int leaseduration;
160: unsigned int timestamp;
161: time_t current_time;
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:
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;
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';
198: iport = (unsigned short)atoi(p);
199: p = strchr(p, ':');
200: if(!p) {
201: syslog(LOG_ERR, "unrecognized data in lease file");
202: continue;
203: }
204: *(p++) = '\0';
205: desc = strchr(p, ':');
206: if(!desc) {
207: syslog(LOG_ERR, "unrecognized data in lease file");
208: continue;
209: }
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';
221:
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;
228: }
229: } else {
230: leaseduration = 0; /* default value */
231: }
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);
241: }
242: }
243: fclose(fd);
244:
245: return 0;
246: }
247: #endif
248:
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
256: */
257: int
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)
262: {
263: int proto, r;
264: char iaddr_old[32];
265: unsigned short iport_old;
266: struct in_addr address;
267: unsigned int timestamp;
268:
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,
282: iaddr_old, sizeof(iaddr_old), &iport_old, 0, 0,
283: 0, 0,
284: ×tamp, 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: 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 {
298: timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0;
299: syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
300: eport, iaddr, iport, protocol, desc);
301: return upnp_redirect_internal(rhost, eport, iaddr, iport, proto,
302: desc, timestamp);
303: }
304:
305: return 0;
306: }
307:
308: int
309: upnp_redirect_internal(const char * rhost, unsigned short eport,
310: const char * iaddr, unsigned short iport,
311: int proto, const char * desc,
312: unsigned int timestamp)
313: {
314: /*syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
315: eport, iaddr, iport, protocol, desc); */
316: if(add_redirect_rule2(ext_if_name, rhost, eport, iaddr, iport, proto,
317: desc, timestamp) < 0) {
318: return -1;
319: }
320:
321: #ifdef ENABLE_LEASEFILE
322: lease_file_add( eport, iaddr, iport, proto, desc, timestamp);
323: #endif
324: /* syslog(LOG_INFO, "creating pass rule to %s:%hu protocol %s for: %s",
325: iaddr, iport, protocol, desc);*/
326: if(add_filter_rule2(ext_if_name, rhost, iaddr, eport, iport, proto, desc) < 0) {
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: }
333: if(timestamp > 0) {
334: if(!nextruletoclean_timestamp || (timestamp < nextruletoclean_timestamp))
335: nextruletoclean_timestamp = timestamp;
336: }
337: #ifdef ENABLE_EVENTS
338: /* the number of port mappings changed, we must
339: * inform the subscribers */
340: upnp_event_var_change_notify(EWanIPC);
341: #endif
342: return 0;
343: }
344:
345:
346:
347: /* Firewall independant code which call the FW dependant code. */
348: int
349: upnp_get_redirection_infos(unsigned short eport, const char * protocol,
350: unsigned short * iport,
351: char * iaddr, int iaddrlen,
352: char * desc, int desclen,
353: char * rhost, int rhostlen,
354: unsigned int * leaseduration)
355: {
356: int r;
357: unsigned int timestamp;
358: time_t current_time;
359:
360: if(desc && (desclen > 0))
361: desc[0] = '\0';
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);
368: if(r == 0 &&
369: timestamp > 0 &&
370: timestamp > (unsigned int)(current_time = time(NULL))) {
371: *leaseduration = timestamp - current_time;
372: } else {
373: *leaseduration = 0;
374: }
375: return r;
376: }
377:
378: int
379: upnp_get_redirection_infos_by_index(int index,
380: unsigned short * eport, char * protocol,
381: unsigned short * iport,
382: char * iaddr, int iaddrlen,
383: char * desc, int desclen,
384: char * rhost, int rhostlen,
385: unsigned int * leaseduration)
386: {
387: /*char ifname[IFNAMSIZ];*/
388: int proto = 0;
389: unsigned int timestamp;
390: time_t current_time;
391:
392: if(desc && (desclen > 0))
393: desc[0] = '\0';
394: if(rhost && (rhostlen > 0))
395: rhost[0] = '\0';
396: if(get_redirect_rule_by_index(index, 0/*ifname*/, eport, iaddr, iaddrlen,
397: iport, &proto, desc, desclen,
398: rhost, rhostlen, ×tamp,
399: 0, 0) < 0)
400: return -1;
401: else
402: {
403: current_time = time(NULL);
404: *leaseduration = (timestamp > (unsigned int)current_time)
405: ? (timestamp - current_time)
406: : 0;
407: if(proto == IPPROTO_TCP)
408: memcpy(protocol, "TCP", 4);
409: else
410: memcpy(protocol, "UDP", 4);
411: return 0;
412: }
413: }
414:
415: /* called from natpmp.c too */
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()
444: * TODO: improve this code. */
445: int
446: upnp_get_portmapping_number_of_entries()
447: {
448: int n = 0, r = 0;
449: unsigned short eport, iport;
450: char protocol[4], iaddr[32], desc[64], rhost[32];
451: unsigned int leaseduration;
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),
456: desc, sizeof(desc),
457: rhost, sizeof(rhost),
458: &leaseduration);
459: n++;
460: } while(r==0);
461: return (n-1);
462: }
463:
464: /* functions used to remove unused rules
465: * As a side effect, delete expired rules (based on LeaseDuration) */
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;
472: unsigned int timestamp;
473: struct rule_state * tmp;
474: struct rule_state * list = 0;
475: struct rule_state * * p;
476: int i = 0;
477: time_t current_time;
478:
479: /*ifname[0] = '\0';*/
480: tmp = malloc(sizeof(struct rule_state));
481: if(!tmp)
482: return 0;
483: current_time = time(NULL);
484: nextruletoclean_timestamp = 0;
485: while(get_redirect_rule_by_index(i, /*ifname*/0, &tmp->eport, 0, 0,
486: &iport, &proto, 0, 0, 0,0, ×tamp,
487: &tmp->packets, &tmp->bytes) >= 0)
488: {
489: tmp->to_remove = 0;
490: if(timestamp > 0) {
491: /* need to remove this port mapping ? */
492: if(timestamp <= (unsigned int)current_time)
493: tmp->to_remove = 1;
494: else if((nextruletoclean_timestamp <= (unsigned int)current_time)
495: || (timestamp < nextruletoclean_timestamp))
496: nextruletoclean_timestamp = timestamp;
497: }
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);
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: }
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;
544: unsigned int timestamp;
545: int n = 0;
546:
547: while(list)
548: {
549: /* remove the rule if no traffic has used it */
550: if(get_redirect_rule(ifname, list->eport, list->proto,
551: 0, 0, &iport, 0, 0, 0, 0, ×tamp,
552: &packets, &bytes) >= 0)
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:
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:
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];
593: char rhost[32];
594: unsigned int timestamp;
595: u_int64_t packets;
596: u_int64_t bytes;
597: int i = 0;
598: char buffer[256];
599: int n;
600:
601: write(s, "Ruleset :\n", 10);
602: while(get_redirect_rule_by_index(i, 0/*ifname*/, &eport, iaddr, sizeof(iaddr),
603: &iport, &proto, desc, sizeof(desc),
604: rhost, sizeof(rhost),
605: ×tamp,
606: &packets, &bytes) >= 0)
607: {
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);
614: write(s, buffer, n);
615: i++;
616: }
617: }
618: #endif
619:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>