miniupnpd: Add option to match rules with regex
Some reports that a certain app is abusing UPnP for exploiting upload bandwidth. This commit adds support to restrict UPnP rules to a regex. By matching requester's description string against rule's regex, this will make some obstacles for that app.
This commit is contained in:
parent
59335e4637
commit
2ff8cb17da
|
@ -15,7 +15,7 @@ UPNP_VERSION_MINOR=1
|
|||
|
||||
# input environment variables :
|
||||
# IPV6, IGD2, STRICT, DEBUG, LEASFILE, VENDORCFG, PCP_PEER,
|
||||
# PORTINUSE, DISABLEPPPCONN, FW, IPTABLESPATH, TARGET_OPENWRT,
|
||||
# PORTINUSE, REGEX, DISABLEPPPCONN, FW, IPTABLESPATH, TARGET_OPENWRT,
|
||||
# PKG_CONFIG, NO_BACKGROUND_NO_PIDFILE, DYNAMIC_OS_VERSION
|
||||
# OS_NAME, OS_VERSION, OS_MACHINE, V6SOCKETS_ARE_V6ONLY
|
||||
|
||||
|
@ -33,6 +33,7 @@ case "$argv" in
|
|||
--vendorcfg) VENDORCFG=1 ;;
|
||||
--pcp-peer) PCP_PEER=1 ;;
|
||||
--portinuse) PORTINUSE=1 ;;
|
||||
--regex) REGEX=1 ;;
|
||||
--uda-version=*)
|
||||
UPNP_VERSION=$(echo $argv | cut -d= -f2)
|
||||
UPNP_VERSION_MAJOR=$(echo $UPNP_VERSION | cut -s -d. -f1)
|
||||
|
@ -67,6 +68,7 @@ case "$argv" in
|
|||
echo " --vendorcfg enable configuration of manufacturer info"
|
||||
echo " --pcp-peer enable PCP PEER operation"
|
||||
echo " --portinuse enable port in use check"
|
||||
echo " --regex enable description regex filter"
|
||||
echo " --uda-version=x.x set advertised UPnP version (default to ${UPNP_VERSION_MAJOR}.${UPNP_VERSION_MINOR})"
|
||||
echo " --disable-pppconn disable WANPPPConnection"
|
||||
echo " --firewall=<name> force the firewall type (nftables, iptables, pf, ipf, ipfw)"
|
||||
|
@ -753,6 +755,14 @@ else
|
|||
fi
|
||||
echo "" >> ${CONFIGFILE}
|
||||
|
||||
echo "/* Uncomment the following line to enable description regex filter */" >> ${CONFIGFILE}
|
||||
if [ -n "$REGEX" ]; then
|
||||
echo "#define ENABLE_REGEX" >> ${CONFIGFILE}
|
||||
else
|
||||
echo "/*#define ENABLE_REGEX*/" >> ${CONFIGFILE}
|
||||
fi
|
||||
echo "" >> ${CONFIGFILE}
|
||||
|
||||
echo "/* Define one or none of the two following macros in order to make some" >> ${CONFIGFILE}
|
||||
echo " * clients happy. It will change the XML Root Description of the IGD." >> ${CONFIGFILE}
|
||||
echo " * Enabling the Layer3Forwarding Service seems to be the more compatible" >> ${CONFIGFILE}
|
||||
|
|
|
@ -168,7 +168,7 @@ uuid=00000000-0000-0000-0000-000000000000
|
|||
#force_igd_desc_v1=no
|
||||
|
||||
# UPnP permission rules
|
||||
# (allow|deny) (external port range) IP/mask (internal port range)
|
||||
# (allow|deny) (external port range) IP/mask (internal port range) (optional regex filter)
|
||||
# A port range is <min port>-<max port> or <port> if there is only
|
||||
# one port in the range.
|
||||
# IP/mask format must be nnn.nnn.nnn.nnn/nn
|
||||
|
@ -180,6 +180,8 @@ uuid=00000000-0000-0000-0000-000000000000
|
|||
# also consider implementing network-specific restrictions
|
||||
# CAUTION: failure to enforce any rules may permit insecure requests to be made!
|
||||
allow 1024-65535 192.168.0.0/24 1024-65535
|
||||
# disallow requests whose description string matches the given regex
|
||||
# deny 1024-65535 192.168.1.0/24 1024-65535 "My evil app ver [[:digit:]]*"
|
||||
allow 1024-65535 192.168.1.0/24 1024-65535
|
||||
allow 1024-65535 192.168.0.0/23 22
|
||||
allow 12345 192.168.7.113/32 54321
|
||||
|
|
|
@ -350,7 +350,7 @@ void ProcessIncomingNATPMPPacket(int s, unsigned char *msg_buff, int len,
|
|||
}
|
||||
break;
|
||||
}
|
||||
if(!check_upnp_rule_against_permissions(upnppermlist, num_upnpperm, eport, senderaddr->sin_addr, iport)) {
|
||||
if(!check_upnp_rule_against_permissions(upnppermlist, num_upnpperm, eport, senderaddr->sin_addr, iport, "NAT-PMP")) {
|
||||
eport++;
|
||||
if(eport == 0) eport++; /* skip port zero */
|
||||
continue;
|
||||
|
|
|
@ -320,6 +320,10 @@ freeoptions(void)
|
|||
}
|
||||
if(upnppermlist)
|
||||
{
|
||||
unsigned int i;
|
||||
for (i = 0; i < num_upnpperm; i++) {
|
||||
free_permission_line(upnppermlist + i);
|
||||
}
|
||||
free(upnppermlist);
|
||||
upnppermlist = NULL;
|
||||
num_upnpperm = 0;
|
||||
|
|
|
@ -934,7 +934,7 @@ static int CreatePCPMap_NAT(pcp_info_t *pcp_msg_info)
|
|||
(!check_upnp_rule_against_permissions(upnppermlist,
|
||||
num_upnpperm, pcp_msg_info->ext_port,
|
||||
((struct in_addr*)pcp_msg_info->mapped_ip->s6_addr)[3],
|
||||
pcp_msg_info->int_port)))) {
|
||||
pcp_msg_info->int_port, pcp_msg_info->desc)))) {
|
||||
if (pcp_msg_info->pfailure_present) {
|
||||
return PCP_ERR_CANNOT_PROVIDE_EXTERNAL;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* $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
|
||||
* (c) 2006-2022 Thomas Bernard
|
||||
* This software is subject to the conditions detailed
|
||||
* in the LICENCE file provided within the distribution */
|
||||
|
||||
|
@ -13,12 +13,159 @@
|
|||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef ENABLE_REGEX
|
||||
#include <regex.h>
|
||||
#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);
|
||||
}
|
||||
*token = realloc(*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]+)
|
||||
* (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
|
||||
|
@ -172,15 +319,72 @@ read_permission_line(struct upnpperm * perm,
|
|||
{
|
||||
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\n",
|
||||
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);
|
||||
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,
|
||||
|
@ -194,14 +398,20 @@ write_permlist(int fd, const struct upnpperm * permary,
|
|||
for(i = 0; i<nperms; i++)
|
||||
{
|
||||
perm = permary + i;
|
||||
l = snprintf(buf, sizeof(buf), "%02d %s %hu-%hu %08x/%08x %hu-%hu\n",
|
||||
l = snprintf(buf, sizeof(buf), "%02d %s %hu-%hu %08x/%08x %hu-%hu",
|
||||
i,
|
||||
(perm->type==UPNPPERM_ALLOW)?"allow":"deny",
|
||||
(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);
|
||||
if(perm->re)
|
||||
{
|
||||
write(fd, " ", 1);
|
||||
write(fd, perm->re, strlen(perm->re));
|
||||
}
|
||||
write(fd, "\n", 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -211,7 +421,8 @@ write_permlist(int fd, const struct upnpperm * permary,
|
|||
* 0 if no match */
|
||||
static int
|
||||
match_permission(const struct upnpperm * perm,
|
||||
u_short eport, struct in_addr address, u_short iport)
|
||||
u_short eport, struct in_addr address, u_short iport,
|
||||
const char * desc)
|
||||
{
|
||||
if( (eport < perm->eport_min) || (perm->eport_max < eport))
|
||||
return 0;
|
||||
|
@ -220,6 +431,12 @@ match_permission(const struct upnpperm * perm,
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -244,12 +461,12 @@ int
|
|||
check_upnp_rule_against_permissions(const struct upnpperm * permary,
|
||||
int n_perms,
|
||||
u_short eport, struct in_addr address,
|
||||
u_short iport)
|
||||
u_short iport, const char * desc)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<n_perms; i++)
|
||||
{
|
||||
if(match_permission(permary + i, eport, address, iport))
|
||||
if(match_permission(permary + i, eport, address, iport, desc))
|
||||
{
|
||||
syslog(LOG_DEBUG,
|
||||
"UPnP permission rule %d matched : port mapping %s",
|
||||
|
|
|
@ -11,6 +11,11 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#ifdef ENABLE_REGEX
|
||||
#include <regex.h>
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
|
||||
/* UPnP permission rule samples:
|
||||
|
@ -22,6 +27,10 @@ struct upnpperm {
|
|||
u_short eport_min, eport_max; /* external port range */
|
||||
struct in_addr address, mask; /* ip/mask */
|
||||
u_short iport_min, iport_max; /* internal port range */
|
||||
char * re;
|
||||
#ifdef ENABLE_REGEX
|
||||
regex_t regex; /* matching regex */
|
||||
#endif
|
||||
};
|
||||
|
||||
/* read_permission_line()
|
||||
|
@ -36,6 +45,9 @@ int
|
|||
read_permission_line(struct upnpperm * perm,
|
||||
char * p);
|
||||
|
||||
void
|
||||
free_permission_line(struct upnpperm * perm);
|
||||
|
||||
/* check_upnp_rule_against_permissions()
|
||||
* returns: 0 if the upnp rule should be rejected,
|
||||
* 1 if it could be accepted */
|
||||
|
@ -43,7 +55,7 @@ int
|
|||
check_upnp_rule_against_permissions(const struct upnpperm * permary,
|
||||
int n_perms,
|
||||
u_short eport, struct in_addr address,
|
||||
u_short iport);
|
||||
u_short iport, const char * desc);
|
||||
|
||||
/**
|
||||
* Build an array of all allowed external ports (for the address and internal port)
|
||||
|
|
|
@ -351,9 +351,9 @@ upnp_redirect(const char * rhost, unsigned short eport,
|
|||
}
|
||||
|
||||
if(!check_upnp_rule_against_permissions(upnppermlist, num_upnpperm,
|
||||
eport, address, iport)) {
|
||||
eport, address, iport, desc)) {
|
||||
syslog(LOG_INFO, "redirection permission check failed for "
|
||||
"%hu->%s:%hu %s", eport, iaddr, iport, protocol);
|
||||
"%hu->%s:%hu %s %s", eport, iaddr, iport, protocol, desc);
|
||||
return -3;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue