From 74dbad5ab024a345114d109284123a45fc57c4b8 Mon Sep 17 00:00:00 2001 From: Sven Auhagen Date: Tue, 17 Aug 2021 07:19:33 +0000 Subject: [PATCH] IPv6 pinholes lease file This patch adds a lease file for IPv6 pinholes. The leases are maintained and readded when miniupnpd restarts. Currently all IPv6 leases are lost on restart. Signed-off-by: Sven Auhagen --- miniupnpd/miniupnpd.c | 8 + miniupnpd/options.c | 3 + miniupnpd/options.h | 3 + miniupnpd/upnpglobalvars.c | 3 + miniupnpd/upnpglobalvars.h | 3 + miniupnpd/upnppinhole.c | 560 ++++++++++++++++++++++++++++++++++++- miniupnpd/upnppinhole.h | 5 + 7 files changed, 580 insertions(+), 5 deletions(-) diff --git a/miniupnpd/miniupnpd.c b/miniupnpd/miniupnpd.c index 4aa4530..5560a0c 100644 --- a/miniupnpd/miniupnpd.c +++ b/miniupnpd/miniupnpd.c @@ -1441,6 +1441,11 @@ init(int argc, char * * argv, struct runtime_vars * v) case UPNPLEASEFILE: lease_file = ary_options[i].value; break; +#ifdef ENABLE_UPNPPINHOLE + case UPNPLEASEFILE6: + lease_file6 = ary_options[i].value; + break; +#endif /* ENABLE_UPNPPINHOLE */ #endif /* ENABLE_LEASEFILE */ case UPNPMINISSDPDSOCKET: minissdpdsocketpath = ary_options[i].value; @@ -1973,6 +1978,9 @@ init(int argc, char * * argv, struct runtime_vars * v) /*remove(lease_file);*/ syslog(LOG_INFO, "Reloading rules from lease file"); reload_from_lease_file(); +#ifdef ENABLE_UPNPPINHOLE + reload_from_lease_file6(); +#endif #endif #ifdef TOMATO diff --git a/miniupnpd/options.c b/miniupnpd/options.c index 9c61d05..3ebd281 100644 --- a/miniupnpd/options.c +++ b/miniupnpd/options.c @@ -91,6 +91,9 @@ static const struct { #endif #ifdef ENABLE_LEASEFILE { UPNPLEASEFILE, "lease_file"}, +#ifdef ENABLE_UPNPPINHOLE + { UPNPLEASEFILE6, "lease_file6"}, +#endif #endif #ifdef IGD_V2 { UPNPFORCEIGDDESCV1, "force_igd_desc_v1"}, diff --git a/miniupnpd/options.h b/miniupnpd/options.h index b5fd0cc..af4b831 100644 --- a/miniupnpd/options.h +++ b/miniupnpd/options.h @@ -72,6 +72,9 @@ enum upnpconfigoptions { UPNPSECUREMODE, /* secure_mode */ #ifdef ENABLE_LEASEFILE UPNPLEASEFILE, /* lease_file */ +#ifdef ENABLE_UPNPPINHOLE + UPNPLEASEFILE6, /* lease_file v6 */ +#endif #endif UPNPMINISSDPDSOCKET, /* minissdpdsocket */ #ifdef IGD_V2 diff --git a/miniupnpd/upnpglobalvars.c b/miniupnpd/upnpglobalvars.c index e74331d..2e4daa1 100644 --- a/miniupnpd/upnpglobalvars.c +++ b/miniupnpd/upnpglobalvars.c @@ -28,6 +28,9 @@ uint16_t ext_stun_port = 0; /* file to store leases */ #ifdef ENABLE_LEASEFILE const char* lease_file = 0; +#ifdef ENABLE_UPNPPINHOLE +const char* lease_file6 = 0; +#endif #endif /* forced ip address to use for this interface diff --git a/miniupnpd/upnpglobalvars.h b/miniupnpd/upnpglobalvars.h index 0982484..fcdc03e 100644 --- a/miniupnpd/upnpglobalvars.h +++ b/miniupnpd/upnpglobalvars.h @@ -29,6 +29,9 @@ extern uint16_t ext_stun_port; /* file to store all leases */ #ifdef ENABLE_LEASEFILE extern const char * lease_file; +#ifdef ENABLE_UPNPPINHOLE +extern const char * lease_file6; +#endif #endif /* forced ip address to use for this interface diff --git a/miniupnpd/upnppinhole.c b/miniupnpd/upnppinhole.c index 8a49dde..f4e9688 100644 --- a/miniupnpd/upnppinhole.c +++ b/miniupnpd/upnppinhole.c @@ -43,6 +43,9 @@ #endif #if defined(USE_IPFW) #endif +#ifdef ENABLE_LEASEFILE +#include +#endif #ifdef ENABLE_UPNPPINHOLE @@ -89,6 +92,524 @@ upnp_check_outbound_pinhole(int proto, int * timeout) } #endif +#ifdef ENABLE_LEASEFILE +/* proto_atoi() + * convert the string "UDP" or "TCP" to IPPROTO_UDP and IPPROTO_UDP */ +static int +proto_atoi(const char * protocol) +{ + int proto = IPPROTO_TCP; + if(strcasecmp(protocol, "UDP") == 0) + proto = IPPROTO_UDP; +#ifdef IPPROTO_UDPLITE + else if(strcasecmp(protocol, "UDPLITE") == 0) + proto = IPPROTO_UDPLITE; +#endif /* IPPROTO_UDPLITE */ + return proto; +} + +/* proto_itoa() + * convert IPPROTO_UDP, IPPROTO_UDP, etc. to "UDP", "TCP" */ +static const char * +proto_itoa(int proto) +{ + const char * protocol; + switch(proto) { + case IPPROTO_UDP: + protocol = "UDP"; + break; + case IPPROTO_TCP: + protocol = "TCP"; + break; +#ifdef IPPROTO_UDPLITE + case IPPROTO_UDPLITE: + protocol = "UDPLITE"; + break; +#endif /* IPPROTO_UDPLITE */ + default: + protocol = "*UNKNOWN*"; + } + return protocol; +} + +static int +lease_file6_add(const char * rem_client, + unsigned short rem_port, + const char * int_client, + unsigned short int_port, + int proto, + int uid, + const char * desc, + unsigned int leaseduration) +{ + unsigned int timestamp; + FILE * fd; + + if (lease_file6 == NULL) return 0; + + fd = fopen( lease_file6, "a"); + if (fd==NULL) { + syslog(LOG_ERR, "could not open lease file: %s", lease_file); + return -1; + } + + timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0; + + /* convert our time to unix time */ + if (timestamp != 0) { + timestamp -= upnp_time(); + } + + fprintf(fd, "%s;%s;%hu;%s;%hu;%u;%u;%s\n", + proto_itoa(proto), int_client, int_port, rem_client, rem_port, + uid, timestamp, desc); + fclose(fd); + + return 0; +} + +static int +lease_file6_update(int uid, unsigned int leaseduration) +{ + FILE* fd, *fdt; + char * p, * p2; + unsigned short int_port, rem_port; + char * proto; + char * int_client; + char * desc; + char * rem_client; + unsigned int timestamp_rule; + unsigned int timestamp; + char line[128]; + char tmpfilename[128]; + int uid_rule; + int tmp; + + if (lease_file6 == NULL) return 0; + + if (strlen(lease_file6) + 7 > sizeof(tmpfilename)) { + syslog(LOG_ERR, "Lease filename is too long"); + return -1; + } + + snprintf( tmpfilename, sizeof(tmpfilename), "%sXXXXXX", lease_file6); + + fd = fopen( lease_file6, "r"); + if (fd==NULL) { + return 0; + } + + tmp = mkstemp(tmpfilename); + if (tmp==-1) { + fclose(fd); + syslog(LOG_ERR, "could not open temporary lease file"); + return -1; + } + + fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + fdt = fdopen(tmp, "a"); + + timestamp = (leaseduration > 0) ? time(NULL) + leaseduration : 0; + + /* convert our time to unix time */ + if (timestamp != 0) { + timestamp -= upnp_time(); + } + + while(fgets(line, sizeof(line), fd)) { + + syslog(LOG_DEBUG, "parsing lease file line '%s'", line); + proto = line; + p = strchr(line, ';'); + if(!p) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p++) = '\0'; + p2 = strchr(p, ';'); + if(!p2) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p2++) = '\0'; + int_port = (unsigned short)atoi(p2); + int_client = p; + p = strchr(p2, ';'); + if(!p) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p++) = '\0'; + p2 = strchr(p, ';'); + if(!p2) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p2++) = '\0'; + rem_port = (unsigned short)atoi(p2); + rem_client = p; + p = strchr(p2, ';'); + if(!p) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p++) = '\0'; + p2 = strchr(p, ';'); + if(!p2) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p2++) = '\0'; + desc = strchr(p2, ';'); + uid_rule = atoi(p); + if(!desc) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(desc++) = '\0'; + timestamp_rule = (unsigned int)strtoul(p2, NULL, 10); + + if (uid == uid_rule) { + timestamp_rule = timestamp; + } + + fprintf(fdt, "%s;%s;%hu;%s;%hu;%u;%u;%s\n", + proto, int_client, int_port, rem_client, rem_port, + uid, timestamp_rule, desc); + } + + fclose(fdt); + fclose(fd); + + if (rename(tmpfilename, lease_file6) < 0) { + syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file6); + remove(tmpfilename); + } + + return 0; +} + +static int +lease_file6_remove(const char * int_client, unsigned short int_port, int proto, int uid) +{ + FILE* fd, *fdt; + int tmp, uid_tmp; + char buf[512], buf2[512]; + char str[32]; + char tmpfilename[128]; + char *p, *p2; + int str_size, buf_size; + + + if (lease_file6 == NULL) return 0; + + if (strlen(lease_file6) + 7 > sizeof(tmpfilename)) { + syslog(LOG_ERR, "Lease filename is too long"); + return -1; + } + + snprintf( tmpfilename, sizeof(tmpfilename), "%sXXXXXX", lease_file6); + + fd = fopen( lease_file6, "r"); + if (fd==NULL) { + return 0; + } + + snprintf( str, sizeof(str), "%s;%s;%u", proto_itoa(proto), int_client, int_port); + str_size = strlen(str); + + tmp = mkstemp(tmpfilename); + if (tmp==-1) { + fclose(fd); + syslog(LOG_ERR, "could not open temporary lease file"); + return -1; + } + fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + fdt = fdopen(tmp, "a"); + + buf[sizeof(buf)-1] = 0; + while( fgets(buf, sizeof(buf)-1, fd) != NULL) { + buf_size = strlen(buf); + + if (uid > 0) { + strncpy(buf2, buf, buf_size); + // Internal Host + p = strchr(buf2, ';'); + *(p++) = '\0'; + // Internal Port + p = strchr(p, ';'); + *(p++) = '\0'; + // External Host + p = strchr(p, ';'); + *(p++) = '\0'; + // External Port + p = strchr(p, ';'); + *(p++) = '\0'; + // uid + p = strchr(p, ';'); + *(p++) = '\0'; + p2 = strchr(p, ';'); + *(p2++) = '\0'; + uid_tmp = atoi(p); + if (uid != uid_tmp) { + fwrite(buf, buf_size, 1, fdt); + } + + } else if (buf_size < str_size || strncmp(str, buf, str_size)!=0) { + fwrite(buf, buf_size, 1, fdt); + } + } + + fclose(fdt); + fclose(fd); + + if (rename(tmpfilename, lease_file6) < 0) { + syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file6); + remove(tmpfilename); + } + + return 0; + +} + +int lease_file6_expire() +{ + FILE* fd, *fdt; + char * p, * p2; + int tmp; + char buf[512]; + char line[512]; + char tmpfilename[128]; + char * desc; + int buf_size; + unsigned int timestamp; + time_t current_unix_time; + + if (lease_file6 == NULL) return 0; + + if (strlen(lease_file6) + 7 > sizeof(tmpfilename)) { + syslog(LOG_ERR, "Lease filename is too long"); + return -1; + } + + snprintf( tmpfilename, sizeof(tmpfilename), "%sXXXXXX", lease_file6); + + fd = fopen( lease_file6, "r"); + if (fd==NULL) { + return 0; + } + + current_unix_time = time(NULL); + + tmp = mkstemp(tmpfilename); + if (tmp==-1) { + fclose(fd); + syslog(LOG_ERR, "could not open temporary lease file"); + return -1; + } + fchmod(tmp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + fdt = fdopen(tmp, "a"); + + buf[sizeof(buf)-1] = 0; + while(fgets(line, sizeof(line), fd)) { + strncpy(buf, line, sizeof(buf)); + + syslog(LOG_DEBUG, "Expire: parsing lease file line '%s'", line); + // Internal Host + p = strchr(line, ';'); + if(!p) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p++) = '\0'; + // Internal Port + p2 = strchr(p, ';'); + if(!p2) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p2++) = '\0'; + // External Host + p = strchr(p2, ';'); + if(!p) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p++) = '\0'; + // External Port + p2 = strchr(p, ';'); + if(!p2) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p2++) = '\0'; + // uid + p = strchr(p2, ';'); + if(!p) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p++) = '\0'; + // Timestamp + p2 = strchr(p, ';'); + if(!p2) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p2++) = '\0'; + // descr + desc = strchr(p2, ';'); + if(!desc) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(desc++) = '\0'; + /*timestamp = (unsigned int)atoi(p2);*/ + timestamp = (unsigned int)strtoul(p2, NULL, 10); + syslog(LOG_DEBUG, "Expire: timestamp is '%u'", timestamp); + syslog(LOG_DEBUG, "Expire: current timestamp is '%u'", (unsigned int)current_unix_time); + if((timestamp > 0 && timestamp <= (unsigned int)current_unix_time) || timestamp <= 0) { + continue; + } + + buf_size = strlen(buf); + fwrite(buf, buf_size, 1, fdt); + } + + fclose(fdt); + fclose(fd); + + if (rename(tmpfilename, lease_file6) < 0) { + syslog(LOG_ERR, "could not rename temporary lease file to %s", lease_file6); + remove(tmpfilename); + } + + return 0; +} + +/* reload_from_lease_file() + * read lease_file and add the rules contained + */ +int reload_from_lease_file6() +{ + FILE * fd; + char * p, * p2; + unsigned short int_port, rem_port; + char * proto; + char * int_client; + char * desc; + char * rem_client; + unsigned int leaseduration; + unsigned int timestamp; + time_t current_time; + time_t current_unix_time; + char line[128]; + int r, uid; + + if(!lease_file6) return -1; + fd = fopen( lease_file6, "r"); + if (fd==NULL) { + syslog(LOG_ERR, "could not open lease file: %s", lease_file6); + return -1; + } + if(unlink(lease_file6) < 0) { + syslog(LOG_WARNING, "could not unlink file %s : %m", lease_file6); + } + + current_time = upnp_time(); + current_unix_time = time(NULL); + + while(fgets(line, sizeof(line), fd)) { + syslog(LOG_DEBUG, "parsing lease file line '%s'", line); + proto = line; + p = strchr(line, ';'); + if(!p) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p++) = '\0'; + p2 = strchr(p, ';'); + if(!p2) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p2++) = '\0'; + int_port = (unsigned short)atoi(p2); + int_client = p; + p = strchr(p2, ';'); + if(!p) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p++) = '\0'; + p2 = strchr(p, ';'); + if(!p2) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p2++) = '\0'; + rem_port = (unsigned short)atoi(p2); + rem_client = p; + p = strchr(p2, ';'); + if(!p) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p++) = '\0'; + p2 = strchr(p, ';'); + if(!p2) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(p2++) = '\0'; + desc = strchr(p2, ';'); + uid = atoi(p); + if(!desc) { + syslog(LOG_ERR, "unrecognized data in lease file"); + continue; + } + *(desc++) = '\0'; + /*timestamp = (unsigned int)atoi(p2);*/ + timestamp = (unsigned int)strtoul(p2, NULL, 10); + /* trim description */ + while(isspace(*desc)) + desc++; + p = desc; + while(*(p+1)) + p++; + while(isspace(*p) && (p > desc)) + *(p--) = '\0'; + + if(timestamp > 0) { + if(timestamp <= (unsigned int)current_unix_time) { + syslog(LOG_NOTICE, "already expired lease in lease file"); + continue; + } else { + leaseduration = timestamp - current_unix_time; + timestamp = leaseduration + current_time; /* convert to our time */ + } + } else { + leaseduration = 0; /* default value */ + } + + r = upnp_add_inboundpinhole(rem_client, rem_port, int_client, int_port, proto_atoi(proto), + desc, leaseduration, &uid); + if(r == -1) { + syslog(LOG_ERR, "Failed to add %s:%hu -> %s:%hu protocol %s", + rem_client, rem_port, int_client, int_port, proto); + } else if(r == -2) { + /* Add the redirection again to the lease file */ + lease_file6_add(rem_client, rem_port, int_client, int_port, proto_atoi(proto), + uid, desc, timestamp); + } + } + fclose(fd); + + return 0; +} + +#endif + int upnp_find_inboundpinhole(const char * raddr, unsigned short rport, const char * iaddr, unsigned short iport, @@ -140,11 +661,23 @@ upnp_add_inboundpinhole(const char * raddr, if(*uid >= 0) { syslog(LOG_INFO, "Pinhole for inbound traffic from [%s]:%hu to [%s]:%hu with proto %d found uid=%d. Updating it.", raddr, rport, iaddr, iport, proto, *uid); r = upnp_update_inboundpinhole(*uid, timestamp); +#ifdef ENABLE_LEASEFILE + if (r >= 0) { + lease_file6_remove(iaddr, iport, proto, -1); + lease_file6_add(raddr, rport, iaddr, iport, proto, *uid, desc, timestamp); + } +#endif /* ENABLE_LEASEFILE */ return (r >= 0) ? 1 : r; } #if defined(USE_PF) || defined(USE_NETFILTER) *uid = add_pinhole (ext_if_name6, raddr, rport, iaddr, iport, proto, desc, timestamp); +#ifdef ENABLE_LEASEFILE + if (*uid >= 0) { + lease_file6_remove(iaddr, iport, proto, -1); + lease_file6_add(raddr, rport, iaddr, iport, proto, *uid, desc, timestamp); + } +#endif /* ENABLE_LEASEFILE */ return *uid >= 0 ? 1 : -1; #else return -42; /* not implemented */ @@ -290,9 +823,15 @@ upnp_update_inboundpinhole(unsigned short uid, unsigned int leasetime) { #if defined(USE_PF) || defined(USE_NETFILTER) unsigned int timestamp; + int ret; timestamp = upnp_time() + leasetime; - return update_pinhole(uid, timestamp); + ret = update_pinhole(uid, timestamp); +#ifdef ENABLE_LEASEFILE + if (ret == 0) + lease_file6_update(uid, timestamp); +#endif + return ret; #else UNUSED(uid); UNUSED(leasetime); @@ -304,7 +843,14 @@ int upnp_delete_inboundpinhole(unsigned short uid) { #if defined(USE_PF) || defined(USE_NETFILTER) - return delete_pinhole(uid); + int ret; + ret = delete_pinhole(uid); +#ifdef ENABLE_LEASEFILE + if (ret == 0) + lease_file6_remove((const char *)"*", 0, 0, uid); +#endif + + return ret; #else UNUSED(uid); @@ -534,13 +1080,17 @@ upnp_check_pinhole_working(const char * uid, int upnp_clean_expired_pinholes(unsigned int * next_timestamp) { + int ret = 0; #if defined(USE_PF) || defined(USE_NETFILTER) - return clean_pinhole_list(next_timestamp); + ret = clean_pinhole_list(next_timestamp); #else UNUSED(next_timestamp); - - return 0; /* nothing to do */ #endif +#ifdef ENABLE_LEASEFILE + lease_file6_expire(); +#endif + + return ret; } #endif /* ENABLE_UPNPPINHOLE */ diff --git a/miniupnpd/upnppinhole.h b/miniupnpd/upnppinhole.h index c979401..4152925 100644 --- a/miniupnpd/upnppinhole.h +++ b/miniupnpd/upnppinhole.h @@ -13,6 +13,11 @@ #ifdef ENABLE_UPNPPINHOLE +#ifdef ENABLE_LEASEFILE +int reload_from_lease_file6(void); +int lease_file6_expire(void); +#endif + /* functions to be used by WANIPv6_FirewallControl implementation * and PCP (IPv6) */