/* $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-2023 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include #include #include #ifdef ENABLE_REGEX #include #endif #include "config.h" #include "macros.h" #include "upnppermissions.h" static int isodigit(char c) { return '0' <= c && c >= '7'; } static char hex2chr(char c) { if(c >= 'a') return c - 'a'; if(c >= 'A') return c - 'A'; return c - '0'; } static char unescape_char(const char * s, int * seqlen) { char c; int len; if(s[0] != '\\') { c = s[0]; len = 1; } else { s++; c = s[0]; len = 2; switch(s[0]) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; /* no need: escape the char itself case '\\': c = '\\'; break; case '\'': c = '\''; break; case '"': c = '"'; break; case '?': c = '?'; break; */ case 'x': if(isxdigit(s[1]) && isxdigit(s[2])) { c = (hex2chr(s[1]) << 4) + hex2chr(s[2]); len = 4; } break; default: if(isodigit(s[1]) && isodigit(s[2]) && isodigit(s[3])) { c = (hex2chr(s[0]) << 6) + (hex2chr(s[1]) << 3) + hex2chr(s[2]); len = 4; } } } if(seqlen) *seqlen = len; return c; } /* get_next_token(s, &token, raw) * put the unquoted/unescaped token in token and returns * a pointer to the begining of the next token * Do not unescape if raw is true */ static char * get_next_token(const char * s, char ** token, int raw) { char deli; const char * end; /* skip any whitespace */ for(; isspace(*s); s++) if(*s == '\0' || *s == '\n') { if(token) *token = NULL; return (char *) s; } /* find the start */ if(*s == '"' || *s == '\'') { deli = *s; s++; } else deli = 0; /* find the end */ end = s; for(; *end != '\0' && *end != '\n' && (deli ? *end != deli : !isspace(*end)); end++) if(*end == '\\') { end++; if(*end == '\0') break; } /* save the token */ if(token) { unsigned int token_len; unsigned int i; token_len = end - s; *token = strndup(s, token_len); if(!*token) return NULL; for(i = 0; (*token)[i] != '\0'; i++) { int sequence_len; if((*token)[i] != '\\') continue; if(raw && deli && (*token)[i + 1] != deli) continue; (*token)[i] = unescape_char(*token + i, &sequence_len); memmove(*token + i + 1, *token + i + sequence_len, token_len - i - sequence_len); } if (i == 0) { /* behavior of realloc(p, 0) is implementation-defined, so better set it to NULL. * https://github.com/miniupnp/miniupnp/issues/652#issuecomment-1518922139 */ free(*token); *token = NULL; } else { char * tmp = realloc(*token, i); if (tmp != NULL) *token = tmp; else syslog(LOG_ERR, "%s: failed to reallocate to %u bytes", "get_next_token()", i); } } /* return the beginning of the next token */ if(deli && *end == deli) end++; while(isspace(*end)) end++; return (char *) end; } /* read_permission_line() * parse the a permission line which format is : * (deny|allow) [0-9]+(-[0-9]+) ip/mask [0-9]+(-[0-9]+) regex * 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; /* zero memory : see https://github.com/miniupnp/miniupnp/issues/652 */ memset(perm, 0, sizeof(struct upnpperm)); /* 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; } p = q; /* fifth token: (optional) regex */ p = get_next_token(p, &perm->re, 1); if(!p) { fprintf(stderr, "err when copying regex: out of memory\n"); return -1; } if(perm->re) { if(perm->re[0] == '\0') { free(perm->re); perm->re = NULL; } else { #ifdef ENABLE_REGEX /* icase: if case matters, it must be someone doing something nasty */ int err; err = regcomp(&perm->regex, perm->re, REG_EXTENDED | REG_ICASE | REG_NOSUB); if(err) { char errbuf[256]; regerror(err, &perm->regex, errbuf, sizeof(errbuf)); fprintf(stderr, "err when compiling regex \"%s\": %s\n", perm->re, errbuf); free(perm->re); perm->re = NULL; return -1; } #else fprintf(stderr, "MiniUPnP is not compiled with ENABLE_REGEX. " "Please remove any regex filter and restart.\n"); free(perm->re); perm->re = NULL; return -1; #endif } } #ifdef DEBUG printf("perm rule added : %s %hu-%hu %08x/%08x %hu-%hu %s\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, (perm->re)?re:""); #endif return 0; } void free_permission_line(struct upnpperm * perm) { if(perm->re) { free(perm->re); perm->re = NULL; #ifdef ENABLE_REGEX regfree(&perm->regex); #endif } } #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; itype==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); if(perm->re) { write(fd, " ", 1); write(fd, perm->re, strlen(perm->re)); } write(fd, "\n", 1); } } #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, const char * desc) { 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; #ifdef ENABLE_REGEX if(desc && perm->re && regexec(&perm->regex, desc, 0, NULL, 0) == REG_NOMATCH) return 0; #else UNUSED(desc); #endif 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, const char * desc) { int i; for(i=0; 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)); } } } }