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