/* $Id: upnppermissions.c,v 1.20 2020/10/30 21:37:35 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2006-2020 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include <ctype.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <syslog.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include "config.h" #include "upnppermissions.h" /* read_permission_line() * parse the a permission line which format is : * (deny|allow) [0-9]+(-[0-9]+) ip/mask [0-9]+(-[0-9]+) * ip/mask is either 192.168.1.1/24 or 192.168.1.1/255.255.255.0 */ int read_permission_line(struct upnpperm * perm, char * p) { char * q; int n_bits; int i; /* first token: (allow|deny) */ while(isspace(*p)) p++; if(0 == memcmp(p, "allow", 5)) { perm->type = UPNPPERM_ALLOW; p += 5; } else if(0 == memcmp(p, "deny", 4)) { perm->type = UPNPPERM_DENY; p += 4; } else { return -1; } while(isspace(*p)) p++; /* second token: eport or eport_min-eport_max */ if(!isdigit(*p)) return -1; for(q = p; isdigit(*q); q++); if(*q=='-') { *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->eport_min = (u_short)i; q++; p = q; while(isdigit(*q)) q++; *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->eport_max = (u_short)i; if(perm->eport_min > perm->eport_max) return -1; } else if(isspace(*q)) { *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->eport_min = perm->eport_max = (u_short)i; } else { return -1; } p = q + 1; while(isspace(*p)) p++; /* third token: ip/mask */ if(!isdigit(*p)) return -1; for(q = p; isdigit(*q) || (*q == '.'); q++); if(*q=='/') { *q = '\0'; if(!inet_aton(p, &perm->address)) return -1; q++; p = q; while(isdigit(*q)) q++; if(*q == '.') { while(*q == '.' || isdigit(*q)) q++; if(!isspace(*q)) return -1; *q = '\0'; if(!inet_aton(p, &perm->mask)) return -1; } else if(!isspace(*q)) return -1; else { *q = '\0'; n_bits = atoi(p); if(n_bits > 32) return -1; perm->mask.s_addr = htonl(n_bits ? (0xffffffffu << (32 - n_bits)) : 0); } } else if(isspace(*q)) { *q = '\0'; if(!inet_aton(p, &perm->address)) return -1; perm->mask.s_addr = 0xffffffffu; } else { return -1; } p = q + 1; /* fourth token: iport or iport_min-iport_max */ while(isspace(*p)) p++; if(!isdigit(*p)) return -1; for(q = p; isdigit(*q); q++); if(*q=='-') { *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->iport_min = (u_short)i; q++; p = q; while(isdigit(*q)) q++; *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->iport_max = (u_short)i; if(perm->iport_min > perm->iport_max) return -1; } else if(isspace(*q) || *q == '\0') { *q = '\0'; i = atoi(p); if(i > 65535) return -1; perm->iport_min = perm->iport_max = (u_short)i; } else { return -1; } #ifdef DEBUG printf("perm rule added : %s %hu-%hu %08x/%08x %hu-%hu\n", (perm->type==UPNPPERM_ALLOW)?"allow":"deny", perm->eport_min, perm->eport_max, ntohl(perm->address.s_addr), ntohl(perm->mask.s_addr), perm->iport_min, perm->iport_max); #endif return 0; } #ifdef USE_MINIUPNPDCTL void write_permlist(int fd, const struct upnpperm * permary, int nperms) { int l; const struct upnpperm * perm; int i; char buf[128]; write(fd, "Permissions :\n", 14); for(i = 0; i<nperms; i++) { perm = permary + i; l = snprintf(buf, sizeof(buf), "%02d %s %hu-%hu %08x/%08x %hu-%hu\n", i, (perm->type==UPNPPERM_ALLOW)?"allow":"deny", perm->eport_min, perm->eport_max, ntohl(perm->address.s_addr), ntohl(perm->mask.s_addr), perm->iport_min, perm->iport_max); if(l<0) return; write(fd, buf, l); } } #endif /* match_permission() * returns: 1 if eport, address, iport matches the permission rule * 0 if no match */ static int match_permission(const struct upnpperm * perm, u_short eport, struct in_addr address, u_short iport) { if( (eport < perm->eport_min) || (perm->eport_max < eport)) return 0; if( (iport < perm->iport_min) || (perm->iport_max < iport)) return 0; if( (address.s_addr & perm->mask.s_addr) != (perm->address.s_addr & perm->mask.s_addr) ) return 0; return 1; } #if 0 /* match_permission_internal() * returns: 1 if address, iport matches the permission rule * 0 if no match */ static int match_permission_internal(const struct upnpperm * perm, struct in_addr address, u_short iport) { if( (iport < perm->iport_min) || (perm->iport_max < iport)) return 0; if( (address.s_addr & perm->mask.s_addr) != (perm->address.s_addr & perm->mask.s_addr) ) return 0; return 1; } #endif int check_upnp_rule_against_permissions(const struct upnpperm * permary, int n_perms, u_short eport, struct in_addr address, u_short iport) { int i; for(i=0; i<n_perms; i++) { if(match_permission(permary + i, eport, address, iport)) { syslog(LOG_DEBUG, "UPnP permission rule %d matched : port mapping %s", i, (permary[i].type == UPNPPERM_ALLOW)?"accepted":"rejected" ); return (permary[i].type == UPNPPERM_ALLOW); } } syslog(LOG_DEBUG, "no permission rule matched : accept by default (n_perms=%d)", n_perms); return 1; /* Default : accept */ } void get_permitted_ext_ports(uint32_t * allowed, const struct upnpperm * permary, int n_perms, in_addr_t addr, u_short iport) { int i, j; /* build allowed external ports array */ memset(allowed, 0xff, 65536 / 8); /* everything allowed by default */ for (i = n_perms - 1; i >= 0; i--) { if( (addr & permary[i].mask.s_addr) != (permary[i].address.s_addr & permary[i].mask.s_addr) ) continue; if( (iport < permary[i].iport_min) || (permary[i].iport_max < iport)) continue; for (j = (int)permary[i].eport_min ; j <= (int)permary[i].eport_max; ) { if ((j % 32) == 0 && ((int)permary[i].eport_max >= (j + 31))) { /* 32bits at once */ allowed[j / 32] = (permary[i].type == UPNPPERM_ALLOW) ? 0xffffffff : 0; j += 32; } else { do { /* one bit at once */ if (permary[i].type == UPNPPERM_ALLOW) allowed[j / 32] |= (1 << (j % 32)); else allowed[j / 32] &= ~(1 << (j % 32)); j++; } while ((j % 32) != 0 && (j <= (int)permary[i].eport_max)); } } } }