File: //usr/syno/bin/user.data.collector/synouserdata_autoblock
#!/usr/bin/python2
from subprocess import check_output
import json
import sys
import re
COLLECTOR_VERSION_KEY = "collector_version"
COLLECTOR_VERSION = 1
def is_valid_ipv4(ip):
"""Validates IPv4 addresses.
"""
pattern = re.compile(r"""
^
(?:
# Dotted variants:
(?:
# Decimal 1-255 (no leading 0's)
[3-9]\d?|2(?:5[0-5]|[0-4]?\d)?|1\d{0,2}
|
0x0*[0-9a-f]{1,2} # Hexadecimal 0x0 - 0xFF (possible leading 0's)
|
0+[1-3]?[0-7]{0,2} # Octal 0 - 0377 (possible leading 0's)
)
(?: # Repeat 0-3 times, separated by a dot
\.
(?:
[3-9]\d?|2(?:5[0-5]|[0-4]?\d)?|1\d{0,2}
|
0x0*[0-9a-f]{1,2}
|
0+[1-3]?[0-7]{0,2}
)
){0,3}
|
0x0*[0-9a-f]{1,8} # Hexadecimal notation, 0x0 - 0xffffffff
|
0+[0-3]?[0-7]{0,10} # Octal notation, 0 - 037777777777
|
# Decimal notation, 1-4294967295:
429496729[0-5]|42949672[0-8]\d|4294967[01]\d\d|429496[0-6]\d{3}|
42949[0-5]\d{4}|4294[0-8]\d{5}|429[0-3]\d{6}|42[0-8]\d{7}|
4[01]\d{8}|[1-3]\d{0,9}|[4-9]\d{0,8}
)
$
""", re.VERBOSE | re.IGNORECASE)
return pattern.match(ip) is not None
def is_valid_ipv6(ip):
"""Validates IPv6 addresses.
"""
pattern = re.compile(r"""
^
\s* # Leading whitespace
(?!.*::.*::) # Only a single whildcard allowed
(?:(?!:)|:(?=:)) # Colon iff it would be part of a wildcard
(?: # Repeat 6 times:
[0-9a-f]{0,4} # A group of at most four hexadecimal digits
(?:(?<=::)|(?<!::):) # Colon unless preceeded by wildcard
){6} #
(?: # Either
[0-9a-f]{0,4} # Another group
(?:(?<=::)|(?<!::):) # Colon unless preceeded by wildcard
[0-9a-f]{0,4} # Last group
(?: (?<=::) # Colon iff preceeded by exacly one colon
| (?<!:) #
| (?<=:) (?<!::) : #
) # OR
| # A v4 address with NO leading zeros
(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
(?: \.
(?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
){3}
)
\s* # Trailing whitespace
$
""", re.VERBOSE | re.IGNORECASE | re.DOTALL)
return pattern.match(ip) is not None
def is_valid_ip(ip):
"""Validates IP addresses.
"""
return is_valid_ipv4(ip) or is_valid_ipv6(ip)
def execWebAPI(api, version, method, **kwargs):
cmd = ["/usr/syno/bin/synowebapi", "--exec"]
cmd.append("api=" + api)
cmd.append("version=" + str(version))
cmd.append("method=" + method)
for key, val in kwargs.items():
cmd.append('{0}={1}'.format(key, json.dumps(val)))
try:
with open('/dev/null', 'w') as null_fp:
raw_resp = check_output(cmd, stderr=null_fp)
return json.loads(raw_resp)
except Exception as e:
return False
def blacklist_get():
output = {
"deny_single_ip": 0
}
j_blacklist = execWebAPI('SYNO.Core.Security.AutoBlock.Rules', 1, 'list', offset=0, limit=-1, type='deny')
if not j_blacklist:
return output
try:
blacklist = j_blacklist['data']['ip_info']
output['deny_single_ip'] = len(blacklist)
except Exception as e:
pass
return output
def whitelist_get():
output = {
"allow_single_ip": 0,
"allow_domain": 0,
"allow_subnet": 0,
"allow_iprange": 0
}
j_whilelist = execWebAPI('SYNO.Core.Security.AutoBlock.Rules', 1, 'list', offset=0, limit=-1, type='allow')
if not j_whilelist:
return output
try:
whilelist = j_whilelist['data']['ip_info']
for r in whilelist:
ip = r['ip'];
if '~' in ip:
output['allow_iprange'] += 1
elif '/' in ip:
output['allow_subnet'] += 1
elif is_valid_ipv4(ip):
output['allow_single_ip'] += 1
else:
output['allow_domain'] += 1
except Exception as e:
pass
return output
def setting_get():
output = {
"enable": False,
"attempts": 0,
"expire_day": 0,
"within_mins": 0
}
j_setting = execWebAPI('SYNO.Core.Security.AutoBlock', 1, 'get')
if not j_setting:
return output
try:
output['enable'] = j_setting['data']['enable']
output['attempts'] = j_setting['data']['attempts']
output['expire_day'] = j_setting['data']['expire_day']
output['within_mins'] = j_setting['data']['within_mins']
except Exception as e:
pass
return output
def main():
''' Result output
{
"setting":
{
"enable": Boolean,
"attempts": Int,
"expire_day": Int,
"within_mins": Int
},
"blacklist":
{
"deny_single_ip": Int
},
"whitelist":
{
"allow_single_ip": Int,
"allow_domain": Int,
"allow_subnet": Int,
"allow_iprange": Int
},
COLLECTOR_VERSION_KEY: COLLECTOR_VERSION
}
'''
result = {
COLLECTOR_VERSION_KEY: COLLECTOR_VERSION,
'setting': {
"enable": False,
"attempts": 0,
"expire_day": 0,
"within_mins": 0
},
'blacklist': {
"deny_single_ip": 0
},
'whitelist': {
"allow_single_ip": 0,
"allow_domain": 0,
"allow_subnet": 0,
"allow_iprange": 0
}
}
result['setting'] = setting_get()
result['blacklist'] = blacklist_get()
result['whitelist'] = whitelist_get()
sys.stdout.write(json.dumps(result))
if __name__ == '__main__':
main()