mirror of
https://github.com/gSpotx2f/ruantiblock_openwrt.git
synced 2026-05-14 06:30:59 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
## Ruantiblock
|
||||||
|
|
||||||
|
|
||||||
|
Обход блокировок в OpenWrt с помощью Tor или VPN.
|
||||||
|
|
||||||
|
|
||||||
|
Подробно об установке и настройке: [https://github.com/gSpotx2f/ruantiblock_openwrt/wiki](https://github.com/gSpotx2f/ruantiblock_openwrt/wiki)
|
||||||
|
|
||||||
|
___________________
|
||||||
Executable
+441
@@ -0,0 +1,441 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
PREFIX=""
|
||||||
|
TOR_USER="tor"
|
||||||
|
|
||||||
|
PROXY_MODE=1
|
||||||
|
RAM_CONFIG=0
|
||||||
|
LUA_MODULE=1
|
||||||
|
LUCI_APP=1
|
||||||
|
|
||||||
|
### ruantiblock
|
||||||
|
URL_CONFIG="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock/files/etc/ruantiblock/ruantiblock.conf"
|
||||||
|
URL_FQDN_FILTER="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock/files/etc/ruantiblock/fqdn_filter"
|
||||||
|
URL_CONFIG_SCRIPT="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock/files/etc/ruantiblock/scripts/config_script"
|
||||||
|
URL_INFO_OUTPUT="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock/files/etc/ruantiblock/scripts/info_output"
|
||||||
|
URL_IPT_FUNCTIONS="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock/files/etc/ruantiblock/scripts/ipt_functions"
|
||||||
|
URL_START_SCRIPT="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock/files/etc/ruantiblock/scripts/start_script"
|
||||||
|
URL_UCI_CONFIG="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock/files/etc/config/ruantiblock"
|
||||||
|
URL_INIT_SCRIPT="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock/files/etc/init.d/ruantiblock"
|
||||||
|
URL_MAIN_SCRIPT="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock/files/usr/bin/ruantiblock"
|
||||||
|
URL_VPN_UP="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock/files/etc/hotplug.d/iface/40-ruantiblock"
|
||||||
|
### tor
|
||||||
|
URL_TORRC="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/tor/etc/tor/torrc"
|
||||||
|
### ruantiblock-mod-lua
|
||||||
|
URL_PARSER_LUA="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock-mod-lua/files/usr/bin/ruab_parser.lua"
|
||||||
|
URL_LUA_IPTOOL="https://raw.githubusercontent.com/gSpotx2f/iptool-lua/master/5.1/iptool.lua"
|
||||||
|
URL_LUA_SUM_IP="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/ruantiblock-mod-lua/files/usr/lib/lua/ruab_sum_ip.lua"
|
||||||
|
URL_LUA_IDN="https://raw.githubusercontent.com/haste/lua-idn/master/idn.lua"
|
||||||
|
### luci-app-ruantiblock
|
||||||
|
URL_LUCI_CONTROLLER="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/luci-app-ruantiblock/luasrc/controller/ruantiblock.lua"
|
||||||
|
URL_LUCI_MENU="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/luci-app-ruantiblock/root/usr/share/luci/menu.d/luci-app-ruantiblock.json"
|
||||||
|
URL_LUCI_RPCD_ACL="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/luci-app-ruantiblock/root/usr/share/rpcd/acl.d/luci-app-ruantiblock.json"
|
||||||
|
URL_LUCI_JS_CRON="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/cron.js"
|
||||||
|
URL_LUCI_JS_INFO="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/info.js"
|
||||||
|
URL_LUCI_JS_SERVICE="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/service.js"
|
||||||
|
URL_LUCI_JS_SETTINGS="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/settings.js"
|
||||||
|
URL_LUCI_JS_TOOLS="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/tools.js"
|
||||||
|
URL_LUCI_JS_LOG="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/log.js"
|
||||||
|
URL_LUCI_JS_STATUS="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/luci-app-ruantiblock/htdocs/luci-static/resources/view/status/include/80_ruantiblock.js"
|
||||||
|
URL_LUCI_I18N_RU="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master/translations/ruantiblock.ru.lmo"
|
||||||
|
|
||||||
|
RUAB_CFG_DIR="${PREFIX}/etc/ruantiblock"
|
||||||
|
EXEC_DIR="${PREFIX}/usr/bin"
|
||||||
|
LUCI_ROOT="${PREFIX}/usr/lib/lua/luci"
|
||||||
|
HTDOCS_VIEW="${PREFIX}/www/luci-static/resources/view"
|
||||||
|
HTDOCS_RUAB="${HTDOCS_VIEW}/ruantiblock"
|
||||||
|
BACKUP_DIR="${RUAB_CFG_DIR}/autoinstall.bak"
|
||||||
|
DATA_DIR="${RUAB_CFG_DIR}/var"
|
||||||
|
DATA_DIR_RAM="/var/ruantiblock"
|
||||||
|
RC_LOCAL="/etc/rc.local"
|
||||||
|
|
||||||
|
### ruantiblock
|
||||||
|
FILE_CONFIG="${RUAB_CFG_DIR}/ruantiblock.conf"
|
||||||
|
FILE_FQDN_FILTER="${RUAB_CFG_DIR}/fqdn_filter"
|
||||||
|
FILE_IP_FILTER="${RUAB_CFG_DIR}/ip_filter"
|
||||||
|
FILE_USER_ENTRIES="${RUAB_CFG_DIR}/user_entries"
|
||||||
|
FILE_CONFIG_SCRIPT="${RUAB_CFG_DIR}/scripts/config_script"
|
||||||
|
FILE_INFO_OUTPUT="${RUAB_CFG_DIR}/scripts/info_output"
|
||||||
|
FILE_IPT_FUNCTIONS="${RUAB_CFG_DIR}/scripts/ipt_functions"
|
||||||
|
FILE_START_SCRIPT="${RUAB_CFG_DIR}/scripts/start_script"
|
||||||
|
FILE_UCI_CONFIG="${PREFIX}/etc/config/ruantiblock"
|
||||||
|
FILE_INIT_SCRIPT="${PREFIX}/etc/init.d/ruantiblock"
|
||||||
|
FILE_MAIN_SCRIPT="${EXEC_DIR}/ruantiblock"
|
||||||
|
FILE_VPN_UP="${PREFIX}/etc/hotplug.d/iface/40-ruantiblock"
|
||||||
|
### tor
|
||||||
|
FILE_TORRC="${PREFIX}/etc/tor/torrc"
|
||||||
|
### ruantiblock-mod-lua
|
||||||
|
FILE_PARSER_LUA="${EXEC_DIR}/ruab_parser.lua"
|
||||||
|
FILE_LUA_IPTOOL="${PREFIX}/usr/lib/lua/iptool.lua"
|
||||||
|
FILE_LUA_SUM_IP="${PREFIX}/usr/lib/lua/ruab_sum_ip.lua"
|
||||||
|
FILE_LUA_IDN="${PREFIX}/usr/lib/lua/idn.lua"
|
||||||
|
### luci-app-ruantiblock
|
||||||
|
FILE_LUCI_CONTROLLER="${LUCI_ROOT}/controller/ruantiblock.lua"
|
||||||
|
FILE_LUCI_I18N_RU="${LUCI_ROOT}/i18n/ruantiblock.ru.lmo"
|
||||||
|
FILE_LUCI_MENU="${PREFIX}/usr/share/luci/menu.d/luci-app-ruantiblock.json"
|
||||||
|
FILE_LUCI_RPCD_ACL="${PREFIX}/usr/share/rpcd/acl.d/luci-app-ruantiblock.json"
|
||||||
|
FILE_LUCI_JS_CRON="${HTDOCS_RUAB}/cron.js"
|
||||||
|
FILE_LUCI_JS_INFO="${HTDOCS_RUAB}/info.js"
|
||||||
|
FILE_LUCI_JS_SERVICE="${HTDOCS_RUAB}/service.js"
|
||||||
|
FILE_LUCI_JS_SETTINGS="${HTDOCS_RUAB}/settings.js"
|
||||||
|
FILE_LUCI_JS_TOOLS="${HTDOCS_RUAB}/tools.js"
|
||||||
|
FILE_LUCI_JS_LOG="${HTDOCS_RUAB}/log.js"
|
||||||
|
FILE_LUCI_JS_STATUS="${HTDOCS_VIEW}/status/include/80_ruantiblock.js"
|
||||||
|
|
||||||
|
AWK_CMD="awk"
|
||||||
|
WGET_CMD=`which wget`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo " Error! wget doesn't exists" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
WGET_PARAMS="--no-check-certificate -q -O "
|
||||||
|
OPKG_CMD=`which opkg`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo " Error! opkg doesn't exists" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
UCI_CMD=`which uci`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo " Error! uci doesn't exists" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
InstallPackages() {
|
||||||
|
local _pkg
|
||||||
|
for _pkg in $@
|
||||||
|
do
|
||||||
|
if [ -z "`$OPKG_CMD list-installed $_pkg`" ]; then
|
||||||
|
$OPKG_CMD install $_pkg
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error during installation of the package (${_pkg})" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
FileExists() {
|
||||||
|
test -e "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeDir() {
|
||||||
|
[ -d "$1" ] || mkdir -p "$1"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error! Can't create directory (${1})" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ChmodExec() {
|
||||||
|
chmod 755 "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdatePackagesList() {
|
||||||
|
$OPKG_CMD update
|
||||||
|
}
|
||||||
|
|
||||||
|
DlFile() {
|
||||||
|
local _dir _file
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
_dir=`dirname "$2"`
|
||||||
|
MakeDir "$_dir"
|
||||||
|
_file="$2"
|
||||||
|
else
|
||||||
|
_file="-"
|
||||||
|
fi
|
||||||
|
$WGET_CMD $WGET_PARAMS "$_file" "$1"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Connection error (${1})" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Downloading ${1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
BackupFile() {
|
||||||
|
[ -e "$1" ] && cp -f "$1" "${1}.bak.`date +%s`"
|
||||||
|
}
|
||||||
|
|
||||||
|
BackupCurrentConfig() {
|
||||||
|
local _file
|
||||||
|
MakeDir "$BACKUP_DIR"
|
||||||
|
for _file in "$FILE_CONFIG" "$FILE_FQDN_FILTER" "$FILE_IP_FILTER" "$FILE_USER_ENTRIES" "$FILE_UCI_CONFIG" "$FILE_TORRC"
|
||||||
|
do
|
||||||
|
[ -e "$_file" ] && cp -f "$_file" "${BACKUP_DIR}/`basename ${_file}`"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallBaseConfig() {
|
||||||
|
InstallPackages "ipset" "kmod-ipt-ipset" "dnsmasq-full"
|
||||||
|
DlFile "$URL_CONFIG" "$FILE_CONFIG"
|
||||||
|
DlFile "$URL_FQDN_FILTER" "$FILE_FQDN_FILTER"
|
||||||
|
DlFile "$URL_CONFIG_SCRIPT" "$FILE_CONFIG_SCRIPT"
|
||||||
|
DlFile "$URL_INFO_OUTPUT" "$FILE_INFO_OUTPUT"
|
||||||
|
DlFile "$URL_IPT_FUNCTIONS" "$FILE_IPT_FUNCTIONS"
|
||||||
|
DlFile "$URL_START_SCRIPT" "$FILE_START_SCRIPT" && ChmodExec "$FILE_START_SCRIPT"
|
||||||
|
DlFile "$URL_UCI_CONFIG" "$FILE_UCI_CONFIG"
|
||||||
|
DlFile "$URL_INIT_SCRIPT" "$FILE_INIT_SCRIPT" && ChmodExec "$FILE_INIT_SCRIPT"
|
||||||
|
DlFile "$URL_MAIN_SCRIPT" "$FILE_MAIN_SCRIPT" && ChmodExec "$FILE_MAIN_SCRIPT"
|
||||||
|
DlFile "$URL_VPN_UP" "$FILE_VPN_UP" && ChmodExec "$FILE_VPN_UP"
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallVPNConfig() {
|
||||||
|
local _if_vpn
|
||||||
|
$UCI_CMD set ruantiblock.config.proxy_mode="2"
|
||||||
|
_if_vpn=`$UCI_CMD get network.VPN.ifname`
|
||||||
|
if [ -z "$_if_vpn" ]; then
|
||||||
|
_if_vpn="tun0"
|
||||||
|
fi
|
||||||
|
$UCI_CMD set ruantiblock.config.if_vpn="$_if_vpn"
|
||||||
|
$UCI_CMD commit
|
||||||
|
}
|
||||||
|
|
||||||
|
TorrcSettings() {
|
||||||
|
local _lan_ip=`$UCI_CMD get network.lan.ipaddr | $AWK_CMD -F "/" '{print $1}'`
|
||||||
|
if [ -z "$_lan_ip" ]; then
|
||||||
|
_lan_ip="0.0.0.0"
|
||||||
|
fi
|
||||||
|
$AWK_CMD -v lan_ip="$_lan_ip" -v TOR_USER="$TOR_USER" '{
|
||||||
|
if($0 ~ /^([#]?TransPort|[#]?TransListenAddress|[#]?SOCKSPort)/ && $0 !~ "127.0.0.1") sub(/([0-9]{1,3}.){3}[0-9]{1,3}/, lan_ip, $0);
|
||||||
|
else if($0 ~ /^User/) $2 = TOR_USER;
|
||||||
|
print $0;
|
||||||
|
}' "$FILE_TORRC" > "${FILE_TORRC}.tmp" && mv -f "${FILE_TORRC}.tmp" "$FILE_TORRC"
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallTorConfig() {
|
||||||
|
local _if_lan
|
||||||
|
InstallPackages "tor" "tor-geoip"
|
||||||
|
BackupFile "$FILE_TORRC"
|
||||||
|
DlFile "$URL_TORRC" "$FILE_TORRC"
|
||||||
|
TorrcSettings
|
||||||
|
$UCI_CMD set ruantiblock.config.proxy_mode="1"
|
||||||
|
_if_lan=`$UCI_CMD get network.lan.ifname`
|
||||||
|
if [ -z "$_if_lan" ]; then
|
||||||
|
_if_lan="eth0"
|
||||||
|
fi
|
||||||
|
$UCI_CMD set ruantiblock.config.if_lan="$_if_lan"
|
||||||
|
$UCI_CMD commit
|
||||||
|
}
|
||||||
|
|
||||||
|
RamConfigPrepare() {
|
||||||
|
$AWK_CMD -v DATA_DIR_RAM="$DATA_DIR_RAM" '{
|
||||||
|
sub(/^DATA_DIR=.*$/, "DATA_DIR=\"" DATA_DIR_RAM "\"");
|
||||||
|
print $0;
|
||||||
|
}' "$FILE_CONFIG" > "${FILE_CONFIG}.tmp" && mv -f "${FILE_CONFIG}.tmp" "$FILE_CONFIG"
|
||||||
|
$AWK_CMD -v FILE_MAIN_SCRIPT="$FILE_MAIN_SCRIPT" '{
|
||||||
|
if($0 ~ /^exit 0/) next;
|
||||||
|
print $0;
|
||||||
|
} END { print FILE_MAIN_SCRIPT " update\nexit 0" }' "$RC_LOCAL" > "${RC_LOCAL}.tmp" && mv -f "${RC_LOCAL}.tmp" "$RC_LOCAL"
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallLuaModule() {
|
||||||
|
InstallPackages "lua" "luasocket" "luasec" "luabitop"
|
||||||
|
DlFile "$URL_PARSER_LUA" "$FILE_PARSER_LUA" && ChmodExec "$FILE_PARSER_LUA"
|
||||||
|
DlFile "$URL_LUA_IPTOOL" "$FILE_LUA_IPTOOL"
|
||||||
|
DlFile "$URL_LUA_SUM_IP" "$FILE_LUA_SUM_IP"
|
||||||
|
FileExists "$FILE_LUA_IDN" || DlFile "$URL_LUA_IDN" "$FILE_LUA_IDN"
|
||||||
|
$UCI_CMD set ruantiblock.config.bllist_module="/usr/bin/ruab_parser.lua"
|
||||||
|
$UCI_CMD commit
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallLuciApp() {
|
||||||
|
InstallPackages "luci-mod-rpc" "rpcd-mod-luci" "uhttpd-mod-ubus"
|
||||||
|
DlFile "$URL_LUCI_CONTROLLER" "$FILE_LUCI_CONTROLLER"
|
||||||
|
DlFile "$URL_LUCI_I18N_RU" "$FILE_LUCI_I18N_RU"
|
||||||
|
DlFile "$URL_LUCI_MENU" "$FILE_LUCI_MENU"
|
||||||
|
DlFile "$URL_LUCI_RPCD_ACL" "$FILE_LUCI_RPCD_ACL"
|
||||||
|
DlFile "$URL_LUCI_JS_CRON" "$FILE_LUCI_JS_CRON"
|
||||||
|
DlFile "$URL_LUCI_JS_INFO" "$FILE_LUCI_JS_INFO"
|
||||||
|
DlFile "$URL_LUCI_JS_SERVICE" "$FILE_LUCI_JS_SERVICE"
|
||||||
|
DlFile "$URL_LUCI_JS_SETTINGS" "$FILE_LUCI_JS_SETTINGS"
|
||||||
|
DlFile "$URL_LUCI_JS_TOOLS" "$FILE_LUCI_JS_TOOLS"
|
||||||
|
DlFile "$URL_LUCI_JS_LOG" "$FILE_LUCI_JS_LOG"
|
||||||
|
DlFile "$URL_LUCI_JS_STATUS" "$FILE_LUCI_JS_STATUS"
|
||||||
|
rm -f /tmp/luci-modulecache/*
|
||||||
|
rm -f /tmp/luci-indexcache
|
||||||
|
/etc/init.d/rpcd restart
|
||||||
|
/etc/init.d/uhttpd restart
|
||||||
|
}
|
||||||
|
|
||||||
|
RunAtStartup() {
|
||||||
|
$FILE_INIT_SCRIPT enable
|
||||||
|
}
|
||||||
|
|
||||||
|
AppStop() {
|
||||||
|
FileExists "$FILE_MAIN_SCRIPT" && $FILE_MAIN_SCRIPT destroy
|
||||||
|
}
|
||||||
|
|
||||||
|
AppStart() {
|
||||||
|
modprobe ip_set > /dev/null
|
||||||
|
modprobe ip_set_hash_ip > /dev/null
|
||||||
|
modprobe ip_set_hash_net > /dev/null
|
||||||
|
modprobe ip_set_list_set > /dev/null
|
||||||
|
modprobe xt_set > /dev/null
|
||||||
|
$FILE_INIT_SCRIPT start
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCronTask() {
|
||||||
|
echo "0 3 */3 * * ${FILE_MAIN_SCRIPT} update" >> /etc/crontabs/root
|
||||||
|
/etc/init.d/cron restart 2> /dev/null
|
||||||
|
/etc/init.d/cron enable
|
||||||
|
}
|
||||||
|
|
||||||
|
Reboot() {
|
||||||
|
reboot
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintBold() {
|
||||||
|
printf "\033[1m - ${1}\033[0m\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
InputError () {
|
||||||
|
printf "\033[1;31m Wrong input! Try again...\033[m\n"; $1
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmProxyMode() {
|
||||||
|
local _reply
|
||||||
|
printf " Select configuration [1: Tor | 2: VPN] (default: 1, quit: q) > "
|
||||||
|
read _reply
|
||||||
|
case $_reply in
|
||||||
|
1|"")
|
||||||
|
PROXY_MODE=1
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
PROXY_MODE=2
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
q|Q)
|
||||||
|
printf "Bye...\n"; exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
InputError ConfirmProxyMode
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmRamConfig() {
|
||||||
|
local _reply
|
||||||
|
printf " Would you like to set the RAM-configuration? [y|n] (default: n, quit: q) > "
|
||||||
|
read _reply
|
||||||
|
case $_reply in
|
||||||
|
y|Y)
|
||||||
|
RAM_CONFIG=1
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
n|N|"")
|
||||||
|
RAM_CONFIG=0
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
q|Q)
|
||||||
|
printf "Bye...\n"; exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
InputError ConfirmRamConfig
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmLuaModule() {
|
||||||
|
local _reply
|
||||||
|
printf " Would you like to install the lua module? [y|n] (default: y, quit: q) > "
|
||||||
|
read _reply
|
||||||
|
case $_reply in
|
||||||
|
y|Y|"")
|
||||||
|
LUA_MODULE=1
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
n|N)
|
||||||
|
LUA_MODULE=0
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
q|Q)
|
||||||
|
printf "Bye...\n"; exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
InputError ConfirmLuaModule
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmLuciApp() {
|
||||||
|
local _reply
|
||||||
|
printf " Would you like to install the LuCI application? [y|n] (default: y, quit: q) > "
|
||||||
|
read _reply
|
||||||
|
case $_reply in
|
||||||
|
y|Y|"")
|
||||||
|
LUCI_APP=1
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
n|N)
|
||||||
|
LUCI_APP=0
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
q|Q)
|
||||||
|
printf "Bye...\n"; exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
InputError ConfirmLuciApp
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmProcessing() {
|
||||||
|
local _reply
|
||||||
|
printf " Next, the installation will begin... Continue? [y|n] (default: y, quit: q) > "
|
||||||
|
read _reply
|
||||||
|
case $_reply in
|
||||||
|
y|Y|"")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
n|N|q|Q)
|
||||||
|
printf "Bye...\n"; exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
InputError ConfirmLuciApp
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmProxyMode
|
||||||
|
ConfirmRamConfig
|
||||||
|
ConfirmLuciApp
|
||||||
|
ConfirmProcessing
|
||||||
|
|
||||||
|
AppStop
|
||||||
|
PrintBold "Updating packages list..."
|
||||||
|
UpdatePackagesList
|
||||||
|
PrintBold "Saving current configuration..."
|
||||||
|
BackupCurrentConfig
|
||||||
|
PrintBold "Installing basic configuration..."
|
||||||
|
InstallBaseConfig
|
||||||
|
|
||||||
|
if [ $PROXY_MODE = 2 ]; then
|
||||||
|
PrintBold "Installing VPN configuration..."
|
||||||
|
InstallVPNConfig
|
||||||
|
else
|
||||||
|
PrintBold "Installing Tor configuration..."
|
||||||
|
InstallTorConfig
|
||||||
|
if `/etc/init.d/tor enabled`; then
|
||||||
|
/etc/init.d/tor restart
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $RAM_CONFIG = 1 ]; then
|
||||||
|
PrintBold "Setting the RAM-configuration..."
|
||||||
|
RamConfigPrepare
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $LUA_MODULE = 1 ]; then
|
||||||
|
PrintBold "Installing lua module..."
|
||||||
|
InstallLuaModule
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $LUCI_APP = 1 ]; then
|
||||||
|
PrintBold "Installing luci app..."
|
||||||
|
InstallLuciApp
|
||||||
|
fi
|
||||||
|
|
||||||
|
RunAtStartup
|
||||||
|
SetCronTask
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+161
@@ -0,0 +1,161 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
PREFIX=""
|
||||||
|
|
||||||
|
RUAB_CFG_DIR="${PREFIX}/etc/ruantiblock"
|
||||||
|
EXEC_DIR="${PREFIX}/usr/bin"
|
||||||
|
LUCI_ROOT="${PREFIX}/usr/lib/lua/luci"
|
||||||
|
HTDOCS_VIEW="${PREFIX}/www/luci-static/resources/view"
|
||||||
|
HTDOCS_RUAB="${HTDOCS_VIEW}/ruantiblock"
|
||||||
|
CRONTAB_FILE="/etc/crontabs/root"
|
||||||
|
DATA_DIR="${RUAB_CFG_DIR}/var"
|
||||||
|
DATA_DIR_RAM="/var/ruantiblock"
|
||||||
|
RC_LOCAL="/etc/rc.local"
|
||||||
|
DNSMASQ_CONF_LINK="/tmp/dnsmasq.d/ruantiblock.conf"
|
||||||
|
|
||||||
|
### ruantiblock
|
||||||
|
FILE_CONFIG="${RUAB_CFG_DIR}/ruantiblock.conf"
|
||||||
|
FILE_FQDN_FILTER="${RUAB_CFG_DIR}/fqdn_filter"
|
||||||
|
FILE_IP_FILTER="${RUAB_CFG_DIR}/ip_filter"
|
||||||
|
FILE_USER_ENTRIES="${RUAB_CFG_DIR}/user_entries"
|
||||||
|
FILE_CONFIG_SCRIPT="${RUAB_CFG_DIR}/scripts/config_script"
|
||||||
|
FILE_INFO_OUTPUT="${RUAB_CFG_DIR}/scripts/info_output"
|
||||||
|
FILE_IPT_FUNCTIONS="${RUAB_CFG_DIR}/scripts/ipt_functions"
|
||||||
|
FILE_START_SCRIPT="${RUAB_CFG_DIR}/scripts/start_script"
|
||||||
|
FILE_STOP_SCRIPT="${RUAB_CFG_DIR}/scripts/stop_script"
|
||||||
|
FILE_UCI_CONFIG="${PREFIX}/etc/config/ruantiblock"
|
||||||
|
FILE_INIT_SCRIPT="${PREFIX}/etc/init.d/ruantiblock"
|
||||||
|
FILE_MAIN_SCRIPT="${EXEC_DIR}/ruantiblock"
|
||||||
|
FILE_VPN_UP="${PREFIX}/etc/hotplug.d/iface/40-ruantiblock"
|
||||||
|
### tor
|
||||||
|
FILE_TORRC="${PREFIX}/etc/tor/torrc"
|
||||||
|
### ruantiblock-mod-lua
|
||||||
|
FILE_PARSER_LUA="${EXEC_DIR}/ruab_parser.lua"
|
||||||
|
FILE_LUA_IPTOOL="${PREFIX}/usr/lib/lua/iptool.lua"
|
||||||
|
FILE_LUA_SUM_IP="${PREFIX}/usr/lib/lua/ruab_sum_ip.lua"
|
||||||
|
FILE_LUA_IDN="${PREFIX}/usr/lib/lua/idn.lua"
|
||||||
|
### ruantiblock-mod-py
|
||||||
|
FILE_PARSER_PY="${EXEC_DIR}/ruab_parser.py"
|
||||||
|
FILE_PY_SUM_IP="${PREFIX}/usr/lib/python3.7/ruab_sum_ip.py"
|
||||||
|
### luci-app-ruantiblock
|
||||||
|
FILE_LUCI_CONTROLLER="${LUCI_ROOT}/controller/ruantiblock.lua"
|
||||||
|
FILE_LUCI_I18N_RU="${LUCI_ROOT}/i18n/ruantiblock.ru.lmo"
|
||||||
|
FILE_LUCI_MENU="${PREFIX}/usr/share/luci/menu.d/luci-app-ruantiblock.json"
|
||||||
|
FILE_LUCI_RPCD_ACL="${PREFIX}/usr/share/rpcd/acl.d/luci-app-ruantiblock.json"
|
||||||
|
FILE_LUCI_JS_CRON="${HTDOCS_RUAB}/cron.js"
|
||||||
|
FILE_LUCI_JS_INFO="${HTDOCS_RUAB}/info.js"
|
||||||
|
FILE_LUCI_JS_SERVICE="${HTDOCS_RUAB}/service.js"
|
||||||
|
FILE_LUCI_JS_SETTINGS="${HTDOCS_RUAB}/settings.js"
|
||||||
|
FILE_LUCI_JS_TOOLS="${HTDOCS_RUAB}/tools.js"
|
||||||
|
FILE_LUCI_JS_LOG="${HTDOCS_RUAB}/log.js"
|
||||||
|
FILE_LUCI_JS_STATUS="${HTDOCS_VIEW}/status/include/80_ruantiblock.js"
|
||||||
|
|
||||||
|
AWK_CMD="awk"
|
||||||
|
|
||||||
|
FileExists() {
|
||||||
|
test -e "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveFile() {
|
||||||
|
if [ -e "$1" ]; then
|
||||||
|
echo "Removing ${1}"
|
||||||
|
rm -f "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
RestoreTorConfig() {
|
||||||
|
[ -e "${FILE_TORRC}.bak" ] && mv -f "${FILE_TORRC}.bak" "$FILE_TORRC"
|
||||||
|
if `/etc/init.d/tor enabled`; then
|
||||||
|
/etc/init.d/tor restart
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveAppFiles() {
|
||||||
|
RestoreTorConfig
|
||||||
|
rm -rf "$DATA_DIR"
|
||||||
|
rm -rf "$DATA_DIR_RAM"
|
||||||
|
RemoveFile "$FILE_VPN_UP"
|
||||||
|
RemoveFile "$FILE_CONFIG_SCRIPT"
|
||||||
|
RemoveFile "$FILE_INFO_OUTPUT"
|
||||||
|
RemoveFile "$FILE_IPT_FUNCTIONS"
|
||||||
|
RemoveFile "$FILE_START_SCRIPT"
|
||||||
|
RemoveFile "$FILE_STOP_SCRIPT"
|
||||||
|
rmdir "${RUAB_CFG_DIR}/scripts" 2> /dev/null
|
||||||
|
RemoveFile "$FILE_UCI_CONFIG"
|
||||||
|
RemoveFile "$FILE_INIT_SCRIPT"
|
||||||
|
RemoveFile "$FILE_MAIN_SCRIPT"
|
||||||
|
RemoveFile "$FILE_SEARCH_SCRIPT"
|
||||||
|
RemoveFile "$FILE_PARSER_LUA"
|
||||||
|
RemoveFile "$FILE_LUA_IPTOOL"
|
||||||
|
RemoveFile "$FILE_LUA_SUM_IP"
|
||||||
|
RemoveFile "$FILE_PARSER_PY"
|
||||||
|
RemoveFile "$FILE_PY_SUM_IP"
|
||||||
|
RemoveFile "$FILE_LUCI_CONTROLLER"
|
||||||
|
RemoveFile "$FILE_LUCI_I18N_RU"
|
||||||
|
RemoveFile "$FILE_LUCI_MENU"
|
||||||
|
RemoveFile "$FILE_LUCI_RPCD_ACL"
|
||||||
|
RemoveFile "$FILE_LUCI_JS_CRON"
|
||||||
|
RemoveFile "$FILE_LUCI_JS_INFO"
|
||||||
|
RemoveFile "$FILE_LUCI_JS_SERVICE"
|
||||||
|
RemoveFile "$FILE_LUCI_JS_SETTINGS"
|
||||||
|
RemoveFile "$FILE_LUCI_JS_TOOLS"
|
||||||
|
RemoveFile "$FILE_LUCI_JS_LOG"
|
||||||
|
RemoveFile "$FILE_LUCI_JS_STATUS"
|
||||||
|
rmdir "$HTDOCS_RUAB" 2> /dev/null
|
||||||
|
rm -f /tmp/luci-modulecache/*
|
||||||
|
rm -f /tmp/luci-indexcache
|
||||||
|
/etc/init.d/rpcd restart
|
||||||
|
/etc/init.d/uhttpd restart
|
||||||
|
}
|
||||||
|
|
||||||
|
AppStop() {
|
||||||
|
rm -f $DNSMASQ_CONF_LINK
|
||||||
|
FileExists "$FILE_MAIN_SCRIPT" && $FILE_MAIN_SCRIPT destroy
|
||||||
|
}
|
||||||
|
|
||||||
|
DisableStartup() {
|
||||||
|
FileExists "$FILE_INIT_SCRIPT" && $FILE_INIT_SCRIPT disable
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveCronTask() {
|
||||||
|
$AWK_CMD -v FILE_MAIN_SCRIPT="$FILE_MAIN_SCRIPT" '$0 !~ FILE_MAIN_SCRIPT {
|
||||||
|
print $0;
|
||||||
|
}' "$CRONTAB_FILE" > "${CRONTAB_FILE}.tmp" && mv -f "${CRONTAB_FILE}.tmp" "$CRONTAB_FILE"
|
||||||
|
/etc/init.d/cron restart
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveRcLocalEntry() {
|
||||||
|
$AWK_CMD -v FILE_MAIN_SCRIPT="$FILE_MAIN_SCRIPT" '$0 !~ FILE_MAIN_SCRIPT {
|
||||||
|
print $0;
|
||||||
|
}' "$RC_LOCAL" > "${RC_LOCAL}.tmp" && mv -f "${RC_LOCAL}.tmp" "$RC_LOCAL"
|
||||||
|
}
|
||||||
|
|
||||||
|
InputError () {
|
||||||
|
printf "\033[1;31m Wrong input! Try again...\033[m\n"; $1
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmRemove() {
|
||||||
|
local _reply
|
||||||
|
printf " Files will be removed... Continue? [y|n] (default: y, quit: q) > "
|
||||||
|
read _reply
|
||||||
|
case $_reply in
|
||||||
|
y|Y|"")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
n|N|q|Q)
|
||||||
|
printf "Bye...\n"; exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
InputError ConfirmRemove
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmRemove
|
||||||
|
AppStop
|
||||||
|
DisableStartup
|
||||||
|
RemoveCronTask
|
||||||
|
RemoveRcLocalEntry
|
||||||
|
RemoveAppFiles
|
||||||
|
|
||||||
|
exit 0
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
@@ -0,0 +1,220 @@
|
|||||||
|
'use strict';
|
||||||
|
'require fs';
|
||||||
|
'require ui';
|
||||||
|
'require view.ruantiblock.tools as tools';
|
||||||
|
|
||||||
|
let crontab_regexp = new RegExp(`^(\\*?\\/?(\\d){0,2}\\s){5}${tools.exec_path} update(\n)?`, 'gm');
|
||||||
|
let current_crontab_content;
|
||||||
|
|
||||||
|
function to_dd(n){
|
||||||
|
return String(n).replace(/^(\d)$/, "0$1");
|
||||||
|
};
|
||||||
|
|
||||||
|
function cron_status_string(s) {
|
||||||
|
return s || _('No Shedule');
|
||||||
|
}
|
||||||
|
|
||||||
|
function pick_cron_task(content) {
|
||||||
|
if(!content){
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let current_tasks = content.match(crontab_regexp) || [];
|
||||||
|
return current_tasks.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
function set_cron_status(value) {
|
||||||
|
document.getElementById('cron_status').value = cron_status_string(value);
|
||||||
|
document.getElementById("btn_cron_del").style.visibility = (value) ? 'visible' : 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
function write_cron_file() {
|
||||||
|
let btn_cron_add = document.getElementById('btn_cron_add');
|
||||||
|
let btn_cron_del = document.getElementById('btn_cron_del');
|
||||||
|
|
||||||
|
if(!current_crontab_content) {
|
||||||
|
ui.addNotification(null, E('p', _('No changes to save.')));
|
||||||
|
btn_cron_add.disabled = false;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
return fs.write(tools.crontab_file, current_crontab_content).then(rc => {
|
||||||
|
ui.addNotification(null, E('p',_('Changes have been saved.')), 'info');
|
||||||
|
set_cron_status(pick_cron_task(current_crontab_content));
|
||||||
|
}).then(() => {
|
||||||
|
return fs.exec('/etc/init.d/cron', [ 'enabled' ]).then(res => {
|
||||||
|
if(res.code !== 0) {
|
||||||
|
return fs.exec('/etc/init.d/cron', [ 'enable' ]);
|
||||||
|
};
|
||||||
|
}).catch(e => {
|
||||||
|
ui.addNotification(null, E('p', _('Unable to execute or read contents')
|
||||||
|
+ ': %s<br />[ %s ]'.format(e.message, '/etc/init.d/cron')));
|
||||||
|
});
|
||||||
|
}).finally(() => {
|
||||||
|
return fs.exec('/etc/init.d/cron', [ 'restart' ]).catch(e => {
|
||||||
|
ui.addNotification(null, E('p', _('Unable to execute or read contents')
|
||||||
|
+ ': %s<br />[ %s ]'.format(e.message, '/etc/init.d/cron')));
|
||||||
|
});
|
||||||
|
}).catch(e => {
|
||||||
|
ui.addNotification(null, E('p', _('Unable to save the changes')
|
||||||
|
+ ': %s<br />[ %s ]'.format(
|
||||||
|
e.message, tools.crontab_file
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function del_cron_schedule(ev) {
|
||||||
|
if(current_crontab_content) {
|
||||||
|
current_crontab_content = current_crontab_content.replace(crontab_regexp, "");
|
||||||
|
};
|
||||||
|
return write_cron_file();
|
||||||
|
};
|
||||||
|
|
||||||
|
function set_cron_schedule(ev) {
|
||||||
|
let hour_interval = document.getElementById('cron_hour_interval').value;
|
||||||
|
let day_interval = document.getElementById('cron_day_interval').value;
|
||||||
|
let hour = document.getElementById('cron_hour').value;
|
||||||
|
let min = document.getElementById('cron_min').value;
|
||||||
|
let task_string = '%s %s %s * * %s update\n'.format(
|
||||||
|
min,
|
||||||
|
(!hour_interval) ? hour : (hour_interval == "1") ? '*' : '*/' + hour_interval,
|
||||||
|
(hour_interval || day_interval == "1") ? '*' : '*/' + day_interval,
|
||||||
|
tools.exec_path
|
||||||
|
);
|
||||||
|
if(current_crontab_content) {
|
||||||
|
current_crontab_content = current_crontab_content.replace(crontab_regexp, "") + task_string;
|
||||||
|
};
|
||||||
|
return write_cron_file();
|
||||||
|
};
|
||||||
|
|
||||||
|
function onchange_hour_interval(e) {
|
||||||
|
let value = e.target.value;
|
||||||
|
let bool = (value != '');
|
||||||
|
let cron_hour = document.getElementById('cron_hour');
|
||||||
|
let cron_day_interval = document.getElementById('cron_day_interval');
|
||||||
|
cron_hour.disabled = bool;
|
||||||
|
cron_day_interval.disabled = bool;
|
||||||
|
|
||||||
|
// For luci-theme-material
|
||||||
|
if(bool) {
|
||||||
|
cron_hour.style.opacity = '50%';
|
||||||
|
cron_day_interval.style.opacity = '50%';
|
||||||
|
} else {
|
||||||
|
cron_hour.style.opacity = '100%';
|
||||||
|
cron_day_interval.style.opacity = '100%';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return L.view.extend({
|
||||||
|
load: function() {
|
||||||
|
return fs.read(tools.crontab_file).catch(e => {
|
||||||
|
ui.addNotification(null, E('p', _('Unable to read the contents')
|
||||||
|
+ ': %s<br />[ %s ]'.format(
|
||||||
|
e.message, tools.crontab_file
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(content) {
|
||||||
|
current_crontab_content = content;
|
||||||
|
let current_task = pick_cron_task(content);
|
||||||
|
|
||||||
|
let cron_status = E('textarea', {
|
||||||
|
'id': 'cron_status',
|
||||||
|
'name': 'cron_status',
|
||||||
|
'style': 'width:30em; padding:5px 10px 5px 10px !important; vertical-align:middle; resize:none !important;',
|
||||||
|
'readonly': 'readonly',
|
||||||
|
'wrap': 'off',
|
||||||
|
'rows': 2,
|
||||||
|
}, cron_status_string(current_task));
|
||||||
|
|
||||||
|
let btn_cron_del = E('button', {
|
||||||
|
'class': 'cbi-button btn cbi-button-reset',
|
||||||
|
'id': 'btn_cron_del',
|
||||||
|
'name': 'btn_cron_del',
|
||||||
|
}, _('Reset'));
|
||||||
|
btn_cron_del.onclick = ui.createHandlerFn(this, del_cron_schedule);
|
||||||
|
btn_cron_del.style.visibility = (current_task) ? 'visible' : 'hidden';
|
||||||
|
|
||||||
|
let status_header = E('div', { 'class': 'cbi-section-node' }, [
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title', 'for': 'cron_status' },
|
||||||
|
_('Current schedule')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [ cron_status, ' ', btn_cron_del ]),
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
let layout = E('div', { 'class': 'cbi-section-node' });
|
||||||
|
|
||||||
|
function layout_append(elem, title, descr) {
|
||||||
|
descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : '';
|
||||||
|
layout.append(
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title', 'for': elem.id || null },
|
||||||
|
title),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [ elem, descr ]),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
layout_append(E('b', {}, _('Interval')));
|
||||||
|
|
||||||
|
let cron_hour_interval = E('select',
|
||||||
|
{ 'id': 'cron_hour_interval', 'style': 'width:60px !important; min-width:60px !important' }, [
|
||||||
|
E('option', { 'value': '' }, ''),
|
||||||
|
E('option', { 'value': '1' }, '∗')
|
||||||
|
]);
|
||||||
|
for(let i = 2; i <= 12 ; i += 2) {
|
||||||
|
cron_hour_interval.append(E('option', { 'value': String(i) }, '∗/' + i));
|
||||||
|
};
|
||||||
|
layout_append(cron_hour_interval, _('Hour'));
|
||||||
|
cron_hour_interval.onchange = onchange_hour_interval;
|
||||||
|
|
||||||
|
let cron_day_interval = E('select',
|
||||||
|
{ 'id': 'cron_day_interval', 'style': 'width:60px !important; min-width:60px !important' },
|
||||||
|
E('option', { 'value': '1' }, '∗')
|
||||||
|
);
|
||||||
|
for(let i = 2; i < 8 ; i++) {
|
||||||
|
cron_day_interval.append(E('option', { 'value': String(i) }, '∗/' + i));
|
||||||
|
};
|
||||||
|
cron_day_interval.append(E('option', { 'value': '14' }, '∗/14'));
|
||||||
|
cron_day_interval.append(E('option', { 'value': '28' }, '∗/28'));
|
||||||
|
layout_append(cron_day_interval, _('Day'));
|
||||||
|
|
||||||
|
layout_append(E('b', {}, _('Time')));
|
||||||
|
|
||||||
|
let cron_hour = E('select',
|
||||||
|
{ 'id': 'cron_hour', 'style': 'width:60px !important; min-width:60px !important' });
|
||||||
|
for(let i = 0; i < 24 ; i++) {
|
||||||
|
cron_hour.append(E('option', { 'value': String(i) }, to_dd(i)));
|
||||||
|
};
|
||||||
|
layout_append(cron_hour, _('Hour'));
|
||||||
|
|
||||||
|
let cron_min = E('select',
|
||||||
|
{ 'id': 'cron_min', 'style': 'width:60px !important; min-width:60px !important' });
|
||||||
|
for(let i = 0; i < 60 ; i++) {
|
||||||
|
cron_min.append(E('option', { 'value': String(i) }, to_dd(i)));
|
||||||
|
};
|
||||||
|
layout_append(cron_min, _('Minute'));
|
||||||
|
|
||||||
|
let btn_cron_add = E('button', {
|
||||||
|
'class': 'btn cbi-button-save',
|
||||||
|
'id': 'btn_cron_add',
|
||||||
|
'name': 'btn_cron_add'
|
||||||
|
}, _('Set'));
|
||||||
|
btn_cron_add.onclick = ui.createHandlerFn(this, set_cron_schedule);
|
||||||
|
layout_append(btn_cron_add);
|
||||||
|
|
||||||
|
return E([
|
||||||
|
E('h2',
|
||||||
|
{ 'class': 'fade-in' }, _('Ruantiblock') + ' - ' + _('Blacklist updates') + ' (cron)'),
|
||||||
|
E('div', { 'class': 'cbi-section-descr fade-in' }),
|
||||||
|
E('div', { 'class': 'cbi-section fade-in' }, status_header),
|
||||||
|
E('div', { 'class': 'cbi-section fade-in' }, layout),
|
||||||
|
]);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSave: null,
|
||||||
|
handleSaveApply: null,
|
||||||
|
handleReset: null,
|
||||||
|
});
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
'use strict';
|
||||||
|
'require fs';
|
||||||
|
'require ui';
|
||||||
|
'require view.ruantiblock.tools as tools';
|
||||||
|
|
||||||
|
return L.view.extend({
|
||||||
|
poll_info: function() {
|
||||||
|
return fs.exec_direct(tools.exec_path, [ 'html-info' ], 'json').catch(e => {
|
||||||
|
ui.addNotification(null, E('p', _('Unable to execute or read contents')
|
||||||
|
+ ': %s<br />[ %s ]'.format(e.message, tools.exec_path)
|
||||||
|
));
|
||||||
|
L.Poll.stop();
|
||||||
|
}).then(data => {
|
||||||
|
if(!data) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
} catch(err) {};
|
||||||
|
|
||||||
|
if(data.status === 'enabled') {
|
||||||
|
let date = document.getElementById('last_blacklist_update.date');
|
||||||
|
|
||||||
|
if(data.last_blacklist_update.status) {
|
||||||
|
if(date) {
|
||||||
|
date.textContent = data.last_blacklist_update.date;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ip = document.getElementById('last_blacklist_update.ip');
|
||||||
|
if(ip) {
|
||||||
|
ip.textContent = data.last_blacklist_update.ip;
|
||||||
|
};
|
||||||
|
|
||||||
|
let cidr = document.getElementById('last_blacklist_update.cidr');
|
||||||
|
if(cidr) {
|
||||||
|
cidr.textContent = data.last_blacklist_update.cidr;
|
||||||
|
};
|
||||||
|
|
||||||
|
let fqdn = document.getElementById('last_blacklist_update.fqdn');
|
||||||
|
if(fqdn) {
|
||||||
|
fqdn.textContent = data.last_blacklist_update.fqdn;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if(date) {
|
||||||
|
date.textContent = _('No data');
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if(data.iptables) {
|
||||||
|
for(let [k, v] of Object.entries(data.iptables)) {
|
||||||
|
if(k === '_dummy') continue;
|
||||||
|
|
||||||
|
let elem = document.getElementById('iptables.' + k);
|
||||||
|
if(elem) {
|
||||||
|
elem.textContent = v;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if(data.ipset) {
|
||||||
|
for(let [k, v] of Object.entries(data.ipset)) {
|
||||||
|
if(k === '_dummy') continue;
|
||||||
|
|
||||||
|
let elem0 = document.getElementById('ipset.' + k + '.' + '0');
|
||||||
|
let elem1 = document.getElementById('ipset.' + k + '.' + '1');
|
||||||
|
if(elem0 && elem1) {
|
||||||
|
elem0.textContent = v[0];
|
||||||
|
elem1.textContent = v[1];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if(L.Poll.active()) {
|
||||||
|
L.Poll.stop();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
return fs.exec_direct(tools.exec_path, [ 'html-info' ], 'json').catch(e => {
|
||||||
|
ui.addNotification(null, E('p', _('Unable to execute or read contents')
|
||||||
|
+ ': %s<br />[ %s ]'.format(e.message, tools.exec_path)
|
||||||
|
));
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(data) {
|
||||||
|
if(!data) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
} catch(err) {};
|
||||||
|
|
||||||
|
let update_status = null,
|
||||||
|
iptables = null,
|
||||||
|
ipset = null;
|
||||||
|
if(data) {
|
||||||
|
if(data.status === 'enabled') {
|
||||||
|
update_status = E('div', { 'class': 'table' });
|
||||||
|
|
||||||
|
if(data.last_blacklist_update.status) {
|
||||||
|
update_status.append(
|
||||||
|
E('div', { 'class': 'tr' }, [
|
||||||
|
E('div', { 'class': 'td left', 'width': '33%' },
|
||||||
|
_('Last blacklist update')),
|
||||||
|
E('div', { 'class': 'td left', 'id': 'last_blacklist_update.date' },
|
||||||
|
data.last_blacklist_update.date),
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'tr' }, [
|
||||||
|
E('div', { 'class': 'td left', 'width': '33%' }, 'IP'),
|
||||||
|
E('div', { 'class': 'td left', 'id': 'last_blacklist_update.ip' },
|
||||||
|
data.last_blacklist_update.ip),
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'tr' }, [
|
||||||
|
E('div', { 'class': 'td left', 'width': '33%' }, 'CIDR'),
|
||||||
|
E('div', { 'class': 'td left', 'id': 'last_blacklist_update.cidr' },
|
||||||
|
data.last_blacklist_update.cidr),
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'tr' }, [
|
||||||
|
E('div', { 'class': 'td left', 'width': '33%' }, 'FQDN'),
|
||||||
|
E('div', { 'class': 'td left', 'id': 'last_blacklist_update.fqdn' },
|
||||||
|
data.last_blacklist_update.fqdn),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
update_status.append(
|
||||||
|
E('div', { 'class': 'tr' }, [
|
||||||
|
E('div', { 'class': 'td left', 'width': '33%' },
|
||||||
|
_('Last blacklist update')),
|
||||||
|
E('div', { 'class': 'td left' }, _('No data')),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if(data.iptables) {
|
||||||
|
let table_iptables = E('div', { 'class': 'table' }, [
|
||||||
|
E('div', { 'class': 'tr table-titles' }, [
|
||||||
|
E('div', { 'class': 'th left', 'width': '33%' },
|
||||||
|
_('Match-set')),
|
||||||
|
E('div', { 'class': 'th left' }, _('Bytes')),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for(let [k, v] of Object.entries(data.iptables)) {
|
||||||
|
if(k === '_dummy') continue;
|
||||||
|
|
||||||
|
table_iptables.append(
|
||||||
|
E('div', { 'class': 'tr' }, [
|
||||||
|
E('div', { 'class': 'td left', 'width': '33%' },
|
||||||
|
k),
|
||||||
|
E('div', { 'class': 'td left', 'id': 'iptables.' + k },
|
||||||
|
v),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
iptables = E([
|
||||||
|
E('h3', {}, _('Iptables rules')),
|
||||||
|
table_iptables,
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
if(data.ipset) {
|
||||||
|
let table_ipset = E('div', { 'class': 'table' },
|
||||||
|
E('div', { 'class': 'tr table-titles' }, [
|
||||||
|
E('div', { 'class': 'th left', 'width': '33%' }, _('Name')),
|
||||||
|
E('div', { 'class': 'th left' }, _('Size in memory')),
|
||||||
|
E('div', { 'class': 'th left' }, _('Number of entries')),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
for(let [k, v] of Object.entries(data.ipset)) {
|
||||||
|
if(k === '_dummy') continue;
|
||||||
|
|
||||||
|
table_ipset.append(
|
||||||
|
E('div', { 'class': 'tr' }, [
|
||||||
|
E('div', { 'class': 'td left', 'width': '33%' }, k),
|
||||||
|
E('div', { 'class': 'td left', 'id': 'ipset.' + k + '.' + '0' },
|
||||||
|
v[0]),
|
||||||
|
E('div', { 'class': 'td left', 'id': 'ipset.' + k + '.' + '1' },
|
||||||
|
v[1]),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ipset = E([
|
||||||
|
E('h3', {}, _('Ipset')),
|
||||||
|
table_ipset,
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
L.Poll.add(this.poll_info);
|
||||||
|
} else {
|
||||||
|
update_status = E('em', {}, _('Status') + ' : ' + _('disabled'));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return E([
|
||||||
|
E('h2', { 'class': 'fade-in' }, _('Ruantiblock') + ' - ' + _('Statistics')),
|
||||||
|
E('div', { 'class': 'cbi-section-descr fade-in' }),
|
||||||
|
E('div', { 'class': 'cbi-section fade-in' },
|
||||||
|
E('div', { 'class': 'cbi-section-node' }, update_status)
|
||||||
|
),
|
||||||
|
E('div', { 'class': 'cbi-section fade-in' },
|
||||||
|
E('div', { 'class': 'cbi-section-node' }, iptables)
|
||||||
|
),
|
||||||
|
E('div', { 'class': 'cbi-section fade-in' },
|
||||||
|
E('div', { 'class': 'cbi-section-node' }, ipset)
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSave: null,
|
||||||
|
handleSaveApply: null,
|
||||||
|
handleReset: null,
|
||||||
|
});
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
'use strict';
|
||||||
|
'require fs';
|
||||||
|
'require ui';
|
||||||
|
'require view.ruantiblock.tools as tools';
|
||||||
|
|
||||||
|
let log_regexp = new RegExp(`^.*(user\\.notice ${tools.app_name}).*$`, 'gm');
|
||||||
|
|
||||||
|
return L.view.extend({
|
||||||
|
tail_default: 25,
|
||||||
|
|
||||||
|
parse_log_data: function(logdata) {
|
||||||
|
return logdata.trim().match(log_regexp);
|
||||||
|
},
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
return Promise.all([
|
||||||
|
L.resolveDefault(fs.stat('/sbin/logread'), null),
|
||||||
|
L.resolveDefault(fs.stat('/usr/sbin/logread'), null),
|
||||||
|
]).then(stat => {
|
||||||
|
let logger = (stat[0]) ? stat[0].path : (stat[1]) ? stat[1].path : null;
|
||||||
|
|
||||||
|
if(logger) {
|
||||||
|
return fs.exec_direct(logger, [ '-e', tools.app_name ]).catch(e => {
|
||||||
|
ui.addNotification(null, E('p', _('Unable to execute or read contents')
|
||||||
|
+ ': %s<br />[ %s ]'.format(e.message, logger)
|
||||||
|
));
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(logdata) {
|
||||||
|
let nav_btns_top = '120px';
|
||||||
|
let loglines = this.parse_log_data(logdata);
|
||||||
|
|
||||||
|
let log_textarea = E('textarea', {
|
||||||
|
'id': 'syslog',
|
||||||
|
'class': 'cbi-input-textarea',
|
||||||
|
'style': 'width:100% !important; padding: 0 0 0 45px; font-size:12px',
|
||||||
|
'readonly': 'readonly',
|
||||||
|
'wrap': 'off',
|
||||||
|
'rows': this.tail_default,
|
||||||
|
'spellcheck': 'false',
|
||||||
|
}, [ loglines.slice(-this.tail_default).join('\n') ]);
|
||||||
|
|
||||||
|
let tail_value = E('input', {
|
||||||
|
'id': 'tail_value',
|
||||||
|
'name': 'tail_value',
|
||||||
|
'type': 'text',
|
||||||
|
'form': 'log_form',
|
||||||
|
'class': 'cbi-input-text',
|
||||||
|
'style': 'width:4em !important; min-width:4em !important',
|
||||||
|
'maxlength': 5,
|
||||||
|
});
|
||||||
|
tail_value.value = this.tail_default;
|
||||||
|
ui.addValidator(tail_value, 'uinteger', true);
|
||||||
|
|
||||||
|
let log_filter = E('input', {
|
||||||
|
'id': 'log_filter',
|
||||||
|
'name': 'log_filter',
|
||||||
|
'type': 'text',
|
||||||
|
'form': 'log_form',
|
||||||
|
'class': 'cbi-input-text',
|
||||||
|
'style': 'margin-left:1em !important; width:16em !important; min-width:16em !important',
|
||||||
|
'placeholder': _('Message filter'),
|
||||||
|
'data-tooltip': _('Filter messages with a regexp'),
|
||||||
|
});
|
||||||
|
|
||||||
|
let log_form_submit_btn = E('input', {
|
||||||
|
'type': 'submit',
|
||||||
|
'form': 'log_form',
|
||||||
|
'class': 'cbi-button btn',
|
||||||
|
'style': 'margin-left:1em !important; vertical-align:middle',
|
||||||
|
'value': _('Apply'),
|
||||||
|
'click': ev => ev.target.blur(),
|
||||||
|
});
|
||||||
|
|
||||||
|
function set_log_tail(c_arr) {
|
||||||
|
let tail_num_val = tail_value.value;
|
||||||
|
if(tail_num_val && tail_num_val > 0 && c_arr) {
|
||||||
|
return c_arr.slice(-tail_num_val);
|
||||||
|
};
|
||||||
|
return c_arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_log_filter(c_arr) {
|
||||||
|
let f_pattern = log_filter.value;
|
||||||
|
if(!f_pattern) {
|
||||||
|
return c_arr;
|
||||||
|
};
|
||||||
|
let f_arr = [];
|
||||||
|
try {
|
||||||
|
f_arr = c_arr.filter(s => new RegExp(f_pattern.toLowerCase()).test(s.toLowerCase()));
|
||||||
|
} catch(err) {
|
||||||
|
if(err.name === 'SyntaxError') {
|
||||||
|
ui.addNotification(null,
|
||||||
|
E('p', {}, _('Wrong regular expression') + ': ' + err.message));
|
||||||
|
return c_arr;
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if(f_arr.length === 0) {
|
||||||
|
f_arr.push(_('No matches...'));
|
||||||
|
};
|
||||||
|
return f_arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return E([
|
||||||
|
E('h2', { 'id': 'log_title', 'class': 'fade-in' }, _('Ruantiblock') + ' - ' + _('Log')),
|
||||||
|
E('div', { 'class': 'cbi-section-descr fade-in' }),
|
||||||
|
E('div', { 'class': 'cbi-section fade-in' },
|
||||||
|
E('div', { 'class': 'cbi-section-node' },
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title', 'for': 'tail_value' },
|
||||||
|
_('Show only the last messages')),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [
|
||||||
|
tail_value,
|
||||||
|
E('input', {
|
||||||
|
'type': 'button',
|
||||||
|
'form': 'log_form',
|
||||||
|
'class': 'cbi-button btn cbi-button-reset',
|
||||||
|
'value': 'Χ',
|
||||||
|
'click': ev => {
|
||||||
|
tail_value.value = null;
|
||||||
|
log_form_submit_btn.click();
|
||||||
|
ev.target.blur();
|
||||||
|
},
|
||||||
|
|
||||||
|
}),
|
||||||
|
log_filter,
|
||||||
|
E('input', {
|
||||||
|
'type': 'button',
|
||||||
|
'form': 'log_form',
|
||||||
|
'class': 'cbi-button btn cbi-button-reset',
|
||||||
|
'value': 'Χ',
|
||||||
|
'click': ev => {
|
||||||
|
log_filter.value = null;
|
||||||
|
log_form_submit_btn.click();
|
||||||
|
ev.target.blur();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
log_form_submit_btn,
|
||||||
|
E('form', {
|
||||||
|
'id': 'log_form',
|
||||||
|
'name': 'log_form',
|
||||||
|
'style': 'display:inline-block; margin-left:1em !important',
|
||||||
|
'submit': ui.createHandlerFn(this, function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
let form_elems = Array.from(document.forms.log_form.elements);
|
||||||
|
form_elems.forEach(e => e.disabled = true);
|
||||||
|
|
||||||
|
return this.load().then(logdata => {
|
||||||
|
let loglines = set_log_filter(set_log_tail(
|
||||||
|
this.parse_log_data(logdata)));
|
||||||
|
log_textarea.value = loglines.join('\n');
|
||||||
|
}).finally(() => {
|
||||||
|
form_elems.forEach(e => e.disabled = false);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
}, E('span', {}, ' ')),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
),
|
||||||
|
E('div', { 'class': 'cbi-section fade-in' },
|
||||||
|
E('div', { 'class': 'cbi-section-node' },
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('div', { 'style': 'position:fixed' }, [
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn',
|
||||||
|
'style': 'position:relative; display:block; margin:0 !important; left:1px; top:'
|
||||||
|
+ nav_btns_top,
|
||||||
|
'click': ev => {
|
||||||
|
log_textarea.scrollTop = 0;
|
||||||
|
ev.target.blur();
|
||||||
|
},
|
||||||
|
}, '↑'),
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn',
|
||||||
|
'style': 'position:relative; display:block; margin:0 !important; margin-top:1px !important; left:1px; top:'
|
||||||
|
+ nav_btns_top,
|
||||||
|
'click': ev => {
|
||||||
|
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||||
|
ev.target.blur();
|
||||||
|
},
|
||||||
|
}, '↓'),
|
||||||
|
]),
|
||||||
|
log_textarea,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSaveApply: null,
|
||||||
|
handleSave: null,
|
||||||
|
handleReset: null,
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
'use strict';
|
||||||
|
'require fs';
|
||||||
|
'require uci';
|
||||||
|
'require ui';
|
||||||
|
'require view.ruantiblock.tools as tools';
|
||||||
|
|
||||||
|
const btn_style_neutral = 'btn'
|
||||||
|
const btn_style_action = 'btn cbi-button-action';
|
||||||
|
const btn_style_save = 'btn cbi-button-save important';
|
||||||
|
const btn_style_reset = 'btn cbi-button-reset important';
|
||||||
|
const btn_style_warning = 'btn cbi-button-negative important'
|
||||||
|
let status_token_value;
|
||||||
|
|
||||||
|
function disable_buttons(bool, btn, elems=[]) {
|
||||||
|
let btn_start = elems[1] || document.getElementById("btn_start");
|
||||||
|
let btn_destroy = elems[5] || document.getElementById("btn_destroy");
|
||||||
|
let btn_enable = elems[2] || document.getElementById("btn_enable");
|
||||||
|
let btn_update = elems[4] || document.getElementById("btn_update");
|
||||||
|
let btn_tp = elems[3] || document.getElementById("btn_tp");
|
||||||
|
|
||||||
|
btn_start.disabled = bool;
|
||||||
|
btn_update.disabled = bool;
|
||||||
|
btn_destroy.disabled = bool;
|
||||||
|
if(btn === btn_update) {
|
||||||
|
btn_enable.disabled = false;
|
||||||
|
} else {
|
||||||
|
btn_enable.disabled = bool;
|
||||||
|
};
|
||||||
|
if(btn_tp) {
|
||||||
|
btn_tp.disabled = bool
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_app_status() {
|
||||||
|
return Promise.all([
|
||||||
|
fs.exec(tools.exec_path, [ 'raw-status' ]),
|
||||||
|
fs.exec(tools.exec_path, [ 'total-proxy-status' ]),
|
||||||
|
fs.exec(tools.exec_path, [ 'vpn-route-status' ]),
|
||||||
|
fs.exec(tools.init_path, [ 'enabled' ]),
|
||||||
|
L.resolveDefault(fs.read(tools.token_file), 0),
|
||||||
|
uci.load(tools.app_name),
|
||||||
|
]).catch(e => {
|
||||||
|
ui.addNotification(null, E('p', _('Unable to execute or read contents')
|
||||||
|
+ ': %s<br />[ %s | %s | %s ]'.format(
|
||||||
|
e.message, tools.exec_path, tools.init_path, 'uci.ruantiblock'
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_app_status(status_array, elems=[], force_app_code) {
|
||||||
|
let section = uci.get(tools.app_name, 'config');
|
||||||
|
if(!status_array || typeof(section) !== 'object') {
|
||||||
|
(elems[0] || document.getElementById("status")).innerHTML = tools.make_status_string(1);
|
||||||
|
ui.addNotification(null, E('p', _('Unable to read the contents')
|
||||||
|
+ ': set_app_status()'));
|
||||||
|
disable_buttons(true, null, elems);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let app_status_code = (force_app_code) ? force_app_code : status_array[0].code;
|
||||||
|
let tp_status_code = status_array[1].code;
|
||||||
|
let vpn_route_status_code = status_array[2].code;
|
||||||
|
let enabled_flag = status_array[3].code;
|
||||||
|
|
||||||
|
let proxy_local_clients = section.proxy_local_clients;
|
||||||
|
let proxy_mode = section.proxy_mode;
|
||||||
|
let bllist_mode = section.bllist_mode;
|
||||||
|
let bllist_module = section.bllist_module;
|
||||||
|
let bllist_source = section.bllist_source;
|
||||||
|
|
||||||
|
let btn_enable = elems[2] || document.getElementById('btn_enable');
|
||||||
|
if(enabled_flag == 0) {
|
||||||
|
btn_enable.onclick = ui.createHandlerFn(this, button_action, 'disable');
|
||||||
|
btn_enable.textContent = _('Disable');
|
||||||
|
btn_enable.className = btn_style_reset;
|
||||||
|
} else {
|
||||||
|
btn_enable.onclick = ui.createHandlerFn(this, button_action, 'enable');
|
||||||
|
btn_enable.textContent = _('Enable');
|
||||||
|
btn_enable.className = btn_style_save;
|
||||||
|
};
|
||||||
|
|
||||||
|
let btn_tp = elems[3] || document.getElementById('btn_tp');
|
||||||
|
if(btn_tp) {
|
||||||
|
if(tp_status_code == 0) {
|
||||||
|
btn_tp.onclick = ui.createHandlerFn(this, button_action, 'total-proxy-off');
|
||||||
|
btn_tp.textContent = _('Disable');
|
||||||
|
btn_tp.className = btn_style_reset;
|
||||||
|
} else {
|
||||||
|
btn_tp.onclick = ui.createHandlerFn(this, button_action, 'total-proxy-on');
|
||||||
|
btn_tp.textContent = _('Enable');
|
||||||
|
btn_tp.className = btn_style_save;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let btn_start = elems[1] || document.getElementById("btn_start");
|
||||||
|
let btn_update = elems[4] || document.getElementById("btn_update");
|
||||||
|
let btn_destroy = elems[5] || document.getElementById("btn_destroy");
|
||||||
|
|
||||||
|
function btn_start_state_on() {
|
||||||
|
btn_start.onclick = ui.createHandlerFn(this, button_action, 'stop');
|
||||||
|
btn_start.textContent = _('Disable');
|
||||||
|
btn_start.className = btn_style_reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
function btn_start_state_off() {
|
||||||
|
btn_start.onclick = ui.createHandlerFn(this, button_action, 'start');
|
||||||
|
btn_start.textContent = _('Enable');
|
||||||
|
btn_start.className = btn_style_action;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(app_status_code == 0) {
|
||||||
|
disable_buttons(false, null, elems);
|
||||||
|
btn_start_state_on();
|
||||||
|
btn_destroy.disabled = false;
|
||||||
|
btn_update.disabled = false;
|
||||||
|
if(btn_tp) {
|
||||||
|
btn_tp.disabled = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if(app_status_code == 2) {
|
||||||
|
disable_buttons(false, null, elems);
|
||||||
|
btn_start_state_off();
|
||||||
|
btn_update.disabled = true;
|
||||||
|
if(btn_tp) {
|
||||||
|
btn_tp.disabled = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if(app_status_code == 3) {
|
||||||
|
btn_start_state_off();
|
||||||
|
disable_buttons(true, btn_start, elems);
|
||||||
|
}
|
||||||
|
else if(app_status_code == 4) {
|
||||||
|
btn_start_state_on();
|
||||||
|
disable_buttons(true, btn_update, elems);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ui.addNotification(null, E('p', _('Error')
|
||||||
|
+ ' %s: return code = %s'.format(tools.exec_path, app_status_code)));
|
||||||
|
disable_buttons(true, null, elems);
|
||||||
|
};
|
||||||
|
|
||||||
|
(elems[0] || document.getElementById("status")).innerHTML = tools.make_status_string(
|
||||||
|
app_status_code,
|
||||||
|
proxy_mode,
|
||||||
|
bllist_mode,
|
||||||
|
bllist_module,
|
||||||
|
bllist_source,
|
||||||
|
tp_status_code,
|
||||||
|
vpn_route_status_code);
|
||||||
|
|
||||||
|
if(!L.Poll.active()) {
|
||||||
|
L.Poll.start();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function button_action(action) {
|
||||||
|
let btn,
|
||||||
|
cmd = tools.exec_path;
|
||||||
|
|
||||||
|
switch(action) {
|
||||||
|
case 'start':
|
||||||
|
case 'stop':
|
||||||
|
btn = document.getElementById('btn_start');
|
||||||
|
break;
|
||||||
|
case 'destroy':
|
||||||
|
btn = document.getElementById('btn_destroy');
|
||||||
|
break;
|
||||||
|
case 'update':
|
||||||
|
btn = document.getElementById('btn_update');
|
||||||
|
break;
|
||||||
|
case 'enable':
|
||||||
|
case 'disable':
|
||||||
|
btn = document.getElementById('btn_enable');
|
||||||
|
cmd = tools.init_path;
|
||||||
|
break;
|
||||||
|
case 'total-proxy-on':
|
||||||
|
case 'total-proxy-off':
|
||||||
|
btn = document.getElementById('btn_tp');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_buttons(true, btn);
|
||||||
|
L.Poll.stop();
|
||||||
|
|
||||||
|
if(action === 'update') {
|
||||||
|
get_app_status().then(status_array => {
|
||||||
|
set_app_status(status_array, [], 4);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return fs.exec_direct(cmd, [ action ]).then(res => {
|
||||||
|
return get_app_status().then(
|
||||||
|
(status_array) => {
|
||||||
|
set_app_status(status_array);
|
||||||
|
ui.hideModal();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return L.view.extend({
|
||||||
|
poll_status: function() {
|
||||||
|
return fs.read(tools.token_file).then(v => {
|
||||||
|
v = tools.normalize_value(v);
|
||||||
|
if(v != status_token_value) {
|
||||||
|
get_app_status().then(set_app_status);
|
||||||
|
}
|
||||||
|
status_token_value = v;
|
||||||
|
}).catch(e => {
|
||||||
|
status_token_value = 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
dialog_destroy: function(ev) {
|
||||||
|
ev.target.blur();
|
||||||
|
let cancel_button = E('button', {
|
||||||
|
'class': btn_style_neutral,
|
||||||
|
'click': ui.hideModal,
|
||||||
|
}, _('Cancel'));
|
||||||
|
|
||||||
|
let shutdown_btn = E('button', {
|
||||||
|
'class': btn_style_warning,
|
||||||
|
}, _('Shutdown'));
|
||||||
|
shutdown_btn.onclick = ui.createHandlerFn(this, function() {
|
||||||
|
cancel_button.disabled = true;
|
||||||
|
return button_action('destroy');
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.showModal(_('Shutdown'), [
|
||||||
|
E('div', { 'class': 'cbi-section' }, [
|
||||||
|
E('p', _('The service will be disabled and all blacklist data will be deleted. Continue?')),
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'right' }, [
|
||||||
|
shutdown_btn,
|
||||||
|
' ',
|
||||||
|
cancel_button,
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
return get_app_status();
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(status_array) {
|
||||||
|
if(!status_array) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let section = uci.get(tools.app_name, 'config');
|
||||||
|
let proxy_local_clients = (typeof(section) === 'object') ? section.proxy_local_clients : null;
|
||||||
|
status_token_value = (Array.isArray(status_array)) ? tools.normalize_value(status_array[4]) : null;
|
||||||
|
|
||||||
|
document.head.append(E('style', {'type': 'text/css'}, tools.css));
|
||||||
|
|
||||||
|
let status_string = E('div', {
|
||||||
|
'id': 'status',
|
||||||
|
'name': 'status',
|
||||||
|
'class': 'cbi-section-node',
|
||||||
|
});
|
||||||
|
|
||||||
|
let layout = E('div', { 'class': 'cbi-section-node' });
|
||||||
|
|
||||||
|
function layout_append(elem, title, descr) {
|
||||||
|
descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : '';
|
||||||
|
layout.append(
|
||||||
|
E('div', { 'class': 'cbi-value' }, [
|
||||||
|
E('label', { 'class': 'cbi-value-title' }, title),
|
||||||
|
E('div', { 'class': 'cbi-value-field' }, [ elem, descr ]),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let btn_start = E('button', {
|
||||||
|
'id': 'btn_start',
|
||||||
|
'name': 'btn_start',
|
||||||
|
'class': btn_style_action,
|
||||||
|
}, _('Enable'));
|
||||||
|
layout_append(btn_start, _('Service'));
|
||||||
|
|
||||||
|
let btn_enable = E('button', {
|
||||||
|
'id': 'btn_enable',
|
||||||
|
'name': 'btn_enable',
|
||||||
|
'class': btn_style_save,
|
||||||
|
}, _('Enable'));
|
||||||
|
layout_append(btn_enable, _('Run at startup'));
|
||||||
|
|
||||||
|
let btn_tp = E('button', {
|
||||||
|
'id': 'btn_tp',
|
||||||
|
'name': 'btn_tp',
|
||||||
|
'class': btn_style_save,
|
||||||
|
}, _('Enable'));
|
||||||
|
if(proxy_local_clients == '0') {
|
||||||
|
layout_append(btn_tp, _('Total-proxy'),
|
||||||
|
_('All traffic goes through the proxy without applying rules'));
|
||||||
|
};
|
||||||
|
|
||||||
|
let btn_update = E('button', {
|
||||||
|
'id': 'btn_update',
|
||||||
|
'name': 'btn_update',
|
||||||
|
'class': btn_style_action,
|
||||||
|
}, _('Update'));
|
||||||
|
btn_update.onclick = ui.createHandlerFn(this, () => { button_action('update') });
|
||||||
|
layout_append(btn_update, _('Update blacklist'));
|
||||||
|
|
||||||
|
let btn_destroy = E('button', {
|
||||||
|
'id': 'btn_destroy',
|
||||||
|
'name': 'btn_destroy',
|
||||||
|
'class': btn_style_reset,
|
||||||
|
}, _('Shutdown'));
|
||||||
|
btn_destroy.onclick = this.dialog_destroy;
|
||||||
|
|
||||||
|
layout_append(btn_destroy, _('Shutdown'),
|
||||||
|
_('Complete service shutdown, as well as deleting ipsets and blacklist data'));
|
||||||
|
|
||||||
|
set_app_status(status_array, [
|
||||||
|
status_string,
|
||||||
|
btn_start,
|
||||||
|
btn_enable,
|
||||||
|
btn_tp,
|
||||||
|
btn_update,
|
||||||
|
btn_destroy,
|
||||||
|
]);
|
||||||
|
|
||||||
|
L.Poll.add(this.poll_status);
|
||||||
|
|
||||||
|
return E([
|
||||||
|
E('h2', { 'class': 'fade-in' }, _('Ruantiblock')),
|
||||||
|
E('div', { 'class': 'cbi-section-descr fade-in' },
|
||||||
|
E('a', {
|
||||||
|
'href': 'https://github.com/gSpotx2f/ruantiblock_openwrt/wiki',
|
||||||
|
'target': '_blank' },
|
||||||
|
'https://github.com/gSpotx2f/ruantiblock_openwrt/wiki')
|
||||||
|
),
|
||||||
|
E('div', { 'class': 'cbi-section fade-in' }, [
|
||||||
|
status_string,
|
||||||
|
E('hr'),
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cbi-section fade-in' },
|
||||||
|
layout
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSave: null,
|
||||||
|
handleSaveApply: null,
|
||||||
|
handleReset: null,
|
||||||
|
});
|
||||||
@@ -0,0 +1,397 @@
|
|||||||
|
'use strict';
|
||||||
|
'require fs';
|
||||||
|
'require uci';
|
||||||
|
'require form';
|
||||||
|
'require ui';
|
||||||
|
'require tools.widgets as widgets';
|
||||||
|
'require view.ruantiblock.tools as tools';
|
||||||
|
|
||||||
|
let available_parsers = [];
|
||||||
|
|
||||||
|
function depends(elem, key, array, empty=true) {
|
||||||
|
if(empty && array.length === 0) {
|
||||||
|
elem.depends(key, '_dummy');
|
||||||
|
} else {
|
||||||
|
array.forEach(e => elem.depends(key, e));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function depends_bllist_module(elem) {
|
||||||
|
depends(elem, 'bllist_module', available_parsers);
|
||||||
|
};
|
||||||
|
|
||||||
|
function validate_ip_port(section, value) {
|
||||||
|
return (/^$|^([0-9]{1,3}\.){3}[0-9]{1,3}(#[\d]{2,5})?$/.test(value)) ? true : _('Expecting:')
|
||||||
|
+ ` ${_('One of the following:')}\n - ${_('valid IP address')}\n - ${_('valid address#port')}\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
let CBIBlockTitle = form.DummyValue.extend({
|
||||||
|
string: null,
|
||||||
|
|
||||||
|
renderWidget: function(section_id, option_index, cfgvalue) {
|
||||||
|
this.title = this.description = null;
|
||||||
|
return E([
|
||||||
|
E('label', { 'class': 'cbi-value-title' }),
|
||||||
|
E('div', { 'class': 'cbi-value-field' },
|
||||||
|
E('b', {}, this.string)
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let ip_filter_edit = new tools.file_edit_dialog(
|
||||||
|
tools.ip_filter_file,
|
||||||
|
_('IP filter'),
|
||||||
|
_('Patterns can be strings or regular expressions. Each pattern in a separate line, the symbol <code>#</code> in the first position of the line - comments on the line.<br />Examples (dot is a special character):') + '<br /><code>128[.]199[.]0[.]0/16<br />34[.]217[.]90[.]52<br />162[.]13[.]190[.]</code>'
|
||||||
|
);
|
||||||
|
|
||||||
|
let fqdn_filter_edit = new tools.file_edit_dialog(
|
||||||
|
tools.fqdn_filter_file,
|
||||||
|
_('FQDN filter'),
|
||||||
|
_('Patterns can be strings or regular expressions. Each pattern in a separate line, the symbol <code>#</code> in the first position of the line - comments on the line.<br />Examples:') + '<br /><code>poker<br />[ck]?a[sz]ino?<br />[vw]ulkan<br />slots?</code>'
|
||||||
|
);
|
||||||
|
|
||||||
|
let user_entries_edit = new tools.file_edit_dialog(
|
||||||
|
tools.user_entries_file,
|
||||||
|
_('User entries'),
|
||||||
|
_('One entry (IP, CIDR or FQDN) per line. In the FQDN records, you can specify the DNS server for resolving this domain (separated by a space). You can also comment on lines (<code>#</code> is the first character of a line).<br />Examples:') + '<br /><code>#comment<br />domain.net<br />sub.domain.com 8.8.8.8<br />sub.domain.com 8.8.8.8#53<br />74.125.131.19<br />74.125.0.0/16</code>'
|
||||||
|
);
|
||||||
|
|
||||||
|
let torrc_edit = new tools.file_edit_dialog(
|
||||||
|
tools.torrc_file,
|
||||||
|
_('Tor configuration file'),
|
||||||
|
null,
|
||||||
|
function(rc) {
|
||||||
|
return fs.exec('/etc/init.d/tor', [ 'enabled' ]).then(res => {
|
||||||
|
if(res.code === 0) {
|
||||||
|
return fs.exec('/etc/init.d/tor', [ 'restart' ]);
|
||||||
|
};
|
||||||
|
}).catch(e => {
|
||||||
|
ui.addNotification(null, E('p', _('Unable to execute or read contents')
|
||||||
|
+ ': %s<br />[ %s ]'.format(e.message, '/etc/init.d/tor')));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return L.view.extend({
|
||||||
|
app_status_code: null,
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
return Promise.all([
|
||||||
|
L.resolveDefault(fs.exec(tools.exec_path, [ 'raw-status' ]), 1),
|
||||||
|
fs.list(tools.parsers_dir),
|
||||||
|
uci.load('network'),
|
||||||
|
]).catch(e => {
|
||||||
|
ui.addNotification(null, E('p', _('Unable to read the contents')
|
||||||
|
+ ': %s<br />[ %s ]'.format(
|
||||||
|
e.message, tools.parsers_dir
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(data) {
|
||||||
|
if(!data) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
this.app_status_code = data[0].code;
|
||||||
|
let p_dir_arr = data[1];
|
||||||
|
let lan_iface = uci.get('network', 'lan', 'ifname') || 'eth0';
|
||||||
|
let vpn_iface = uci.get('network', 'VPN', 'ifname') || 'tun0';
|
||||||
|
|
||||||
|
if(p_dir_arr) {
|
||||||
|
p_dir_arr.forEach(e => {
|
||||||
|
let fname = e.name;
|
||||||
|
if(fname.startsWith('ruab_parser')) {
|
||||||
|
available_parsers.push(tools.parsers_dir + '/' + fname);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let m, s, o;
|
||||||
|
|
||||||
|
m = new form.Map(tools.app_name, _('Ruantiblock') + ' - ' + _('Settings'));
|
||||||
|
|
||||||
|
s = m.section(form.NamedSection, 'config');
|
||||||
|
s.anonymous = true;
|
||||||
|
s.addremove = false;
|
||||||
|
|
||||||
|
/* Main settings tab */
|
||||||
|
|
||||||
|
s.tab('main_settings', _('Main settings'));
|
||||||
|
|
||||||
|
// PROXY_MODE
|
||||||
|
if(this.app_status_code == 1 || this.app_status_code == 2) {
|
||||||
|
o = s.taboption('main_settings', form.ListValue, 'proxy_mode',
|
||||||
|
_('Proxy mode'));
|
||||||
|
o.value('1', 'Tor');
|
||||||
|
o.value('2', 'VPN');
|
||||||
|
};
|
||||||
|
|
||||||
|
// PROXY_LOCAL_CLIENTS
|
||||||
|
let proxy_local_clients = s.taboption('main_settings', form.Flag, 'proxy_local_clients',
|
||||||
|
_("Apply proxy rules to router application traffic"));
|
||||||
|
proxy_local_clients.rmempty = false;
|
||||||
|
proxy_local_clients.default = proxy_local_clients.enabled;
|
||||||
|
|
||||||
|
// USE_LOGGER
|
||||||
|
o = s.taboption('main_settings', form.Flag, 'use_logger',
|
||||||
|
_('Logging events'));
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = 1;
|
||||||
|
|
||||||
|
// DEF_TOTAL_PROXY
|
||||||
|
o = s.taboption('main_settings', form.Flag, 'def_total_proxy',
|
||||||
|
_("Enable the 'total-proxy' option at startup"));
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = 0;
|
||||||
|
o.depends('proxy_local_clients', '0');
|
||||||
|
|
||||||
|
// IPSET_CLEAR_SETS
|
||||||
|
o = s.taboption('main_settings', form.Flag, 'ipset_clear_sets',
|
||||||
|
_('Clean up ipsets before updating blacklist'));
|
||||||
|
o.description = _('Reduces RAM consumption during update');
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = 0;
|
||||||
|
|
||||||
|
|
||||||
|
if(this.app_status_code == 1 || this.app_status_code == 2) {
|
||||||
|
/* Tor tab */
|
||||||
|
|
||||||
|
s.tab('tor_settings', _('Tor mode'));
|
||||||
|
|
||||||
|
// IF_LAN
|
||||||
|
o = s.taboption('tor_settings', widgets.DeviceSelect, 'if_lan',
|
||||||
|
_('LAN interface'));
|
||||||
|
o.multiple = false;
|
||||||
|
o.noaliases = true;
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = lan_iface;
|
||||||
|
|
||||||
|
// TOR_TRANS_PORT
|
||||||
|
o = s.taboption('tor_settings', form.Value, 'tor_trans_port',
|
||||||
|
_('Transparent proxy port for iptables rules'));
|
||||||
|
o.rmempty = false;
|
||||||
|
o.datatype = "port";
|
||||||
|
o.default = '9040';
|
||||||
|
|
||||||
|
// ONION_DNS_ADDR
|
||||||
|
o = s.taboption('tor_settings', form.Value, 'onion_dns_addr',
|
||||||
|
_("Optional DNS resolver for '.onion' zone"), '<code>ipaddress#port</code>');
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = '127.0.0.1#9053';
|
||||||
|
o.validate = validate_ip_port;
|
||||||
|
|
||||||
|
// Torrc edit dialog
|
||||||
|
o = s.taboption('tor_settings', form.Button, '_torrc_btn',
|
||||||
|
_('Tor configuration file'));
|
||||||
|
o.onclick = () => torrc_edit.show();
|
||||||
|
o.inputtitle = _('Edit');
|
||||||
|
o.inputstyle = 'edit btn';
|
||||||
|
|
||||||
|
|
||||||
|
/* VPN tab */
|
||||||
|
|
||||||
|
s.tab('vpn_settings', _('VPN mode'));
|
||||||
|
|
||||||
|
// IF_VPN
|
||||||
|
o = s.taboption('vpn_settings', widgets.DeviceSelect, 'if_vpn',
|
||||||
|
_('VPN interface'));
|
||||||
|
o.multiple = false;
|
||||||
|
o.noaliases = true;
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = vpn_iface;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Parser settings tab */
|
||||||
|
|
||||||
|
s.tab('parser_settings', _('Blacklist settings'));
|
||||||
|
|
||||||
|
// BLLIST_MODULE
|
||||||
|
let bllist_module = s.taboption('parser_settings', form.ListValue,
|
||||||
|
'bllist_module', _('Blacklist module'));
|
||||||
|
bllist_module.value("", _("user entries only"));
|
||||||
|
available_parsers.forEach(e => bllist_module.value(e));
|
||||||
|
|
||||||
|
// BLLIST_MODE
|
||||||
|
let bllist_mode = s.taboption('parser_settings', form.ListValue,
|
||||||
|
'bllist_mode', _('Module operation mode'));
|
||||||
|
bllist_mode.value('ip');
|
||||||
|
bllist_mode.value('fqdn');
|
||||||
|
depends_bllist_module(bllist_mode);
|
||||||
|
|
||||||
|
// BLLIST_SOURCE
|
||||||
|
let bllist_source = s.taboption('parser_settings', form.ListValue,
|
||||||
|
'bllist_source', _('Blacklist source'));
|
||||||
|
bllist_source.description = _("Options") + ':';
|
||||||
|
for(let [k, v] of Object.entries(tools.blacklist_sources)) {
|
||||||
|
bllist_source.value(k);
|
||||||
|
bllist_source.description += `<br />${k} - <a href="${v}" target="_blank">${v}</a>`;
|
||||||
|
};
|
||||||
|
depends_bllist_module(bllist_source);
|
||||||
|
|
||||||
|
o = s.taboption('parser_settings', CBIBlockTitle, '_dummy_ip');
|
||||||
|
o.string = _('IP configuration') + ':';
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// IP_LIMIT
|
||||||
|
o = s.taboption('parser_settings', form.Value, 'ip_limit', _("IP limit"));
|
||||||
|
o.description = _("The number of IP addresses in the subnet, upon reaching which the entire '/24' subnet is added to the list");
|
||||||
|
o.datatype = 'uinteger';
|
||||||
|
o.default = '0';
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// OPT_EXCLUDE_NETS
|
||||||
|
o = s.taboption('parser_settings', form.DynamicList, 'opt_exclude_nets');
|
||||||
|
o.title = _('IP subnet patterns (/24) that are excluded from optimization');
|
||||||
|
o.description = _('ex:') + ' <code>192.168.1.</code>';
|
||||||
|
o.placeholder = _('ex:') + ' 192.168.1.';
|
||||||
|
o.default = '';
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// SUMMARIZE_IP
|
||||||
|
o = s.taboption('parser_settings', form.Flag, 'summarize_ip',
|
||||||
|
_("Summarize IP ranges"));
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = 0;
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// SUMMARIZE_CIDR
|
||||||
|
o = s.taboption('parser_settings', form.Flag, 'summarize_cidr',
|
||||||
|
_("Summarize '/24' networks"));
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = 0;
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
o = s.taboption('parser_settings', CBIBlockTitle, '_dummy_fqdn');
|
||||||
|
o.string = _('FQDN configuration') + ':';
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// SD_LIMIT
|
||||||
|
o = s.taboption('parser_settings', form.Value, 'sd_limit',
|
||||||
|
_("Subdomains limit"));
|
||||||
|
o.description = _('The number of subdomains in the domain, upon reaching which the entire 2nd level domain is added to the list');
|
||||||
|
o.datatype = 'uinteger';
|
||||||
|
o.default = '16';
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// OPT_EXCLUDE_SLD
|
||||||
|
o = s.taboption('parser_settings', form.DynamicList, 'opt_exclude_sld',
|
||||||
|
_('2nd level domains that are excluded from optimization'));
|
||||||
|
o.datatype = "hostname";
|
||||||
|
o.default = [
|
||||||
|
'livejournal.com',
|
||||||
|
'facebook.com',
|
||||||
|
'vk.com',
|
||||||
|
'blog.jp',
|
||||||
|
'msk.ru',
|
||||||
|
'net.ru',
|
||||||
|
'org.ru',
|
||||||
|
'net.ua',
|
||||||
|
'com.ua',
|
||||||
|
'org.ua',
|
||||||
|
'co.uk',
|
||||||
|
'amazonaws.com',
|
||||||
|
];
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// USE_IDN
|
||||||
|
o = s.taboption('parser_settings', form.Flag, 'use_idn',
|
||||||
|
_("Convert cyrillic domains to punycode"));
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = 0;
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// ALT_NSLOOKUP
|
||||||
|
o = s.taboption('parser_settings', form.Flag, 'alt_nslookup',
|
||||||
|
_('Use optional DNS resolver'));
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = 0;
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// ALT_DNS_ADDR
|
||||||
|
o = s.taboption('parser_settings', form.Value, 'alt_dns_addr',
|
||||||
|
_("Optional DNS resolver"), '<code>ipaddress[#port]</code>');
|
||||||
|
o.rmempty = false;
|
||||||
|
o.depends('alt_nslookup', '1');
|
||||||
|
o.validate = validate_ip_port;
|
||||||
|
o.default = '8.8.8.8';
|
||||||
|
|
||||||
|
|
||||||
|
/* Entries filters tab */
|
||||||
|
|
||||||
|
s.tab('entries_filter_tab', _('Entries filters'));
|
||||||
|
|
||||||
|
// IP_FILTER
|
||||||
|
o = s.taboption('entries_filter_tab', form.Flag, 'ip_filter',
|
||||||
|
_("Enable IP filter"));
|
||||||
|
o.description = _('Exclude IP addresses from blacklist by IP filter patterns');
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = 0;
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// IP_FILTER edit dialog
|
||||||
|
o = s.taboption('entries_filter_tab', form.Button, '_ip_filter_btn',
|
||||||
|
_("IP filter"));
|
||||||
|
o.onclick = () => ip_filter_edit.show();
|
||||||
|
o.inputtitle = _('Edit');
|
||||||
|
o.inputstyle = 'edit btn';
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// FQDN_FILTER
|
||||||
|
o = s.taboption('entries_filter_tab', form.Flag, 'fqdn_filter',
|
||||||
|
_("Enable FQDN filter"));
|
||||||
|
o.description = _('Exclude domains from blacklist by FQDN filter patterns');
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = 0;
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// FQDN_FILTER edit dialog
|
||||||
|
o = s.taboption('entries_filter_tab', form.Button, '_fqdn_filter_btn',
|
||||||
|
_("FQDN filter"));
|
||||||
|
o.onclick = () => fqdn_filter_edit.show();
|
||||||
|
o.inputtitle = _('Edit');
|
||||||
|
o.inputstyle = 'edit btn';
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
|
||||||
|
/* User entries tab */
|
||||||
|
|
||||||
|
s.tab('user_entries_tab', _('User entries'));
|
||||||
|
|
||||||
|
// ADD_USER_ENTRIES
|
||||||
|
o = s.taboption('user_entries_tab', form.Flag, 'add_user_entries',
|
||||||
|
_('Enable'), _("Add user entries to the blacklist when updating"));
|
||||||
|
o.rmempty = false;
|
||||||
|
o.default = 0;
|
||||||
|
depends_bllist_module(o);
|
||||||
|
|
||||||
|
// USER_ENTRIES_DNS
|
||||||
|
o = s.taboption('user_entries_tab', form.Value, 'user_entries_dns',
|
||||||
|
_("DNS server that is used for FQDN entries"), '<code>ipaddress[#port]</code>');
|
||||||
|
o.validate = validate_ip_port;
|
||||||
|
|
||||||
|
// USER_ENTRIES edit dialog
|
||||||
|
o = s.taboption('user_entries_tab', form.Button, '_user_entries_btn',
|
||||||
|
_('User entries'));
|
||||||
|
o.onclick = () => user_entries_edit.show();
|
||||||
|
o.inputtitle = _('Edit');
|
||||||
|
o.inputstyle = 'edit btn';
|
||||||
|
|
||||||
|
|
||||||
|
let map_promise = m.render();
|
||||||
|
map_promise.then(node => node.classList.add('fade-in'));
|
||||||
|
|
||||||
|
return map_promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSaveApply: function(ev, mode) {
|
||||||
|
return this.handleSave(ev).then(() => {
|
||||||
|
ui.changes.apply(mode == '0');
|
||||||
|
|
||||||
|
if(this.app_status_code != 1 && this.app_status_code != 2) {
|
||||||
|
window.setTimeout(() => fs.exec(tools.init_path, [ 'restart' ]), 3000);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
'use strict';
|
||||||
|
'require fs';
|
||||||
|
'require ui';
|
||||||
|
|
||||||
|
return L.Class.extend({
|
||||||
|
app_name: 'ruantiblock',
|
||||||
|
exec_path: '/usr/bin/ruantiblock',
|
||||||
|
init_path: '/etc/init.d/ruantiblock',
|
||||||
|
token_file: '/var/run/ruantiblock.token',
|
||||||
|
parsers_dir: '/usr/bin',
|
||||||
|
torrc_file: '/etc/tor/torrc',
|
||||||
|
user_entries_file: '/etc/ruantiblock/user_entries',
|
||||||
|
fqdn_filter_file: '/etc/ruantiblock/fqdn_filter',
|
||||||
|
ip_filter_file: '/etc/ruantiblock/ip_filter',
|
||||||
|
crontab_file: '/etc/crontabs/root',
|
||||||
|
info_label_starting: '<span class="label-status starting">' + _('Starting') + '</span>',
|
||||||
|
info_label_running: '<span class="label-status running">' + _('Enabled') + '</span>',
|
||||||
|
info_label_updating: '<span class="label-status updating">' + _('Updating') + '</span>',
|
||||||
|
info_label_stopped: '<span class="label-status stopped">' + _('Disabled') + '</span>',
|
||||||
|
info_label_error: '<span class="label-status error">' + _('Error') + '</span>',
|
||||||
|
|
||||||
|
blacklist_sources: {
|
||||||
|
'rublacklist': 'https://rublacklist.net',
|
||||||
|
'zapret-info': 'https://github.com/zapret-info/z-i',
|
||||||
|
'antifilter': 'https://antifilter.download',
|
||||||
|
},
|
||||||
|
|
||||||
|
css: `
|
||||||
|
.label-status {
|
||||||
|
display: inline;
|
||||||
|
margin: 0px 2px 0px 0 !important;
|
||||||
|
padding: 1px 4px 2px 4px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.starting {
|
||||||
|
background-color: #a7b668 !important;
|
||||||
|
}
|
||||||
|
.running {
|
||||||
|
background-color: #2ea256 !important;
|
||||||
|
}
|
||||||
|
.updating {
|
||||||
|
background-color: #1e82ff !important;
|
||||||
|
}
|
||||||
|
.stopped {
|
||||||
|
background-color: #acacac !important;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
background-color: #ff4e54 !important;
|
||||||
|
}
|
||||||
|
.total-proxy {
|
||||||
|
background-color: #ffb937 !important;
|
||||||
|
}`,
|
||||||
|
|
||||||
|
normalize_value: function(v) {
|
||||||
|
return (v && typeof(v) === 'string') ? v.trim().replace(/\r?\n/g, '') : v;
|
||||||
|
},
|
||||||
|
|
||||||
|
make_status_string: function(
|
||||||
|
app_status_code,
|
||||||
|
proxy_mode,
|
||||||
|
bllist_mode,
|
||||||
|
bllist_module,
|
||||||
|
bllist_source,
|
||||||
|
tp_status_code,
|
||||||
|
vpn_route_status_code) {
|
||||||
|
let app_status_label;
|
||||||
|
let spinning = '';
|
||||||
|
|
||||||
|
switch(app_status_code) {
|
||||||
|
case 0:
|
||||||
|
app_status_label = this.info_label_running;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
app_status_label = this.info_label_stopped;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
app_status_label = this.info_label_starting;
|
||||||
|
spinning = ' spinning';
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
app_status_label = this.info_label_updating;
|
||||||
|
spinning = ' spinning';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
app_status_label = this.info_label_error;
|
||||||
|
return `<div class="table">
|
||||||
|
<div class="tr">
|
||||||
|
<div class="td left" style="width:33%">
|
||||||
|
${_('Status')}
|
||||||
|
</div>
|
||||||
|
<div class="td left">
|
||||||
|
${app_status_label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
};
|
||||||
|
|
||||||
|
return `<div class="table">
|
||||||
|
<div class="tr">
|
||||||
|
<div class="td left" style="width:33%%">
|
||||||
|
${_('Status')}
|
||||||
|
</div>
|
||||||
|
<div class="td left%s">
|
||||||
|
%s %s %s
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tr">
|
||||||
|
<div class="td left" style="width:33%%">
|
||||||
|
${_('Proxy mode')}
|
||||||
|
</div>
|
||||||
|
<div class="td left">
|
||||||
|
%s
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tr">
|
||||||
|
<div class="td left" style="width:33%%">
|
||||||
|
${_('Blacklist update mode')}
|
||||||
|
</div>
|
||||||
|
<div class="td left">
|
||||||
|
%s
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
%s
|
||||||
|
</div>
|
||||||
|
`.format(
|
||||||
|
spinning,
|
||||||
|
app_status_label,
|
||||||
|
(tp_status_code == 0) ? '<span class="label-status total-proxy">'
|
||||||
|
+ _('Total-proxy is on') + '</span>' : '',
|
||||||
|
(app_status_code != 2 && proxy_mode == 2 && vpn_route_status_code != 0)
|
||||||
|
? '<span class="label-status error">'
|
||||||
|
+ _('VPN routing error! Need restart') + '</span>' : '',
|
||||||
|
(proxy_mode == 1) ? 'Tor' : 'VPN',
|
||||||
|
(!bllist_module || bllist_module === '') ? _('user entries only') : bllist_mode,
|
||||||
|
(!bllist_module || bllist_module === '') ? '' : `<div class="tr">
|
||||||
|
<div class="td left" style="width:33%%">
|
||||||
|
${_('Blacklist source')}
|
||||||
|
</div>
|
||||||
|
<div class="td left">
|
||||||
|
<span style="cursor:help; border-bottom:1px dotted" data-tooltip="${this.blacklist_sources[bllist_source]}">
|
||||||
|
${bllist_source}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
file_edit_dialog: L.Class.extend({
|
||||||
|
__init__: function(file, title, description, callback, file_exists=false) {
|
||||||
|
this.file = file;
|
||||||
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
|
this.callback = callback;
|
||||||
|
this.file_exists = file_exists;
|
||||||
|
},
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
return fs.read(this.file);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(content) {
|
||||||
|
ui.showModal(this.title, [
|
||||||
|
E('div', { 'class': 'cbi-section' }, [
|
||||||
|
E('div', { 'class': 'cbi-section-descr' }, this.description),
|
||||||
|
E('div', { 'class': 'cbi-section' },
|
||||||
|
E('p', {},
|
||||||
|
E('textarea', {
|
||||||
|
'id': 'widget.modal_content',
|
||||||
|
'class': 'cbi-input-textarea',
|
||||||
|
'style': 'width:100% !important',
|
||||||
|
'rows': 10,
|
||||||
|
'wrap': 'off',
|
||||||
|
'spellcheck': 'false',
|
||||||
|
},
|
||||||
|
content || '')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'right' }, [
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn',
|
||||||
|
'click': ui.hideModal,
|
||||||
|
}, _('Dismiss')),
|
||||||
|
' ',
|
||||||
|
E('button', {
|
||||||
|
'id': 'btn_save',
|
||||||
|
'class': 'btn cbi-button-positive important',
|
||||||
|
'click': ui.createHandlerFn(this, this.handleSave),
|
||||||
|
}, _('Save')),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSave: function(ev) {
|
||||||
|
let textarea = document.getElementById('widget.modal_content');
|
||||||
|
let value = textarea.value.trim().replace(/\r\n/g, '\n') + '\n';
|
||||||
|
|
||||||
|
return fs.write(this.file, value).then(async rc => {
|
||||||
|
textarea.value = value;
|
||||||
|
ui.addNotification(null, E('p', _('Contents have been saved.')),
|
||||||
|
'info');
|
||||||
|
if(this.callback) {
|
||||||
|
return this.callback(rc);
|
||||||
|
};
|
||||||
|
}).catch(e => {
|
||||||
|
ui.addNotification(null, E('p', _('Unable to save the contents')
|
||||||
|
+ ': %s'.format(e.message)));
|
||||||
|
}).finally(() => {
|
||||||
|
ui.hideModal();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function(e) {
|
||||||
|
if(!this.file_exists && e instanceof Error && e.name === 'NotFoundError') {
|
||||||
|
return this.render();
|
||||||
|
} else {
|
||||||
|
ui.showModal(this.title, [
|
||||||
|
E('div', { 'class': 'cbi-section' },
|
||||||
|
E('p', {}, _('Unable to read the contents')
|
||||||
|
+ ': %s'.format(e.message))
|
||||||
|
),
|
||||||
|
E('div', { 'class': 'right' },
|
||||||
|
E('button', {
|
||||||
|
'class': 'btn',
|
||||||
|
'click': ui.hideModal,
|
||||||
|
}, _('Dismiss'))
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function() {
|
||||||
|
ui.showModal(null,
|
||||||
|
E('p', { 'class': 'spinning' }, _('Loading'))
|
||||||
|
);
|
||||||
|
this.load().then(content => {
|
||||||
|
ui.hideModal();
|
||||||
|
return this.render(content);
|
||||||
|
}).catch(e => {
|
||||||
|
ui.hideModal();
|
||||||
|
return this.error(e);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
'use strict';
|
||||||
|
'require fs';
|
||||||
|
'require uci';
|
||||||
|
'require view.ruantiblock.tools as tools';
|
||||||
|
|
||||||
|
return L.Class.extend({
|
||||||
|
title: _('Ruantiblock'),
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
return Promise.all([
|
||||||
|
fs.exec(tools.exec_path, [ 'raw-status' ]),
|
||||||
|
fs.exec(tools.exec_path, [ 'total-proxy-status' ]),
|
||||||
|
fs.exec(tools.exec_path, [ 'vpn-route-status' ]),
|
||||||
|
uci.load('ruantiblock'),
|
||||||
|
]).catch(e => {});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(status_array) {
|
||||||
|
if(!status_array) {
|
||||||
|
return E('em', _('Error') + ': ' + _('Unable to execute or read contents'));
|
||||||
|
};
|
||||||
|
|
||||||
|
let app_status_code = status_array[0].code;
|
||||||
|
let tp_status_code = status_array[1].code;
|
||||||
|
let vpn_route_status_code = status_array[2].code;
|
||||||
|
|
||||||
|
let section = uci.get('ruantiblock', 'config');
|
||||||
|
let proxy_local_clients, proxy_mode, bllist_mode, bllist_module, bllist_source;
|
||||||
|
|
||||||
|
if(typeof(section) === 'object') {
|
||||||
|
proxy_local_clients = section.proxy_local_clients;
|
||||||
|
proxy_mode = section.proxy_mode;
|
||||||
|
bllist_mode = section.bllist_mode;
|
||||||
|
bllist_module = section.bllist_module;
|
||||||
|
bllist_source = section.bllist_source;
|
||||||
|
} else {
|
||||||
|
return _('Error');
|
||||||
|
};
|
||||||
|
|
||||||
|
document.head.append(E('style', { 'type': 'text/css' }, tools.css));
|
||||||
|
|
||||||
|
return E('div', { 'class': 'cbi-section' }).innerHTML = tools.make_status_string(
|
||||||
|
app_status_code,
|
||||||
|
proxy_mode,
|
||||||
|
bllist_mode,
|
||||||
|
bllist_module,
|
||||||
|
bllist_source,
|
||||||
|
tp_status_code,
|
||||||
|
vpn_route_status_code);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
module('luci.controller.ruantiblock', package.seeall)
|
||||||
|
|
||||||
|
function index()
|
||||||
|
if nixio.fs.access('/etc/config/ruantiblock') and nixio.fs.access('/usr/bin/ruantiblock') then
|
||||||
|
entry({'admin', 'services', 'ruantiblock'}, firstchild(), _('Ruantiblock'), 60).acl_depends = { 'luci-app-ruantiblock' }
|
||||||
|
entry({'admin', 'services', 'ruantiblock', 'service'}, view('ruantiblock/service'), _('Service'), 10)
|
||||||
|
entry({'admin', 'services', 'ruantiblock', 'settings'}, view('ruantiblock/settings'), _('Settings'), 20)
|
||||||
|
entry({'admin', 'services', 'ruantiblock', 'cron'}, view('ruantiblock/cron'), _('Blacklist updates'), 30)
|
||||||
|
entry({'admin', 'services', 'ruantiblock', 'info'}, view('ruantiblock/info'), _('Statistics'), 40)
|
||||||
|
entry({'admin', 'services', 'ruantiblock', 'log'}, view('ruantiblock/log'), _('Log'), 50)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||||
|
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"POT-Creation-Date: \n"
|
||||||
|
"PO-Revision-Date: \n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: ru\n"
|
||||||
|
"X-Generator: Poedit 2.0.6\n"
|
||||||
|
|
||||||
|
msgid "valid IP address"
|
||||||
|
msgstr "верный IP-адрес"
|
||||||
|
|
||||||
|
msgid "valid address#port"
|
||||||
|
msgstr "верный IP-адрес#порт"
|
||||||
|
|
||||||
|
msgid "2nd level domains that are excluded from optimization"
|
||||||
|
msgstr "Домены 2-го уровня не подлежащие оптимизации"
|
||||||
|
|
||||||
|
msgid "Add user entries to the blacklist when updating"
|
||||||
|
msgstr "Добавлять записи пользователя в блэклист при обновлении"
|
||||||
|
|
||||||
|
msgid "All traffic goes through the proxy without applying rules"
|
||||||
|
msgstr "Весь трафик отправляется в прокси, без применения правил"
|
||||||
|
|
||||||
|
msgid "Apply"
|
||||||
|
msgstr "Применить"
|
||||||
|
|
||||||
|
msgid "Apply proxy rules to router application traffic"
|
||||||
|
msgstr "Применять правила прокси к трафику приложений роутера"
|
||||||
|
|
||||||
|
msgid "Blacklist module"
|
||||||
|
msgstr "Модуль блэклиста"
|
||||||
|
|
||||||
|
msgid "Blacklist settings"
|
||||||
|
msgstr "Настройки блэклиста"
|
||||||
|
|
||||||
|
msgid "Blacklist source"
|
||||||
|
msgstr "Источник блэклиста"
|
||||||
|
|
||||||
|
msgid "Blacklist update mode"
|
||||||
|
msgstr "Режим обновления блэклиста"
|
||||||
|
|
||||||
|
msgid "Blacklist updates"
|
||||||
|
msgstr "Обновления блэклиста"
|
||||||
|
|
||||||
|
msgid "Bytes"
|
||||||
|
msgstr "Байты"
|
||||||
|
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Отмена"
|
||||||
|
|
||||||
|
msgid "Changes have been saved."
|
||||||
|
msgstr "Изменения сохранены."
|
||||||
|
|
||||||
|
msgid "Clean up ipsets before updating blacklist"
|
||||||
|
msgstr "Очищать ipset'ы перед обновлением блэклиста"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Complete service shutdown, as well as deleting ipsets and blacklist data"
|
||||||
|
msgstr ""
|
||||||
|
"Полное выключение службы, а также удаление ipset'ов и данных блэклиста"
|
||||||
|
|
||||||
|
msgid "Contents have been saved."
|
||||||
|
msgstr "Содержимое сохранено."
|
||||||
|
|
||||||
|
msgid "Convert cyrillic domains to punycode"
|
||||||
|
msgstr "Конвертировать кириллические домены в punycode"
|
||||||
|
|
||||||
|
msgid "Current schedule"
|
||||||
|
msgstr "Текущее расписание"
|
||||||
|
|
||||||
|
msgid "DNS server that is used for FQDN entries"
|
||||||
|
msgstr "DNS сервер для FQDN записей"
|
||||||
|
|
||||||
|
msgid "Day"
|
||||||
|
msgstr "День"
|
||||||
|
|
||||||
|
msgid "Disable"
|
||||||
|
msgstr "Отключить"
|
||||||
|
|
||||||
|
msgid "Disabled"
|
||||||
|
msgstr "Отключено"
|
||||||
|
|
||||||
|
msgid "Dismiss"
|
||||||
|
msgstr "Закрыть"
|
||||||
|
|
||||||
|
msgid "Edit"
|
||||||
|
msgstr "Изменить"
|
||||||
|
|
||||||
|
msgid "Enable"
|
||||||
|
msgstr "Включить"
|
||||||
|
|
||||||
|
msgid "Enable FQDN filter"
|
||||||
|
msgstr "Включить фильтр FQDN"
|
||||||
|
|
||||||
|
msgid "Enable IP filter"
|
||||||
|
msgstr "Включить фильтр IP"
|
||||||
|
|
||||||
|
msgid "Enable the 'total-proxy' option at startup"
|
||||||
|
msgstr "Включать 'total-proxy' при старте"
|
||||||
|
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr "Включено"
|
||||||
|
|
||||||
|
msgid "Entries filters"
|
||||||
|
msgstr "Фильтры записей"
|
||||||
|
|
||||||
|
msgid "Error"
|
||||||
|
msgstr "Ошибка"
|
||||||
|
|
||||||
|
msgid "Exclude domains from blacklist by FQDN filter patterns"
|
||||||
|
msgstr "Исключение доменов из блэклиста по шаблонам фильтра FQDN"
|
||||||
|
|
||||||
|
msgid "Exclude IP addresses from blacklist by IP filter patterns"
|
||||||
|
msgstr "Исключение IP адресов из блэклиста по шаблонам фильтра IP"
|
||||||
|
|
||||||
|
msgid "Expecting:"
|
||||||
|
msgstr "Ожидается:"
|
||||||
|
|
||||||
|
msgid "Filter messages with a regexp"
|
||||||
|
msgstr "Фильтровать сообщения с помощью регулярного выражения"
|
||||||
|
|
||||||
|
msgid "FQDN configuration"
|
||||||
|
msgstr "Конфигурация FQDN"
|
||||||
|
|
||||||
|
msgid "FQDN filter"
|
||||||
|
msgstr "Фильтр FQDN"
|
||||||
|
|
||||||
|
msgid "Hour"
|
||||||
|
msgstr "Час"
|
||||||
|
|
||||||
|
msgid "Interval"
|
||||||
|
msgstr "Интервал"
|
||||||
|
|
||||||
|
msgid "IP configuration"
|
||||||
|
msgstr "Конфигурация IP"
|
||||||
|
|
||||||
|
msgid "IP filter"
|
||||||
|
msgstr "Фильтр IP"
|
||||||
|
|
||||||
|
msgid "IP limit"
|
||||||
|
msgstr "Лимит IP адресов"
|
||||||
|
|
||||||
|
msgid "IP subnet patterns (/24) that are excluded from optimization"
|
||||||
|
msgstr "Шаблоны IP подсетей (/24) не подлежащих оптимизации"
|
||||||
|
|
||||||
|
msgid "Ipset"
|
||||||
|
msgstr "Ipset"
|
||||||
|
|
||||||
|
msgid "Iptables rules"
|
||||||
|
msgstr "Правила iptables"
|
||||||
|
|
||||||
|
msgid "LAN interface"
|
||||||
|
msgstr "LAN интерфейс"
|
||||||
|
|
||||||
|
msgid "Last blacklist update"
|
||||||
|
msgstr "Последнее обновление блэклиста"
|
||||||
|
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr "Загрузка"
|
||||||
|
|
||||||
|
msgid "Log"
|
||||||
|
msgstr "Лог"
|
||||||
|
|
||||||
|
msgid "Logging events"
|
||||||
|
msgstr "Записывать события в лог"
|
||||||
|
|
||||||
|
msgid "Main settings"
|
||||||
|
msgstr "Основные настройки"
|
||||||
|
|
||||||
|
msgid "Match-set"
|
||||||
|
msgstr "Правило"
|
||||||
|
|
||||||
|
msgid "Message filter"
|
||||||
|
msgstr "Фильтр сообщений"
|
||||||
|
|
||||||
|
msgid "Minute"
|
||||||
|
msgstr "Минута"
|
||||||
|
|
||||||
|
msgid "Module operation mode"
|
||||||
|
msgstr "Режим работы модуля"
|
||||||
|
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Имя"
|
||||||
|
|
||||||
|
msgid "No changes to save."
|
||||||
|
msgstr "Нет изменений для сохранения."
|
||||||
|
|
||||||
|
msgid "No data"
|
||||||
|
msgstr "Нет данных"
|
||||||
|
|
||||||
|
msgid "No matches..."
|
||||||
|
msgstr "Нет совпадений..."
|
||||||
|
|
||||||
|
msgid "No Shedule"
|
||||||
|
msgstr "Нет расписания"
|
||||||
|
|
||||||
|
msgid "Number of entries"
|
||||||
|
msgstr "Кол-во записей"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"One entry (IP, CIDR or FQDN) per line. In the FQDN records, you can specify "
|
||||||
|
"the DNS server for resolving this domain (separated by a space). You can "
|
||||||
|
"also comment on lines (<code>#</code> is the first character of a line).<br /"
|
||||||
|
">Examples:"
|
||||||
|
msgstr ""
|
||||||
|
"Одна запись (IP, CIDR или FQDN) на строку. В записях FQDN можно задать DNS-"
|
||||||
|
"сервер для разрешения данного домена (через пробел). Также можно "
|
||||||
|
"комментировать строки (<code>#</code> - первый символ строки).<br />Примеры:"
|
||||||
|
|
||||||
|
msgid "One of the following:"
|
||||||
|
msgstr "Одно из следующих значений:"
|
||||||
|
|
||||||
|
msgid "Only messages that include the specified string will be displayed"
|
||||||
|
msgstr "Будут показаны только сообщения включающие указанную строку"
|
||||||
|
|
||||||
|
msgid "Optional DNS resolver for '.onion' zone"
|
||||||
|
msgstr "Дополнительный DNS резолвер для '.onion' зоны"
|
||||||
|
|
||||||
|
msgid "Optional DNS resolver"
|
||||||
|
msgstr "Альтернативный DNS резолвер"
|
||||||
|
|
||||||
|
msgid "Options"
|
||||||
|
msgstr "Опции"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Patterns can be strings or regular expressions. Each pattern in a separate "
|
||||||
|
"line, the symbol <code>#</code> in the first position of the line - comments "
|
||||||
|
"on the line.<br />Examples (dot is a special character):"
|
||||||
|
msgstr ""
|
||||||
|
"Шаблоны могут быть строками или регулярными выражениями. Каждый шаблон в "
|
||||||
|
"отдельной строке, символ <code>#</code> в первой позиции строки - "
|
||||||
|
"комментирует строку.<br />Примеры (точка - спецсимвол):"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Patterns can be strings or regular expressions. Each pattern in a separate "
|
||||||
|
"line, the symbol <code>#</code> in the first position of the line - comments "
|
||||||
|
"on the line.<br />Examples:"
|
||||||
|
msgstr ""
|
||||||
|
"Шаблоны могут быть строками или регулярными выражениями. Каждый шаблон в "
|
||||||
|
"отдельной строке, символ <code>#</code> в первой позиции строки - "
|
||||||
|
"комментирует строку.<br />Примеры:"
|
||||||
|
|
||||||
|
msgid "Proxy mode"
|
||||||
|
msgstr "Режим прокси"
|
||||||
|
|
||||||
|
msgid "Reduces RAM consumption during update"
|
||||||
|
msgstr "Уменьшает потребление опреративной памяти при обновлении"
|
||||||
|
|
||||||
|
msgid "Reset"
|
||||||
|
msgstr "Сбросить"
|
||||||
|
|
||||||
|
msgid "Run at startup"
|
||||||
|
msgstr "Запуск при старте системы"
|
||||||
|
|
||||||
|
msgid "Service"
|
||||||
|
msgstr "Служба"
|
||||||
|
|
||||||
|
msgid "Set"
|
||||||
|
msgstr "Установить"
|
||||||
|
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Настройки"
|
||||||
|
|
||||||
|
msgid "Show only the last messages"
|
||||||
|
msgstr "Показать только последние сообщения"
|
||||||
|
|
||||||
|
msgid "Shutdown"
|
||||||
|
msgstr "Выключение"
|
||||||
|
|
||||||
|
msgid "Size in memory"
|
||||||
|
msgstr "Размер в памяти"
|
||||||
|
|
||||||
|
msgid "Starting"
|
||||||
|
msgstr "Запускается"
|
||||||
|
|
||||||
|
msgid "Statistics"
|
||||||
|
msgstr "Статистика"
|
||||||
|
|
||||||
|
msgid "Status"
|
||||||
|
msgstr "Статус"
|
||||||
|
|
||||||
|
msgid "Subdomains limit"
|
||||||
|
msgstr "Лимит субдоменов"
|
||||||
|
|
||||||
|
msgid "Summarize '/24' networks"
|
||||||
|
msgstr "Суммировать '/24' подсети"
|
||||||
|
|
||||||
|
msgid "Summarize IP ranges"
|
||||||
|
msgstr "Суммировать IP диапазоны"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"The number of IP addresses in the subnet, upon reaching which the entire "
|
||||||
|
"'/24' subnet is added to the list"
|
||||||
|
msgstr ""
|
||||||
|
"Количество IP адресов подсети, при достижении которого в список добавляется "
|
||||||
|
"вся '/24' подсеть"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"The number of subdomains in the domain, upon reaching which the entire 2nd "
|
||||||
|
"level domain is added to the list"
|
||||||
|
msgstr ""
|
||||||
|
"Количество субдоменов при достижении которого в список добавляется весь "
|
||||||
|
"домен 2-го уровня"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"The service will be disabled and all blacklist data will be deleted. "
|
||||||
|
"Continue?"
|
||||||
|
msgstr ""
|
||||||
|
"Служба будет выключена и все данные блэклиста будут удалены. Продолжить?"
|
||||||
|
|
||||||
|
msgid "Time"
|
||||||
|
msgstr "Время"
|
||||||
|
|
||||||
|
msgid "Tor configuration file"
|
||||||
|
msgstr "Конфигурационный файл Tor"
|
||||||
|
|
||||||
|
msgid "Tor mode"
|
||||||
|
msgstr "Режим Tor"
|
||||||
|
|
||||||
|
msgid "Total-proxy is on"
|
||||||
|
msgstr "Total-proxy включен"
|
||||||
|
|
||||||
|
msgid "Transparent proxy port for iptables rules"
|
||||||
|
msgstr "Порт прозрачного прокси для правил iptables"
|
||||||
|
|
||||||
|
msgid "Unable to execute or read contents"
|
||||||
|
msgstr "Невозможно выполнить или прочитать содержимое"
|
||||||
|
|
||||||
|
msgid "Unable to read the contents"
|
||||||
|
msgstr "Невозможно прочитать содержимое"
|
||||||
|
|
||||||
|
msgid "Unable to save the changes"
|
||||||
|
msgstr "Невозможно сохранить изменения"
|
||||||
|
|
||||||
|
msgid "Unable to save the contents"
|
||||||
|
msgstr "Невозможно сохранить содержимое"
|
||||||
|
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Обновить"
|
||||||
|
|
||||||
|
msgid "Update blacklist"
|
||||||
|
msgstr "Обновить блэклист"
|
||||||
|
|
||||||
|
msgid "Updating"
|
||||||
|
msgstr "Обновление"
|
||||||
|
|
||||||
|
msgid "Use optional DNS resolver"
|
||||||
|
msgstr "Использовать альтернативный DNS резолвер"
|
||||||
|
|
||||||
|
msgid "User entries"
|
||||||
|
msgstr "Записи пользователя"
|
||||||
|
|
||||||
|
msgid "VPN interface"
|
||||||
|
msgstr "VPN интерфейс"
|
||||||
|
|
||||||
|
msgid "VPN mode"
|
||||||
|
msgstr "Режим VPN"
|
||||||
|
|
||||||
|
msgid "VPN routing error! Need restart"
|
||||||
|
msgstr "Ошибка маршрутизации VPN! Необходим перезапуск"
|
||||||
|
|
||||||
|
msgid "ex:"
|
||||||
|
msgstr "прим:"
|
||||||
|
|
||||||
|
msgid "user entries only"
|
||||||
|
msgstr "только записи пользователя"
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||||
|
|
||||||
|
msgid "valid IP address"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "valid address#port"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "2nd level domains that are excluded from optimization"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Add user entries to the blacklist when updating"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "All traffic goes through the proxy without applying rules"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Apply"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Apply proxy rules to router application traffic"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Blacklist module"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Blacklist settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Blacklist source"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Blacklist update mode"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Blacklist updates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Bytes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Changes have been saved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Clean up ipsets before updating blacklist"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Complete service shutdown, as well as deleting ipsets and blacklist data"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Contents have been saved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Convert cyrillic domains to punycode"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Current schedule"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "DNS server that is used for FQDN entries"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Day"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Disable"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Disabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Dismiss"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Edit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Enable"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Enable FQDN filter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Enable ip filter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Enable the 'total-proxy' option at startup"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Entries filters"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Error"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Exclude domains from blacklist by FQDN filter patterns"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Exclude IP addresses from blacklist by IP filter patterns"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Expecting:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Filter messages with a regexp"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "FQDN configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "FQDN filter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Hour"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Interval"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "IP configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "IP filter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "IP limit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "IP subnet patterns (/24) that are excluded from optimization"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Ipset"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Iptables rules"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "LAN interface"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Last blacklist update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Log"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Logging events"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Main settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Match-set"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Message filter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Minute"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Module operation mode"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "No changes to save."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "No data"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "No matches..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "No Shedule"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Number of entries"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"One entry (IP, CIDR or FQDN) per line. In the FQDN records, you can specify "
|
||||||
|
"the DNS server for resolving this domain (separated by a space). You can "
|
||||||
|
"also comment on lines (<code>#</code> is the first character of a line).<br /"
|
||||||
|
">Examples:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "One of the following:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Optional DNS resolver for '.onion' zone"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Optional DNS resolver"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Options"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Patterns can be strings or regular expressions. Each pattern in a separate "
|
||||||
|
"line, the symbol <code>#</code> in the first position of the line - comments "
|
||||||
|
"on the line.<br />Examples (dot is a special character):"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Patterns can be strings or regular expressions. Each pattern in a separate "
|
||||||
|
"line, the symbol <code>#</code> in the first position of the line - comments "
|
||||||
|
"on the line.<br />Examples:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Proxy mode"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Reduces RAM consumption during update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Reset"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Run at startup"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Set"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Show only the last messages"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Shutdown"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Size in memory"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Starting"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Statistics"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Status"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Subdomains limit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Summarize '/24' networks"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Summarize IP ranges"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"The number of IP addresses in the subnet, upon reaching which the entire "
|
||||||
|
"'/24' subnet is added to the list"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"The number of subdomains in the domain, upon reaching which the entire 2nd "
|
||||||
|
"level domain is added to the list"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"The service will be disabled and all blacklist data will be deleted. "
|
||||||
|
"Continue?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Time"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Tor configuration file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Tor mode"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Total-proxy is on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Transparent proxy port for iptables rules"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unable to execute or read contents"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unable to read the contents"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unable to save the changes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unable to save the contents"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Update blacklist"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Updating"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Use optional DNS resolver"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "User entries"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "VPN interface"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "VPN mode"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "VPN routing error! Need restart"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ex:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "user entries only"
|
||||||
|
msgstr ""
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"admin/services/ruantiblock": {
|
||||||
|
"title": "Ruantiblock",
|
||||||
|
"order": 60,
|
||||||
|
"action": {
|
||||||
|
"type": "alias",
|
||||||
|
"path": "admin/services/ruantiblock/service"
|
||||||
|
},
|
||||||
|
"depends": {
|
||||||
|
"acl": [ "luci-app-ruantiblock" ],
|
||||||
|
"fs": {
|
||||||
|
"/usr/bin/ruantiblock": "executable",
|
||||||
|
"/etc/init.d/ruantiblock": "executable"
|
||||||
|
},
|
||||||
|
"uci": { "ruantiblock": true }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"admin/services/ruantiblock/service": {
|
||||||
|
"title": "Service",
|
||||||
|
"order": 10,
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "ruantiblock/service"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"admin/services/ruantiblock/settings": {
|
||||||
|
"title": "Settings",
|
||||||
|
"order": 20,
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "ruantiblock/settings"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"admin/services/ruantiblock/cron": {
|
||||||
|
"title": "Blacklist updates",
|
||||||
|
"order": 30,
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "ruantiblock/cron"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"admin/services/ruantiblock/info": {
|
||||||
|
"title": "Statistics",
|
||||||
|
"order": 40,
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "ruantiblock/info"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"admin/services/ruantiblock/log": {
|
||||||
|
"title": "Log",
|
||||||
|
"order": 50,
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "ruantiblock/log"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"luci-app-ruantiblock": {
|
||||||
|
"description": "Grant access to ruantiblock procedures",
|
||||||
|
"read": {
|
||||||
|
"cgi-io": [ "exec" ],
|
||||||
|
"file": {
|
||||||
|
"/usr/bin": [ "list" ],
|
||||||
|
"/etc/ruantiblock/fqdn_filter": [ "read" ],
|
||||||
|
"/etc/ruantiblock/ip_filter": [ "read" ],
|
||||||
|
"/etc/ruantiblock/user_entries": [ "read" ],
|
||||||
|
"/var/run/ruantiblock.token": [ "read" ],
|
||||||
|
"/etc/tor/torrc": [ "read" ],
|
||||||
|
"/etc/crontabs/root": [ "read" ],
|
||||||
|
"/usr/bin/ruantiblock": [ "exec" ],
|
||||||
|
"/etc/init.d/ruantiblock": [ "exec" ],
|
||||||
|
"/etc/init.d/tor enabled": [ "exec" ],
|
||||||
|
"/etc/init.d/tor restart": [ "exec" ],
|
||||||
|
"/etc/init.d/cron enabled": [ "exec" ],
|
||||||
|
"/etc/init.d/cron enable": [ "exec" ],
|
||||||
|
"/etc/init.d/cron restart": [ "exec" ],
|
||||||
|
"/sbin/logread -e ruantiblock": [ "exec" ],
|
||||||
|
"/usr/sbin/logread -e ruantiblock": [ "exec" ]
|
||||||
|
},
|
||||||
|
"uci": [ "network", "ruantiblock" ]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"file": {
|
||||||
|
"/etc/ruantiblock/fqdn_filter": [ "write" ],
|
||||||
|
"/etc/ruantiblock/ip_filter": [ "write" ],
|
||||||
|
"/etc/ruantiblock/user_entries": [ "write" ],
|
||||||
|
"/etc/tor/torrc": [ "write" ],
|
||||||
|
"/etc/crontabs/root": [ "write" ]
|
||||||
|
},
|
||||||
|
"uci": [ "ruantiblock" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+591
@@ -0,0 +1,591 @@
|
|||||||
|
#!/usr/bin/env lua
|
||||||
|
|
||||||
|
--[[
|
||||||
|
(с) 2020 gSpot (https://github.com/gSpotx2f/ruantiblock_openwrt)
|
||||||
|
|
||||||
|
lua == 5.1
|
||||||
|
--]]
|
||||||
|
|
||||||
|
-------------------------- Class constructor -------------------------
|
||||||
|
|
||||||
|
local function Class(super, t)
|
||||||
|
local class = t or {}
|
||||||
|
local function instance_constructor(cls, t)
|
||||||
|
local instance = t or {}
|
||||||
|
setmetatable(instance, cls)
|
||||||
|
instance.__class = cls
|
||||||
|
return instance
|
||||||
|
end
|
||||||
|
if not super then
|
||||||
|
local mt = {__call = instance_constructor}
|
||||||
|
mt.__index = mt
|
||||||
|
setmetatable(class, mt)
|
||||||
|
elseif type(super) == "table" and super.__index and super.__call then
|
||||||
|
setmetatable(class, super)
|
||||||
|
class.__super = super
|
||||||
|
else
|
||||||
|
error("Argument error! Incorrect object of a 'super'")
|
||||||
|
end
|
||||||
|
class.__index = class
|
||||||
|
class.__call = instance_constructor
|
||||||
|
return class
|
||||||
|
end
|
||||||
|
|
||||||
|
------------------------------ Settings ------------------------------
|
||||||
|
|
||||||
|
local Config = Class(nil, {
|
||||||
|
environ_table = {
|
||||||
|
["EXEC_DIR"] = true,
|
||||||
|
["BLLIST_SOURCE"] = true,
|
||||||
|
["BLLIST_MODE"] = true,
|
||||||
|
["ALT_NSLOOKUP"] = true,
|
||||||
|
["ALT_DNS_ADDR"] = true,
|
||||||
|
["USE_IDN"] = true,
|
||||||
|
["OPT_EXCLUDE_SLD"] = true,
|
||||||
|
["OPT_EXCLUDE_MASKS"] = true,
|
||||||
|
["FQDN_FILTER"] = true,
|
||||||
|
["FQDN_FILTER_FILE"] = true,
|
||||||
|
["IP_FILTER"] = true,
|
||||||
|
["IP_FILTER_FILE"] = true,
|
||||||
|
["SD_LIMIT"] = true,
|
||||||
|
["IP_LIMIT"] = true,
|
||||||
|
["OPT_EXCLUDE_NETS"] = true,
|
||||||
|
["BLLIST_MIN_ENTRS"] = true,
|
||||||
|
["STRIP_WWW"] = true,
|
||||||
|
["DATA_DIR"] = true,
|
||||||
|
["IPSET_DNSMASQ"] = true,
|
||||||
|
["IPSET_IP_TMP"] = true,
|
||||||
|
["IPSET_CIDR_TMP"] = true,
|
||||||
|
["DNSMASQ_DATA_FILE"] = true,
|
||||||
|
["IP_DATA_FILE"] = true,
|
||||||
|
["UPDATE_STATUS_FILE"] = true,
|
||||||
|
["RBL_ALL_URL"] = true,
|
||||||
|
["RBL_IP_URL"] = true,
|
||||||
|
["ZI_ALL_URL"] = true,
|
||||||
|
["AF_IP_URL"] = true,
|
||||||
|
["AF_FQDN_URL"] = true,
|
||||||
|
["AZ_ENCODING"] = true,
|
||||||
|
["RBL_ENCODING"] = true,
|
||||||
|
["ZI_ENCODING"] = true,
|
||||||
|
["AF_ENCODING"] = true,
|
||||||
|
["SUMMARIZE_IP"] = true,
|
||||||
|
["SUMMARIZE_CIDR"] = true,
|
||||||
|
},
|
||||||
|
FQDN_FILTER_PATTERNS = {},
|
||||||
|
IP_FILTER_PATTERNS = {},
|
||||||
|
-- iconv type: standalone iconv or lua-iconv (standalone, lua)
|
||||||
|
ICONV_TYPE = "standalone",
|
||||||
|
-- standalone iconv
|
||||||
|
ICONV_CMD = "iconv",
|
||||||
|
WGET_CMD = "wget --no-check-certificate -q -O -",
|
||||||
|
encoding = "UTF-8",
|
||||||
|
site_encoding = "",
|
||||||
|
http_send_headers = {
|
||||||
|
--["User-Agent"] = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
Config.wget_user_agent = (Config.http_send_headers["User-Agent"]) and ' -U "' .. Config.http_send_headers["User-Agent"] .. '"' or ''
|
||||||
|
|
||||||
|
-- Load external config
|
||||||
|
|
||||||
|
function Config:load_config(t)
|
||||||
|
local config_arrays = {
|
||||||
|
["OPT_EXCLUDE_SLD"] = true,
|
||||||
|
["OPT_EXCLUDE_NETS"] = true,
|
||||||
|
}
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
if config_arrays[k] then
|
||||||
|
local value_table = {}
|
||||||
|
for v in v:gmatch('[^" ]+') do
|
||||||
|
value_table[v] = true
|
||||||
|
end
|
||||||
|
self[k] = value_table
|
||||||
|
else
|
||||||
|
self[k] = v:match("^[0-9.]+$") and tonumber(v) or v:gsub('"', '')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Config:load_environ_config()
|
||||||
|
local cfg_table = {}
|
||||||
|
for var in pairs(self.environ_table) do
|
||||||
|
val = os.getenv(var)
|
||||||
|
if val then
|
||||||
|
cfg_table[var] = val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:load_config(cfg_table)
|
||||||
|
end
|
||||||
|
|
||||||
|
Config:load_environ_config()
|
||||||
|
|
||||||
|
local function remap_bool(val)
|
||||||
|
return (val ~= 0 and val ~= false and val ~= nil) and true or false
|
||||||
|
end
|
||||||
|
|
||||||
|
Config.ALT_NSLOOKUP = remap_bool(Config.ALT_NSLOOKUP)
|
||||||
|
Config.USE_IDN = remap_bool(Config.USE_IDN)
|
||||||
|
Config.STRIP_WWW = remap_bool(Config.STRIP_WWW)
|
||||||
|
Config.FQDN_FILTER = remap_bool(Config.FQDN_FILTER)
|
||||||
|
Config.IP_FILTER = remap_bool(Config.IP_FILTER)
|
||||||
|
Config.SUMMARIZE_IP = remap_bool(Config.SUMMARIZE_IP)
|
||||||
|
Config.SUMMARIZE_CIDR = remap_bool(Config.SUMMARIZE_CIDR)
|
||||||
|
|
||||||
|
-- Load filters
|
||||||
|
|
||||||
|
function Config:load_filter_files()
|
||||||
|
function load_file(file, t)
|
||||||
|
local file_handler = io.open(file, "r")
|
||||||
|
if file_handler then
|
||||||
|
for line in file_handler:lines() do
|
||||||
|
if #line > 0 and line:match("^[^#]") then
|
||||||
|
t[line] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
file_handler:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self.FQDN_FILTER then
|
||||||
|
load_file(self.FQDN_FILTER_FILE, self.FQDN_FILTER_PATTERNS)
|
||||||
|
end
|
||||||
|
if self.IP_FILTER then
|
||||||
|
load_file(self.IP_FILTER_FILE, self.IP_FILTER_PATTERNS)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Config:load_filter_files()
|
||||||
|
|
||||||
|
-- Import packages
|
||||||
|
|
||||||
|
local function prequire(package)
|
||||||
|
local ret_val, pkg = pcall(require, package)
|
||||||
|
return ret_val and pkg
|
||||||
|
end
|
||||||
|
|
||||||
|
local http = prequire("socket.http")
|
||||||
|
local https = prequire("ssl.https")
|
||||||
|
local ltn12 = prequire("ltn12")
|
||||||
|
if not ltn12 then
|
||||||
|
error("You need to install luasocket or ltn12...")
|
||||||
|
end
|
||||||
|
|
||||||
|
local idn = prequire("idn")
|
||||||
|
if Config.USE_IDN and not idn then
|
||||||
|
error("You need to install idn.lua (github.com/haste/lua-idn) or 'USE_IDN' must be set to '0'")
|
||||||
|
end
|
||||||
|
local iconv = prequire("iconv")
|
||||||
|
|
||||||
|
local si, it
|
||||||
|
if prequire("bit") then
|
||||||
|
it = prequire("iptool")
|
||||||
|
if it then
|
||||||
|
si = prequire("ruab_sum_ip")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not si then
|
||||||
|
Config.SUMMARIZE_CIDR = false
|
||||||
|
Config.SUMMARIZE_IP = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check iconv
|
||||||
|
|
||||||
|
if Config.ICONV_TYPE == "standalone" then
|
||||||
|
local handler = io.popen("which " .. Config.ICONV_CMD)
|
||||||
|
local ret_val = handler:read("*l")
|
||||||
|
handler:close()
|
||||||
|
if not ret_val then
|
||||||
|
Config.ICONV_CMD = nil
|
||||||
|
end
|
||||||
|
elseif Config.ICONV_TYPE == "lua" then
|
||||||
|
else
|
||||||
|
error("Config.ICONV_TYPE should be either 'lua' or 'standalone'")
|
||||||
|
end
|
||||||
|
|
||||||
|
------------------------------ Classes -------------------------------
|
||||||
|
|
||||||
|
local BlackListParser = Class(Config, {
|
||||||
|
ip_pattern = "%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?",
|
||||||
|
cidr_pattern = "%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?/%d%d?",
|
||||||
|
fqdn_pattern = "[a-z0-9_%.%-]-[a-z0-9_%-]+%.[a-z0-9%.%-]",
|
||||||
|
url = "http://127.0.0.1",
|
||||||
|
records_separator = "\n",
|
||||||
|
ips_separator = " | ",
|
||||||
|
})
|
||||||
|
|
||||||
|
function BlackListParser:new(t)
|
||||||
|
-- extended instance constructor
|
||||||
|
local instance = self(t)
|
||||||
|
instance.url = instance["url"] or self.url
|
||||||
|
instance.records_separator = instance["records_separator"] or self.records_separator
|
||||||
|
instance.ips_separator = instance["ips_separator"] or self.ips_separator
|
||||||
|
instance.site_encoding = instance["site_encoding"] or self.site_encoding
|
||||||
|
instance.ip_records_count = 0
|
||||||
|
instance.ip_count = 0
|
||||||
|
instance.ip_subnet_table = {}
|
||||||
|
instance.cidr_count = 0
|
||||||
|
instance.fqdn_table = {}
|
||||||
|
instance.fqdn_count = 0
|
||||||
|
instance.sld_table = {}
|
||||||
|
instance.fqdn_records_count = 0
|
||||||
|
instance.ip_table = {}
|
||||||
|
instance.cidr_table = {}
|
||||||
|
instance.iconv_handler = iconv and iconv.open(instance.encoding, instance.site_encoding) or nil
|
||||||
|
return instance
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:convert_encoding(input)
|
||||||
|
local output
|
||||||
|
if self.ICONV_TYPE == "lua" and self.iconv_handler then
|
||||||
|
output = self.iconv_handler:iconv(input)
|
||||||
|
elseif self.ICONV_TYPE == "standalone" and self.ICONV_CMD then
|
||||||
|
local iconv_handler = assert(io.popen('printf \'' .. input .. '\' | ' .. self.ICONV_CMD .. ' -f "' .. self.site_encoding .. '" -t "' .. self.encoding .. '"', 'r'))
|
||||||
|
output = iconv_handler:read("*a")
|
||||||
|
iconv_handler:close()
|
||||||
|
end
|
||||||
|
return (output)
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:convert_to_punycode(input)
|
||||||
|
if self.site_encoding and self.site_encoding ~= "" then
|
||||||
|
input = self:convert_encoding(input)
|
||||||
|
end
|
||||||
|
return input and (idn.encode(input))
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:check_filter(str, filter_patterns)
|
||||||
|
if filter_patterns and str then
|
||||||
|
for pattern in pairs(filter_patterns) do
|
||||||
|
if str:match(pattern) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:get_subnet(ip)
|
||||||
|
return ip:match("^(%d+%.%d+%.%d+%.)%d+$")
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:fill_ip_tables(val)
|
||||||
|
if val and val ~= "" then
|
||||||
|
for ip_entry in val:gmatch(self.ip_pattern .. "/?%d?%d?") do
|
||||||
|
if not self.IP_FILTER or (self.IP_FILTER and not self:check_filter(ip_entry, self.IP_FILTER_PATTERNS)) then
|
||||||
|
if ip_entry:match("^" .. self.ip_pattern .. "$") and not self.ip_table[ip_entry] then
|
||||||
|
local subnet = self:get_subnet(ip_entry)
|
||||||
|
if subnet and (self.OPT_EXCLUDE_NETS[subnet] or ((not self.IP_LIMIT or self.IP_LIMIT == 0) or (not self.ip_subnet_table[subnet] or self.ip_subnet_table[subnet] < self.IP_LIMIT))) then
|
||||||
|
self.ip_table[ip_entry] = subnet
|
||||||
|
self.ip_subnet_table[subnet] = (self.ip_subnet_table[subnet] or 0) + 1
|
||||||
|
self.ip_count = self.ip_count + 1
|
||||||
|
end
|
||||||
|
elseif ip_entry:match("^" .. self.cidr_pattern .. "$") and not self.cidr_table[ip_entry] then
|
||||||
|
self.cidr_table[ip_entry] = true
|
||||||
|
self.cidr_count = self.cidr_count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:get_sld(fqdn)
|
||||||
|
return fqdn:match("^[a-z0-9_%.%-]-([a-z0-9_%-]+%.[a-z0-9%-]+)$")
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:fill_domain_tables(val)
|
||||||
|
val = val:gsub("%*%.", ""):gsub("%.$", ""):lower()
|
||||||
|
if self.STRIP_WWW then
|
||||||
|
val = val:gsub("^www[0-9]?%.", "")
|
||||||
|
end
|
||||||
|
if not self.FQDN_FILTER or (self.FQDN_FILTER and not self:check_filter(val, self.FQDN_FILTER_PATTERNS)) then
|
||||||
|
if val:match("^" .. self.fqdn_pattern .. "+$") then
|
||||||
|
elseif self.USE_IDN and val:match("^[^\\/&%?]-[^\\/&%?%.]+%.[^\\/&%?%.]+%.?$") then
|
||||||
|
val = self:convert_to_punycode(val)
|
||||||
|
if not val then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local sld = self:get_sld(val)
|
||||||
|
if sld and (self.OPT_EXCLUDE_SLD[sld] or ((not self.SD_LIMIT or self.SD_LIMIT == 0) or (not self.sld_table[sld] or self.sld_table[sld] < self.SD_LIMIT))) then
|
||||||
|
self.fqdn_table[val] = sld
|
||||||
|
self.sld_table[sld] = (self.sld_table[sld] or 0) + 1
|
||||||
|
self.fqdn_count = self.fqdn_count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:sink()
|
||||||
|
-- Must be reload in the subclass
|
||||||
|
error("Method BlackListParser:sink() must be reload in the subclass!")
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:optimize_ip_table()
|
||||||
|
local optimized_table = {}
|
||||||
|
for ipaddr, subnet in pairs(self.ip_table) do
|
||||||
|
if self.ip_subnet_table[subnet] then
|
||||||
|
if (self.IP_LIMIT and self.IP_LIMIT > 0 and not self.OPT_EXCLUDE_NETS[subnet]) and self.ip_subnet_table[subnet] >= self.IP_LIMIT then
|
||||||
|
self.cidr_table[string.format("%s0/24", subnet)] = true
|
||||||
|
self.ip_subnet_table[subnet] = nil
|
||||||
|
self.cidr_count = self.cidr_count + 1
|
||||||
|
else
|
||||||
|
optimized_table[ipaddr] = true
|
||||||
|
self.ip_records_count = self.ip_records_count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.ip_table = optimized_table
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function BlackListParser:optimize_fqdn_table()
|
||||||
|
local optimized_table = {}
|
||||||
|
if self.OPT_EXCLUDE_MASKS and #self.OPT_EXCLUDE_MASKS > 0 then
|
||||||
|
for sld in pairs(self.sld_table) do
|
||||||
|
for _, pattern in ipairs(self.OPT_EXCLUDE_MASKS) do
|
||||||
|
if sld:find(pattern) then
|
||||||
|
self.sld_table[sld] = 0
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for fqdn, sld in pairs(self.fqdn_table) do
|
||||||
|
local key_value = fqdn
|
||||||
|
if (not self.fqdn_table[sld] or fqdn == sld) and self.sld_table[sld] then
|
||||||
|
if (self.SD_LIMIT and self.SD_LIMIT > 0 and not self.OPT_EXCLUDE_SLD[sld]) and self.sld_table[sld] >= self.SD_LIMIT then
|
||||||
|
key_value = sld
|
||||||
|
self.sld_table[sld] = nil
|
||||||
|
end
|
||||||
|
optimized_table[key_value] = true
|
||||||
|
self.fqdn_records_count = self.fqdn_records_count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.fqdn_table = optimized_table
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:group_ip_ranges()
|
||||||
|
for i in si.summarize_ip_ranges(self.ip_table, true) do
|
||||||
|
self.cidr_table[string.format("%s/%s", it.int_to_ip(i[1]), i[2])] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:group_cidr_ranges()
|
||||||
|
for i in si.summarize_nets(self.cidr_table, true) do
|
||||||
|
self.cidr_table[string.format("%s/%s", it.int_to_ip(i[1]), i[2])] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:write_ipset_config()
|
||||||
|
local file_handler = assert(io.open(self.IP_DATA_FILE, "w"), "Could not open ipset config")
|
||||||
|
local i = 0
|
||||||
|
for ipaddr in pairs(self.ip_table) do
|
||||||
|
file_handler:write(string.format("add %s %s\n", self.IPSET_IP_TMP, ipaddr))
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
self.ip_records_count = i
|
||||||
|
local c = 0
|
||||||
|
for cidr in pairs(self.cidr_table) do
|
||||||
|
file_handler:write(string.format("add %s %s\n", self.IPSET_CIDR_TMP, cidr))
|
||||||
|
c = c + 1
|
||||||
|
end
|
||||||
|
self.cidr_count = c
|
||||||
|
file_handler:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:write_dnsmasq_config()
|
||||||
|
local file_handler = assert(io.open(self.DNSMASQ_DATA_FILE, "w"), "Could not open dnsmasq config")
|
||||||
|
for fqdn in pairs(self.fqdn_table) do
|
||||||
|
if self.ALT_NSLOOKUP then
|
||||||
|
file_handler:write(string.format("server=/%s/%s\n", fqdn, self.ALT_DNS_ADDR))
|
||||||
|
end
|
||||||
|
file_handler:write(string.format("ipset=/%s/%s\n", fqdn, self.IPSET_DNSMASQ))
|
||||||
|
end
|
||||||
|
file_handler:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:write_update_status()
|
||||||
|
local file_handler = assert(io.open(self.UPDATE_STATUS_FILE, "w"), "Could not open 'update_status' file")
|
||||||
|
file_handler:write(string.format("%d %d %d", self.ip_records_count, self.cidr_count, self.fqdn_records_count))
|
||||||
|
file_handler:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:chunk_buffer()
|
||||||
|
local buff = ""
|
||||||
|
local ret_value = ""
|
||||||
|
local last_chunk
|
||||||
|
return function(chunk)
|
||||||
|
if last_chunk then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
if chunk then
|
||||||
|
buff = buff .. chunk
|
||||||
|
local last_rs_position = select(2, buff:find("^.*" .. self.records_separator))
|
||||||
|
if last_rs_position then
|
||||||
|
ret_value = buff:sub(1, last_rs_position)
|
||||||
|
buff = buff:sub((last_rs_position + 1), -1)
|
||||||
|
else
|
||||||
|
ret_value = ""
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ret_value = buff
|
||||||
|
last_chunk = true
|
||||||
|
end
|
||||||
|
return (ret_value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:get_http_data(url)
|
||||||
|
local ret_val, ret_code, ret_headers
|
||||||
|
local http_module = url:match("^https") and https or http
|
||||||
|
if http_module then
|
||||||
|
local http_sink = ltn12.sink.chain(self:chunk_buffer(), self:sink())
|
||||||
|
ret_val, ret_code, ret_headers = http_module.request{url = url, sink = http_sink, headers = self.http_send_headers}
|
||||||
|
if not ret_val or ret_code ~= 200 then
|
||||||
|
ret_val = nil
|
||||||
|
print(string.format("Connection error! (%s) URL: %s", ret_code, url))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not ret_val then
|
||||||
|
local wget_sink = ltn12.sink.chain(self:chunk_buffer(), self:sink())
|
||||||
|
ret_val = ltn12.pump.all(ltn12.source.file(io.popen(self.WGET_CMD .. self.wget_user_agent .. ' "' .. url .. '"', 'r')), wget_sink)
|
||||||
|
end
|
||||||
|
return (ret_val == 1) and true or false
|
||||||
|
end
|
||||||
|
|
||||||
|
function BlackListParser:run()
|
||||||
|
local return_code = 0
|
||||||
|
if self:get_http_data(self.url) then
|
||||||
|
if (self.fqdn_count + self.ip_count + self.cidr_count) > self.BLLIST_MIN_ENTRS then
|
||||||
|
self:optimize_fqdn_table()
|
||||||
|
self:optimize_ip_table()
|
||||||
|
if self.SUMMARIZE_IP then
|
||||||
|
self:group_ip_ranges()
|
||||||
|
end
|
||||||
|
if self.SUMMARIZE_CIDR then
|
||||||
|
self:group_cidr_ranges()
|
||||||
|
end
|
||||||
|
self:write_ipset_config()
|
||||||
|
self:write_dnsmasq_config()
|
||||||
|
return_code = 0
|
||||||
|
else
|
||||||
|
return_code = 2
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return_code = 1
|
||||||
|
end
|
||||||
|
self:write_update_status()
|
||||||
|
return return_code
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Subclasses
|
||||||
|
|
||||||
|
local function ip_sink(self)
|
||||||
|
return function(chunk)
|
||||||
|
if chunk and chunk ~= "" then
|
||||||
|
for ip_string in chunk:gmatch(self.ip_string_pattern) do
|
||||||
|
self:fill_ip_tables(ip_string)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fqdn_sink_func(self, ip_str, fqdn_str)
|
||||||
|
if #fqdn_str > 0 and not fqdn_str:match("^" .. self.ip_pattern .. "$") then
|
||||||
|
if self:fill_domain_tables(fqdn_str) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:fill_ip_tables(ip_str)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- rublacklist.net
|
||||||
|
|
||||||
|
local Rbl = Class(BlackListParser, {
|
||||||
|
url = Config.RBL_ALL_URL,
|
||||||
|
ips_separator = ", ",
|
||||||
|
ip_string_pattern = "([a-f0-9/.:]+),?\n?",
|
||||||
|
})
|
||||||
|
|
||||||
|
function Rbl:sink()
|
||||||
|
return function(chunk)
|
||||||
|
if chunk and chunk ~= "" then
|
||||||
|
for ip_str, fqdn_str in chunk:gmatch("%[([a-f0-9/.:', ]+)%],([^,]-),.-" .. self.records_separator) do
|
||||||
|
fqdn_sink_func(self, ip_str, fqdn_str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local RblIp = Class(Rbl, {
|
||||||
|
url = Config.RBL_IP_URL,
|
||||||
|
sink = ip_sink,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- zapret-info
|
||||||
|
|
||||||
|
local Zi = Class(BlackListParser, {
|
||||||
|
url = Config.ZI_ALL_URL,
|
||||||
|
ip_string_pattern = "([a-f0-9%.:/ |]+);.-\n",
|
||||||
|
site_encoding = Config.ZI_ENCODING,
|
||||||
|
})
|
||||||
|
|
||||||
|
function Zi:sink()
|
||||||
|
return function(chunk)
|
||||||
|
if chunk and chunk ~= "" then
|
||||||
|
for ip_str, fqdn_str in chunk:gmatch("([^;]-);([^;]-);.-" .. self.records_separator) do
|
||||||
|
fqdn_sink_func(self, ip_str, fqdn_str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local ZiIp = Class(Zi, {
|
||||||
|
sink = ip_sink,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- antifilter
|
||||||
|
|
||||||
|
local Af = Class(BlackListParser, {
|
||||||
|
url = Config.AF_FQDN_URL,
|
||||||
|
ip_string_pattern = "(.-)\n",
|
||||||
|
})
|
||||||
|
|
||||||
|
function Af:sink()
|
||||||
|
local entry_pattern = "((.-))" .. self.records_separator
|
||||||
|
return function(chunk)
|
||||||
|
if chunk and chunk ~= "" then
|
||||||
|
for fqdn_str, ip_str in chunk:gmatch(entry_pattern) do
|
||||||
|
fqdn_sink_func(self, ip_str, fqdn_str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local AfIp = Class(Af, {
|
||||||
|
url = Config.AF_IP_URL,
|
||||||
|
sink = ip_sink,
|
||||||
|
BLLIST_MIN_ENTRS = 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
----------------------------- Main section ------------------------------
|
||||||
|
|
||||||
|
local ctx_table = {
|
||||||
|
["ip"] = {["rublacklist"] = RblIp, ["zapret-info"] = ZiIp, ["antifilter"] = AfIp},
|
||||||
|
["fqdn"] = {["rublacklist"] = Rbl, ["zapret-info"] = Zi, ["antifilter"] = Af},
|
||||||
|
}
|
||||||
|
|
||||||
|
local return_code = 1
|
||||||
|
local ctx = ctx_table[Config.BLLIST_MODE] and ctx_table[Config.BLLIST_MODE][Config.BLLIST_SOURCE]
|
||||||
|
if ctx then
|
||||||
|
return_code = ctx:new():run()
|
||||||
|
else
|
||||||
|
error("Wrong configuration! (Config.BLLIST_MODE or Config.BLLIST_SOURCE)")
|
||||||
|
end
|
||||||
|
|
||||||
|
os.exit(return_code)
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
--[[
|
||||||
|
(с) 2020 gSpot (https://github.com/gSpotx2f/ruantiblock_openwrt)
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local it = require("iptool")
|
||||||
|
|
||||||
|
HOSTS_LIMIT = 0
|
||||||
|
NETS_LIMIT = 0
|
||||||
|
|
||||||
|
local function sort_ip_list(t)
|
||||||
|
local t2 = {}
|
||||||
|
for k in pairs(t) do
|
||||||
|
t2[#t2 + 1] = k
|
||||||
|
end
|
||||||
|
table.sort(t2, function(a, b) return it.ip_to_int(a) < it.ip_to_int(b) end)
|
||||||
|
return t2
|
||||||
|
end
|
||||||
|
|
||||||
|
local function group_ip_ranges(ip_list, raw_list)
|
||||||
|
local function remove_items(start, stop)
|
||||||
|
for i = start, stop do
|
||||||
|
if raw_list[i] then
|
||||||
|
raw_list[i] = nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local item = it.int_to_ip(i)
|
||||||
|
if raw_list[item] then
|
||||||
|
raw_list[it.int_to_ip(i)] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local start, stop, last_call
|
||||||
|
local hosts = 1
|
||||||
|
local i = 0
|
||||||
|
return function()
|
||||||
|
local ret_val
|
||||||
|
local ip
|
||||||
|
repeat
|
||||||
|
i = i + 1
|
||||||
|
ip = ip_list[i]
|
||||||
|
if ip then
|
||||||
|
local ip_dec = it.ip_to_int(ip)
|
||||||
|
if stop and (stop + 1) == ip_dec then
|
||||||
|
hosts = hosts + 1
|
||||||
|
else
|
||||||
|
if hosts > 1 and hosts >= HOSTS_LIMIT then
|
||||||
|
if raw_list then
|
||||||
|
remove_items(start, stop)
|
||||||
|
end
|
||||||
|
ret_val = {[1] = start, [2] = stop}
|
||||||
|
start = ip_dec
|
||||||
|
stop = ip_dec
|
||||||
|
hosts = 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
start = ip_dec
|
||||||
|
end
|
||||||
|
stop = ip_dec
|
||||||
|
elseif not last_call then
|
||||||
|
if hosts > 1 and hosts >= HOSTS_LIMIT then
|
||||||
|
if raw_list then
|
||||||
|
remove_items(start, stop)
|
||||||
|
end
|
||||||
|
ret_val = {[1] = start, [2] = stop}
|
||||||
|
last_call = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
until not ip
|
||||||
|
return ret_val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function sort_net_list(t)
|
||||||
|
local t2 = {}
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
local ip, pref = it.get_network_addr(k)
|
||||||
|
t2[#t2 + 1] = {[1] = ip, [2] = pref}
|
||||||
|
end
|
||||||
|
table.sort(t2, function(a, b) return a[1] < b[1] end)
|
||||||
|
return t2
|
||||||
|
end
|
||||||
|
|
||||||
|
local function group_nets(cidr_list, raw_list)
|
||||||
|
local function remove_items(start, stop)
|
||||||
|
for i = start, stop, 256 do
|
||||||
|
local item = it.int_to_ip(i) .. "/24"
|
||||||
|
if raw_list[item] then
|
||||||
|
raw_list[item] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local start, stop, last_call, curr_supernet
|
||||||
|
local nets = 1
|
||||||
|
local i = 0
|
||||||
|
return function()
|
||||||
|
local ret_val
|
||||||
|
local cidr
|
||||||
|
repeat
|
||||||
|
i = i + 1
|
||||||
|
cidr = cidr_list[i]
|
||||||
|
if cidr then
|
||||||
|
local network_address, prefixlen
|
||||||
|
if type(cidr) == "string" then
|
||||||
|
network_address, prefixlen = it.get_network_addr(cidr)
|
||||||
|
elseif type(cidr) == "table" then
|
||||||
|
network_address, prefixlen = cidr[1], cidr[2]
|
||||||
|
end
|
||||||
|
if prefixlen == 24 then
|
||||||
|
local supernet = it.get_supernet({[1] = network_address, [2] = prefixlen}, 16)
|
||||||
|
if stop and supernet == curr_supernet and (stop + 256) == network_address then
|
||||||
|
nets = nets + 1
|
||||||
|
else
|
||||||
|
if nets > 1 and nets >= NETS_LIMIT then
|
||||||
|
if raw_list then
|
||||||
|
remove_items(start, stop)
|
||||||
|
end
|
||||||
|
ret_val = {[1] = start, [2] = stop + 255}
|
||||||
|
start = network_address
|
||||||
|
stop = network_address
|
||||||
|
nets = 1
|
||||||
|
curr_supernet = supernet
|
||||||
|
break
|
||||||
|
end
|
||||||
|
start = network_address
|
||||||
|
curr_supernet = supernet
|
||||||
|
end
|
||||||
|
stop = network_address
|
||||||
|
end
|
||||||
|
elseif not last_call then
|
||||||
|
if nets > 1 and nets >= NETS_LIMIT then
|
||||||
|
if raw_list then
|
||||||
|
remove_items(start, stop)
|
||||||
|
end
|
||||||
|
ret_val = {[1] = start, [2] = stop + 255}
|
||||||
|
last_call = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
until not cidr
|
||||||
|
return ret_val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function summarize_ranges(ip_iter)
|
||||||
|
local s_range_iter
|
||||||
|
return function()
|
||||||
|
-- lua >= 5.2
|
||||||
|
--::check_prefix::
|
||||||
|
if s_range_iter then
|
||||||
|
repeat
|
||||||
|
local ip_t = s_range_iter()
|
||||||
|
if ip_t then
|
||||||
|
return ip_t
|
||||||
|
end
|
||||||
|
until not ip_t
|
||||||
|
end
|
||||||
|
local ip_range = ip_iter()
|
||||||
|
if ip_range then
|
||||||
|
s_range_iter = it.summarize_address_range(ip_range[1], ip_range[2])
|
||||||
|
if s_range_iter then
|
||||||
|
-- lua >= 5.2
|
||||||
|
--goto check_prefix
|
||||||
|
-- lua < 5.2
|
||||||
|
repeat
|
||||||
|
local ip_t = s_range_iter()
|
||||||
|
if ip_t then
|
||||||
|
return ip_t
|
||||||
|
end
|
||||||
|
until not ip_t
|
||||||
|
--
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function summarize_ip_ranges(ip_list, modify_raw_list)
|
||||||
|
local summ_iter = summarize_ranges(group_ip_ranges(sort_ip_list(ip_list),
|
||||||
|
modify_raw_list and ip_list)
|
||||||
|
)
|
||||||
|
return function()
|
||||||
|
repeat
|
||||||
|
local ip_t = summ_iter()
|
||||||
|
if ip_t and ip_t[2] == 32 then
|
||||||
|
if modify_raw_list then
|
||||||
|
ip_list[it.int_to_ip(ip_t[1])] = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return ip_t
|
||||||
|
end
|
||||||
|
until not ip_t
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function summarize_nets(cidr_list, modify_raw_list)
|
||||||
|
return summarize_ranges(group_nets(sort_net_list(cidr_list),
|
||||||
|
modify_raw_list and cidr_list))
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
summarize_ip_ranges = summarize_ip_ranges,
|
||||||
|
summarize_nets = summarize_nets,
|
||||||
|
}
|
||||||
+495
@@ -0,0 +1,495 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
(с) 2020 gSpot (https://github.com/gSpotx2f/ruantiblock_openwrt)
|
||||||
|
|
||||||
|
Python >= 3.6
|
||||||
|
"""
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
|
import sys
|
||||||
|
from urllib import request
|
||||||
|
from ruab_sum_ip import summarize_ip_ranges, summarize_nets
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
environ_list = [
|
||||||
|
"BLLIST_SOURCE",
|
||||||
|
"BLLIST_MODE",
|
||||||
|
"ALT_NSLOOKUP",
|
||||||
|
"ALT_DNS_ADDR",
|
||||||
|
"USE_IDN",
|
||||||
|
"OPT_EXCLUDE_SLD",
|
||||||
|
"OPT_EXCLUDE_MASKS",
|
||||||
|
"FQDN_FILTER",
|
||||||
|
"FQDN_FILTER_FILE",
|
||||||
|
"IP_FILTER",
|
||||||
|
"IP_FILTER_FILE",
|
||||||
|
"SD_LIMIT",
|
||||||
|
"IP_LIMIT",
|
||||||
|
"OPT_EXCLUDE_NETS",
|
||||||
|
"BLLIST_MIN_ENTRS",
|
||||||
|
"STRIP_WWW",
|
||||||
|
"DATA_DIR",
|
||||||
|
"IPSET_DNSMASQ",
|
||||||
|
"IPSET_IP_TMP",
|
||||||
|
"IPSET_CIDR_TMP",
|
||||||
|
"DNSMASQ_DATA_FILE",
|
||||||
|
"IP_DATA_FILE",
|
||||||
|
"UPDATE_STATUS_FILE",
|
||||||
|
"RBL_ALL_URL",
|
||||||
|
"RBL_IP_URL",
|
||||||
|
"ZI_ALL_URL",
|
||||||
|
"AF_IP_URL",
|
||||||
|
"AF_FQDN_URL",
|
||||||
|
"AZ_ENCODING",
|
||||||
|
"RBL_ENCODING",
|
||||||
|
"ZI_ENCODING",
|
||||||
|
"AF_ENCODING",
|
||||||
|
"SUMMARIZE_IP",
|
||||||
|
"SUMMARIZE_CIDR",
|
||||||
|
]
|
||||||
|
FQDN_FILTER_PATTERNS = set()
|
||||||
|
IP_FILTER_PATTERNS = set()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_config(cls, cfg_dict):
|
||||||
|
|
||||||
|
def normalize_string(string):
|
||||||
|
return re.sub('"', '', string)
|
||||||
|
|
||||||
|
config_arrays = {
|
||||||
|
"OPT_EXCLUDE_SLD",
|
||||||
|
"OPT_EXCLUDE_NETS",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
for k, v in cfg_dict.items():
|
||||||
|
if k in config_arrays:
|
||||||
|
value = {normalize_string(i) for i in v.split(" ")}
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value = int(v)
|
||||||
|
except ValueError:
|
||||||
|
value = normalize_string(v)
|
||||||
|
setattr(cls, k, value)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_environ_config(cls):
|
||||||
|
cls._load_config({
|
||||||
|
k: v for k, v in os.environ.items()
|
||||||
|
if k in cls.environ_list
|
||||||
|
})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_filter(cls, file_path, filter_patterns):
|
||||||
|
try:
|
||||||
|
with open(file_path, "rt") as file_handler:
|
||||||
|
for line in file_handler:
|
||||||
|
if line and re.match("[^#]", line):
|
||||||
|
filter_patterns.add(line.strip())
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_fqdn_filter(cls, file_path=None):
|
||||||
|
if cls.FQDN_FILTER:
|
||||||
|
cls._load_filter(file_path or cls.FQDN_FILTER_FILE, cls.FQDN_FILTER_PATTERNS)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_ip_filter(cls, file_path=None):
|
||||||
|
if cls.IP_FILTER:
|
||||||
|
cls._load_filter(file_path or cls.IP_FILTER_FILE, cls.IP_FILTER_PATTERNS)
|
||||||
|
|
||||||
|
|
||||||
|
class ParserError(Exception):
|
||||||
|
def __init__(self, reason=None):
|
||||||
|
super().__init__(reason)
|
||||||
|
self.reason = reason
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.reason
|
||||||
|
|
||||||
|
|
||||||
|
class FieldValueError(ParserError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BlackListParser(Config):
|
||||||
|
def __init__(self):
|
||||||
|
self.ip_pattern = re.compile("(([0-9]{1,3}[.]){3})[0-9]{1,3}")
|
||||||
|
self.cidr_pattern = re.compile("([0-9]{1,3}[.]){3}[0-9]{1,3}/[0-9]{1,2}")
|
||||||
|
self.fqdn_pattern = re.compile(
|
||||||
|
"([а-яёa-z0-9_.*-]*?)([а-яёa-z0-9_-]+[.][а-яёa-z0-9-]+)",
|
||||||
|
re.U)
|
||||||
|
self.www_pattern = re.compile("^www[0-9]?[.]")
|
||||||
|
self.cyr_pattern = re.compile("[а-яё]", re.U)
|
||||||
|
self.fqdn_set = {}
|
||||||
|
self.sld_dict = {}
|
||||||
|
self.ip_set = {}
|
||||||
|
self.ip_subnet_dict = {}
|
||||||
|
self.cidr_set = set()
|
||||||
|
self.cidr_count = 0
|
||||||
|
self.ip_count = 0
|
||||||
|
self.output_fqdn_count = 0
|
||||||
|
self.ssl_unverified = False
|
||||||
|
self.send_headers_dict = {
|
||||||
|
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0",
|
||||||
|
}
|
||||||
|
### Proxies (ex.: self.proxies = {"http": "http://192.168.0.1:8080", "https": "http://192.168.0.1:8080"})
|
||||||
|
self.proxies = None
|
||||||
|
self.connect_timeout = None
|
||||||
|
self.data_chunk = 2048
|
||||||
|
self.url = "http://127.0.0.1"
|
||||||
|
self.records_separator = "\n"
|
||||||
|
self.fields_separator = ";"
|
||||||
|
self.ips_separator = " | "
|
||||||
|
self.default_site_encoding = "utf-8"
|
||||||
|
self.site_encoding = self.default_site_encoding
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _compile_filter_patterns(filters_seq):
|
||||||
|
return {
|
||||||
|
re.compile(i, re.U)
|
||||||
|
for i in filters_seq
|
||||||
|
if i and type(i) == str
|
||||||
|
}
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _make_connection(self,
|
||||||
|
url,
|
||||||
|
method="GET",
|
||||||
|
postData=None,
|
||||||
|
send_headers_dict=None,
|
||||||
|
timeout=None):
|
||||||
|
conn_object = http_code = received_headers = None
|
||||||
|
req_object = request.Request(url,
|
||||||
|
data=postData,
|
||||||
|
headers=send_headers_dict,
|
||||||
|
method=method)
|
||||||
|
opener_args = [request.ProxyHandler(self.proxies)]
|
||||||
|
if self.ssl_unverified:
|
||||||
|
opener_args.append(request.HTTPSHandler(context=ssl._create_unverified_context()))
|
||||||
|
try:
|
||||||
|
conn_object = request.build_opener(*opener_args).open(
|
||||||
|
req_object,
|
||||||
|
timeout=(
|
||||||
|
timeout if type(timeout) == int else socket._GLOBAL_DEFAULT_TIMEOUT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
http_code, received_headers = conn_object.status, conn_object.getheaders()
|
||||||
|
except Exception as exception_object:
|
||||||
|
print(f" Connection error! {exception_object} ( {url} )",
|
||||||
|
file=sys.stderr)
|
||||||
|
try:
|
||||||
|
yield (conn_object, http_code, received_headers)
|
||||||
|
except Exception as exception_object:
|
||||||
|
raise ParserError(f"Parser error! {exception_object} ( {self.url} )")
|
||||||
|
finally:
|
||||||
|
if conn_object:
|
||||||
|
conn_object.close()
|
||||||
|
|
||||||
|
def _download_data(self):
|
||||||
|
with self._make_connection(
|
||||||
|
self.url,
|
||||||
|
send_headers_dict=self.send_headers_dict,
|
||||||
|
timeout=self.connect_timeout
|
||||||
|
) as conn_params:
|
||||||
|
conn_object, http_code, _ = conn_params
|
||||||
|
if http_code == 200:
|
||||||
|
while True:
|
||||||
|
chunk = conn_object.read(self.data_chunk)
|
||||||
|
yield (chunk or None)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
|
||||||
|
def _align_chunk(self):
|
||||||
|
rest = bytes()
|
||||||
|
for chunk in self._download_data():
|
||||||
|
if chunk is None:
|
||||||
|
yield rest
|
||||||
|
continue
|
||||||
|
data, _, rest = (rest + chunk).rpartition(self.records_separator)
|
||||||
|
yield data
|
||||||
|
|
||||||
|
def _split_entries(self):
|
||||||
|
for chunk in self._align_chunk():
|
||||||
|
for entry in chunk.split(self.records_separator):
|
||||||
|
try:
|
||||||
|
yield entry.decode(
|
||||||
|
self.site_encoding or self.default_site_encoding)
|
||||||
|
except UnicodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_filter(string, filter_patterns):
|
||||||
|
if filter_patterns and string:
|
||||||
|
for pattern in filter_patterns:
|
||||||
|
if pattern and pattern.search(string):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_subnet(self, ip_addr):
|
||||||
|
regexp_obj = self.ip_pattern.fullmatch(ip_addr)
|
||||||
|
return regexp_obj.group(1) if regexp_obj else None
|
||||||
|
|
||||||
|
def ip_field_processing(self, string):
|
||||||
|
for i in string.split(self.ips_separator):
|
||||||
|
if self.IP_FILTER and self._check_filter(i, self.IP_FILTER_PATTERNS):
|
||||||
|
continue
|
||||||
|
if self.ip_pattern.fullmatch(i) and i not in self.ip_set:
|
||||||
|
subnet = self._get_subnet(i)
|
||||||
|
if subnet in self.OPT_EXCLUDE_NETS or (
|
||||||
|
not self.IP_LIMIT or (
|
||||||
|
subnet not in self.ip_subnet_dict or self.ip_subnet_dict[subnet] < self.IP_LIMIT
|
||||||
|
)
|
||||||
|
):
|
||||||
|
self.ip_set[i] = subnet
|
||||||
|
self.ip_subnet_dict[subnet] = (self.ip_subnet_dict.get(subnet) or 0) + 1
|
||||||
|
elif self.cidr_pattern.fullmatch(i) and i not in self.cidr_set:
|
||||||
|
self.cidr_set.add(i)
|
||||||
|
|
||||||
|
def _convert_to_punycode(self, string):
|
||||||
|
if self.cyr_pattern.search(string):
|
||||||
|
if self.USE_IDN:
|
||||||
|
try:
|
||||||
|
string = string.encode("idna").decode(
|
||||||
|
self.site_encoding or self.default_site_encoding)
|
||||||
|
except UnicodeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise FieldValueError()
|
||||||
|
return string
|
||||||
|
|
||||||
|
def _get_sld(self, fqdn):
|
||||||
|
regexp_obj = self.fqdn_pattern.fullmatch(fqdn)
|
||||||
|
return regexp_obj.group(2) if regexp_obj else None
|
||||||
|
|
||||||
|
def fqdn_field_processing(self, string):
|
||||||
|
if self.ip_pattern.fullmatch(string):
|
||||||
|
raise FieldValueError()
|
||||||
|
string = string.strip("*.").lower()
|
||||||
|
if self.STRIP_WWW:
|
||||||
|
string = self.www_pattern.sub("", string)
|
||||||
|
if not self.FQDN_FILTER or (
|
||||||
|
self.FQDN_FILTER and not self._check_filter(string, self.FQDN_FILTER_PATTERNS)
|
||||||
|
):
|
||||||
|
if self.fqdn_pattern.fullmatch(string):
|
||||||
|
string = self._convert_to_punycode(string)
|
||||||
|
sld = self._get_sld(string)
|
||||||
|
if sld in self.OPT_EXCLUDE_SLD or (
|
||||||
|
not self.SD_LIMIT or (
|
||||||
|
sld not in self.sld_dict or self.sld_dict[sld] < self.SD_LIMIT
|
||||||
|
)
|
||||||
|
):
|
||||||
|
self.sld_dict[sld] = (self.sld_dict.get(sld) or 0) + 1
|
||||||
|
self.fqdn_set[string] = sld
|
||||||
|
else:
|
||||||
|
raise FieldValueError()
|
||||||
|
|
||||||
|
def parser_func(self):
|
||||||
|
"""
|
||||||
|
Must be reload in the subclass
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def _check_sld_masks(self, sld):
|
||||||
|
if self.OPT_EXCLUDE_MASKS:
|
||||||
|
for pattern in self.OPT_EXCLUDE_MASKS:
|
||||||
|
if re.fullmatch(pattern, sld):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _optimize_fqdn_set(self):
|
||||||
|
optimized_set = set()
|
||||||
|
for fqdn, sld in self.fqdn_set.items():
|
||||||
|
if sld and (fqdn == sld or sld not in self.fqdn_set) and self.sld_dict.get(sld):
|
||||||
|
if (not self._check_sld_masks(sld) and (
|
||||||
|
self.SD_LIMIT and sld not in self.OPT_EXCLUDE_SLD
|
||||||
|
)) and (self.sld_dict[sld] >= self.SD_LIMIT):
|
||||||
|
record_value = sld
|
||||||
|
del(self.sld_dict[sld])
|
||||||
|
else:
|
||||||
|
record_value = fqdn
|
||||||
|
optimized_set.add(record_value)
|
||||||
|
self.output_fqdn_count += 1
|
||||||
|
self.fqdn_set = optimized_set
|
||||||
|
|
||||||
|
def _optimize_ip_set(self):
|
||||||
|
optimized_set = set()
|
||||||
|
for ip_addr, subnet in self.ip_set.items():
|
||||||
|
if subnet in self.ip_subnet_dict:
|
||||||
|
if subnet not in self.OPT_EXCLUDE_NETS and (
|
||||||
|
self.IP_LIMIT and self.ip_subnet_dict[subnet] >= self.IP_LIMIT
|
||||||
|
):
|
||||||
|
self.cidr_set.add(f"{subnet}0/24")
|
||||||
|
del(self.ip_subnet_dict[subnet])
|
||||||
|
else:
|
||||||
|
optimized_set.add(ip_addr)
|
||||||
|
self.ip_count += 1
|
||||||
|
self.ip_set = optimized_set
|
||||||
|
|
||||||
|
def _group_ip_ranges(self):
|
||||||
|
if self.SUMMARIZE_IP:
|
||||||
|
for i in summarize_ip_ranges(self.ip_set, True):
|
||||||
|
self.cidr_set.add(i.with_prefixlen)
|
||||||
|
self.ip_count = len(self.ip_set)
|
||||||
|
|
||||||
|
def _group_cidr_ranges(self):
|
||||||
|
if self.SUMMARIZE_CIDR:
|
||||||
|
for i in summarize_nets(self.cidr_set):
|
||||||
|
self.cidr_set.add(i.with_prefixlen)
|
||||||
|
self.cidr_count = len(self.cidr_set)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
ret_value = 1
|
||||||
|
self.FQDN_FILTER_PATTERNS = self._compile_filter_patterns(self.FQDN_FILTER_PATTERNS)
|
||||||
|
self.IP_FILTER_PATTERNS = self._compile_filter_patterns(self.IP_FILTER_PATTERNS)
|
||||||
|
self.records_separator = bytes(self.records_separator, "utf-8")
|
||||||
|
self.parser_func()
|
||||||
|
if (len(self.ip_set) + len(self.cidr_set) + len(self.fqdn_set)) >= self.BLLIST_MIN_ENTRS:
|
||||||
|
self._optimize_fqdn_set()
|
||||||
|
self._optimize_ip_set()
|
||||||
|
self._group_ip_ranges()
|
||||||
|
self._group_cidr_ranges()
|
||||||
|
ret_value = 0
|
||||||
|
else:
|
||||||
|
ret_value = 2
|
||||||
|
return ret_value
|
||||||
|
|
||||||
|
|
||||||
|
class RblFQDN(BlackListParser):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.url = self.RBL_ALL_URL
|
||||||
|
self.fields_separator = "],"
|
||||||
|
self.ips_separator = ","
|
||||||
|
|
||||||
|
def parser_func(self):
|
||||||
|
for entry in self._split_entries():
|
||||||
|
entry_list = entry.partition(self.fields_separator)
|
||||||
|
ip_string = re.sub(r"[' \]\[]", "", entry_list[0])
|
||||||
|
fqdn_string = re.sub(",.*$", "", entry_list[2])
|
||||||
|
if fqdn_string:
|
||||||
|
try:
|
||||||
|
self.fqdn_field_processing(fqdn_string)
|
||||||
|
except FieldValueError:
|
||||||
|
self.ip_field_processing(ip_string)
|
||||||
|
else:
|
||||||
|
self.ip_field_processing(ip_string)
|
||||||
|
|
||||||
|
|
||||||
|
class RblIp(BlackListParser):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.url = self.RBL_IP_URL
|
||||||
|
|
||||||
|
def parser_func(self):
|
||||||
|
for entry in self._split_entries():
|
||||||
|
self.ip_field_processing(entry.rstrip(","))
|
||||||
|
|
||||||
|
|
||||||
|
class ZiFQDN(BlackListParser):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.url = self.ZI_ALL_URL
|
||||||
|
self.site_encoding = self.ZI_ENCODING
|
||||||
|
|
||||||
|
def parser_func(self):
|
||||||
|
for entry in self._split_entries():
|
||||||
|
entry_list = entry.split(self.fields_separator)
|
||||||
|
try:
|
||||||
|
if entry_list[1]:
|
||||||
|
try:
|
||||||
|
self.fqdn_field_processing(entry_list[1])
|
||||||
|
except FieldValueError:
|
||||||
|
self.ip_field_processing(entry_list[0])
|
||||||
|
else:
|
||||||
|
self.ip_field_processing(entry_list[0])
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ZiIp(ZiFQDN):
|
||||||
|
def parser_func(self):
|
||||||
|
for entry in self._split_entries():
|
||||||
|
entry_list = entry.split(self.fields_separator)
|
||||||
|
self.ip_field_processing(entry_list[0])
|
||||||
|
|
||||||
|
|
||||||
|
class AfFQDN(BlackListParser):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.url = self.AF_FQDN_URL
|
||||||
|
|
||||||
|
def parser_func(self):
|
||||||
|
for entry in self._split_entries():
|
||||||
|
try:
|
||||||
|
self.fqdn_field_processing(entry)
|
||||||
|
except FieldValueError:
|
||||||
|
self.ip_field_processing(entry)
|
||||||
|
|
||||||
|
|
||||||
|
class AfIp(BlackListParser):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.url = self.AF_IP_URL
|
||||||
|
self.BLLIST_MIN_ENTRS = 100
|
||||||
|
|
||||||
|
def parser_func(self):
|
||||||
|
for entry in self._split_entries():
|
||||||
|
self.ip_field_processing(entry)
|
||||||
|
|
||||||
|
|
||||||
|
class WriteConfigFiles(Config):
|
||||||
|
def __init__(self):
|
||||||
|
self.write_buffer = -1
|
||||||
|
|
||||||
|
def write_ipset_config(self, ip_set, cidr_set):
|
||||||
|
with open(self.IP_DATA_FILE, "wt", buffering=self.write_buffer) as file_handler:
|
||||||
|
for i in ip_set:
|
||||||
|
file_handler.write(f"add {self.IPSET_IP_TMP} {i}\n")
|
||||||
|
for i in cidr_set:
|
||||||
|
file_handler.write(f"add {self.IPSET_CIDR_TMP} {i}\n")
|
||||||
|
|
||||||
|
def write_dnsmasq_config(self, fqdn_set):
|
||||||
|
with open(self.DNSMASQ_DATA_FILE, "wt", buffering=self.write_buffer) as file_handler:
|
||||||
|
for fqdn in fqdn_set:
|
||||||
|
file_handler.write(
|
||||||
|
f"server=/{fqdn}/{self.ALT_DNS_ADDR}\nipset=/{fqdn}/{self.IPSET_DNSMASQ}\n"
|
||||||
|
if self.ALT_NSLOOKUP else
|
||||||
|
f"ipset=/{fqdn}/{self.IPSET_DNSMASQ}\n")
|
||||||
|
|
||||||
|
def write_update_status_file(self, ip_count, cidr_count, output_fqdn_count):
|
||||||
|
with open(self.UPDATE_STATUS_FILE, "wt") as file_handler:
|
||||||
|
file_handler.write(
|
||||||
|
f"{ip_count} {cidr_count} {output_fqdn_count}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
Config.load_environ_config()
|
||||||
|
Config.load_fqdn_filter()
|
||||||
|
Config.load_ip_filter()
|
||||||
|
ctx_dict = {
|
||||||
|
"ip": {"rublacklist": RblIp, "zapret-info": ZiIp, "antifilter": AfIp},
|
||||||
|
"fqdn": {"rublacklist": RblFQDN, "zapret-info": ZiFQDN, "antifilter": AfFQDN},
|
||||||
|
}
|
||||||
|
write_cfg_obj = WriteConfigFiles()
|
||||||
|
try:
|
||||||
|
ctx = ctx_dict[Config.BLLIST_MODE][Config.BLLIST_SOURCE]()
|
||||||
|
except KeyError:
|
||||||
|
print("Wrong configuration! (Config.BLLIST_MODE or Config.BLLIST_SOURCE)",
|
||||||
|
file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
ret_code = ctx.run()
|
||||||
|
if ret_code == 0:
|
||||||
|
write_cfg_obj.write_dnsmasq_config(ctx.fqdn_set)
|
||||||
|
write_cfg_obj.write_ipset_config(ctx.ip_set, ctx.cidr_set)
|
||||||
|
write_cfg_obj.write_update_status_file(ctx.ip_count, ctx.cidr_count, ctx.output_fqdn_count)
|
||||||
|
sys.exit(ret_code)
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# (с) 2020 gSpot (https://github.com/gSpotx2f/ruantiblock_openwrt)
|
||||||
|
|
||||||
|
from ipaddress import IPv4Address, IPv4Network, summarize_address_range
|
||||||
|
|
||||||
|
HOSTS_LIMIT = 0
|
||||||
|
NETS_LIMIT = 0
|
||||||
|
|
||||||
|
|
||||||
|
def _sort_ip_func(e: str) -> IPv4Address:
|
||||||
|
return IPv4Address(e)
|
||||||
|
|
||||||
|
|
||||||
|
def _group_ip_ranges(ip_list: list, raw_list=None) -> tuple:
|
||||||
|
def remove_items(start, end):
|
||||||
|
for ip in range(int(start), int(end) + 1):
|
||||||
|
raw_list.remove(str(IPv4Address(ip)))
|
||||||
|
|
||||||
|
start = end = None
|
||||||
|
hosts = 1
|
||||||
|
for ip in ip_list:
|
||||||
|
ip_obj = IPv4Address(ip)
|
||||||
|
if end and (end + 1) == ip_obj:
|
||||||
|
hosts += 1
|
||||||
|
else:
|
||||||
|
if hosts > 1 and hosts >= HOSTS_LIMIT:
|
||||||
|
if raw_list:
|
||||||
|
remove_items(start, end)
|
||||||
|
yield start, end
|
||||||
|
start = ip_obj
|
||||||
|
hosts = 1
|
||||||
|
end = ip_obj
|
||||||
|
else:
|
||||||
|
if hosts > 1 and hosts >= HOSTS_LIMIT:
|
||||||
|
if raw_list:
|
||||||
|
remove_items(start, end)
|
||||||
|
yield start, end
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_ip_ranges(ip_list: (list, set), modify_raw_list=False) -> IPv4Network:
|
||||||
|
for s, e in _group_ip_ranges(sorted(ip_list, key=_sort_ip_func),
|
||||||
|
modify_raw_list and ip_list):
|
||||||
|
for i in summarize_address_range(s, e):
|
||||||
|
if i.prefixlen == 32:
|
||||||
|
if modify_raw_list:
|
||||||
|
if type(ip_list) == set:
|
||||||
|
ip_list.add(i.network_address)
|
||||||
|
else:
|
||||||
|
ip_list.append(i.network_address)
|
||||||
|
else:
|
||||||
|
yield i
|
||||||
|
|
||||||
|
|
||||||
|
def _sort_net_func(e: str) -> IPv4Network:
|
||||||
|
return IPv4Network(e)
|
||||||
|
|
||||||
|
|
||||||
|
def _group_nets(cidr_list: list, raw_list=None) -> IPv4Network:
|
||||||
|
|
||||||
|
def remove_items(start, end):
|
||||||
|
for ip in range(int(start), int(end) + 1, 256):
|
||||||
|
raw_list.remove(str(IPv4Address(ip)) + "/24")
|
||||||
|
|
||||||
|
start = end = curr_super_net = None
|
||||||
|
nets = 1
|
||||||
|
for net in cidr_list:
|
||||||
|
net_obj = IPv4Network(net)
|
||||||
|
prefix_len = net_obj.prefixlen
|
||||||
|
if prefix_len == 24:
|
||||||
|
address = net_obj.network_address
|
||||||
|
super_net = net_obj.supernet(new_prefix=16)
|
||||||
|
if end and super_net == curr_super_net and (end + 256) == address:
|
||||||
|
nets += 1
|
||||||
|
else:
|
||||||
|
if nets > 1 and nets >= NETS_LIMIT:
|
||||||
|
if raw_list:
|
||||||
|
remove_items(start, end)
|
||||||
|
yield summarize_address_range(IPv4Address(start), IPv4Address(end + 255))
|
||||||
|
start = address
|
||||||
|
curr_super_net = super_net
|
||||||
|
nets = 1
|
||||||
|
end = address
|
||||||
|
else:
|
||||||
|
if nets > 1 and nets >= NETS_LIMIT:
|
||||||
|
if raw_list:
|
||||||
|
remove_items(start, end)
|
||||||
|
yield summarize_address_range(IPv4Address(start), IPv4Address(end + 255))
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_nets(cidr_list: (list, set)) -> IPv4Network:
|
||||||
|
for i in _group_nets(sorted(cidr_list, key=_sort_net_func), cidr_list):
|
||||||
|
for j in i:
|
||||||
|
yield j
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
config main 'config'
|
||||||
|
option proxy_mode '1'
|
||||||
|
option proxy_local_clients '1'
|
||||||
|
option ipset_clear_sets '0'
|
||||||
|
option if_lan 'eth0'
|
||||||
|
option if_vpn 'tun0'
|
||||||
|
option tor_trans_port '9040'
|
||||||
|
option onion_dns_addr '127.0.0.1#9053'
|
||||||
|
option add_user_entries '0'
|
||||||
|
option use_logger '1'
|
||||||
|
option def_total_proxy '0'
|
||||||
|
option bllist_source 'rublacklist'
|
||||||
|
option bllist_mode 'ip'
|
||||||
|
option ip_limit '0'
|
||||||
|
option summarize_ip '0'
|
||||||
|
option summarize_cidr '0'
|
||||||
|
option ip_filter '0'
|
||||||
|
option sd_limit '16'
|
||||||
|
list opt_exclude_sld 'livejournal.com'
|
||||||
|
list opt_exclude_sld 'facebook.com'
|
||||||
|
list opt_exclude_sld 'vk.com'
|
||||||
|
list opt_exclude_sld 'blog.jp'
|
||||||
|
list opt_exclude_sld 'msk.ru'
|
||||||
|
list opt_exclude_sld 'net.ru'
|
||||||
|
list opt_exclude_sld 'org.ru'
|
||||||
|
list opt_exclude_sld 'net.ua'
|
||||||
|
list opt_exclude_sld 'com.ua'
|
||||||
|
list opt_exclude_sld 'org.ua'
|
||||||
|
list opt_exclude_sld 'co.uk'
|
||||||
|
list opt_exclude_sld 'amazonaws.com'
|
||||||
|
option fqdn_filter '0'
|
||||||
|
option use_idn '0'
|
||||||
|
option alt_nslookup '0'
|
||||||
|
option alt_dns_addr '8.8.8.8'
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
IF_VPN=`uci get ruantiblock.config.if_vpn`
|
||||||
|
PROXY_MODE=`uci get ruantiblock.config.proxy_mode`
|
||||||
|
|
||||||
|
if [ "$ACTION" = "ifup" ] && [ "$PROXY_MODE" = "2" ] && [ "$DEVICE" = "$IF_VPN" ]; then
|
||||||
|
[ `/usr/bin/ruantiblock raw-status` -ne 2 ] && /usr/bin/ruantiblock reload
|
||||||
|
fi
|
||||||
Executable
+22
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
|
||||||
|
START=92
|
||||||
|
STOP=01
|
||||||
|
|
||||||
|
RUAB="/usr/bin/ruantiblock"
|
||||||
|
|
||||||
|
start() {
|
||||||
|
$RUAB start
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
$RUAB stop
|
||||||
|
}
|
||||||
|
|
||||||
|
restart() {
|
||||||
|
$RUAB restart
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() {
|
||||||
|
$RUAB reload
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
^youtube[.]com
|
||||||
|
parimatch
|
||||||
|
paripartners
|
||||||
|
marathon
|
||||||
|
pm[-][0-9]
|
||||||
|
fbmetrix
|
||||||
|
[ck]?a[sz]ino?
|
||||||
|
[vw]ulkan
|
||||||
|
slots?
|
||||||
|
nar[ck]
|
||||||
|
st[au]f
|
||||||
|
al[ck]o
|
||||||
|
[cs]pravk
|
||||||
|
bets?
|
||||||
|
igr[ova]+
|
||||||
|
olimp
|
||||||
|
poker
|
||||||
|
leon
|
||||||
|
jackpot
|
||||||
|
spin
|
||||||
|
loto
|
||||||
|
bcity
|
||||||
|
stavka
|
||||||
|
lotery
|
||||||
|
fortuna
|
||||||
|
blackja
|
||||||
|
azart
|
||||||
|
eldorado
|
||||||
|
play
|
||||||
|
game
|
||||||
|
777
|
||||||
|
bonus
|
||||||
|
admiral
|
||||||
|
winner
|
||||||
|
cash
|
||||||
|
market
|
||||||
|
kupit
|
||||||
|
space
|
||||||
|
drug
|
||||||
|
farma
|
||||||
|
apteka
|
||||||
|
drop
|
||||||
|
rasta
|
||||||
|
smok
|
||||||
|
semen
|
||||||
|
seed
|
||||||
|
steroid
|
||||||
|
diplom
|
||||||
|
medic
|
||||||
|
prostitutk
|
||||||
|
individualk
|
||||||
|
dosug
|
||||||
|
putan
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
|
||||||
|
### Настройки ruantiblock ###
|
||||||
|
|
||||||
|
### Директория данных (генерируемые конфиги dnsmasq, ipset и пр.)
|
||||||
|
DATA_DIR="/etc/ruantiblock/var"
|
||||||
|
### Директория исполняемых скриптов
|
||||||
|
EXEC_DIR="/usr/bin"
|
||||||
|
### Команда для перезапуска dnsmasq
|
||||||
|
DNSMASQ_RESTART_CMD="/etc/init.d/dnsmasq restart"
|
||||||
|
### Директория для html-страницы статуса (не используется в OpenWrt)
|
||||||
|
HTML_DIR="/www"
|
||||||
|
|
||||||
|
### Режим обработки пакетов в правилах iptables (1 - Tor, 2 - VPN)
|
||||||
|
PROXY_MODE=1
|
||||||
|
### Применять правила проксификации для трафика локальных сервисов роутера (0 - off, 1 - on)
|
||||||
|
PROXY_LOCAL_CLIENTS=1
|
||||||
|
### Удаление записей из основных сетов перед началом заполнения временных сетов при обновлении (для освобождения оперативной памяти перед заполнением сетов) (0 - off, 1 - on)
|
||||||
|
IPSET_CLEAR_SETS=0
|
||||||
|
### Входящий сетевой интерфейс для правил iptables
|
||||||
|
IF_LAN="eth0"
|
||||||
|
### VPN интерфейс для правил маршрутизации
|
||||||
|
IF_VPN="tun0"
|
||||||
|
### Порт транспарентного proxy Tor (параметр TransPort в torrc)
|
||||||
|
TOR_TRANS_PORT=9040
|
||||||
|
### DNS-сервер для резолвинга в домене .onion (Tor)
|
||||||
|
ONION_DNS_ADDR="127.0.0.1#9053"
|
||||||
|
### Добавление в список блокировок пользовательских записей из файла $USER_ENTRIES_FILE (0 - off, 1 - on)
|
||||||
|
### В $DATA_DIR можно создать текстовый файл user_entries с записями IP, CIDR или FQDN (одна на строку). Эти записи будут добавлены в список блокировок
|
||||||
|
### В записях FQDN можно задать DNS-сервер для разрешения данного домена, через пробел (прим.: domain.com 8.8.8.8)
|
||||||
|
### Можно комментировать строки (#)
|
||||||
|
ADD_USER_ENTRIES=0
|
||||||
|
### DNS-сервер для пользовательских записей (пустая строка - без DNS-сервера). Можно с портом: 8.8.8.8#53. Если в записи указан свой DNS-сервер - он имеет приоритет
|
||||||
|
USER_ENTRIES_DNS=""
|
||||||
|
### Файл пользовательских записей
|
||||||
|
USER_ENTRIES_FILE="/etc/ruantiblock/user_entries"
|
||||||
|
### Запись событий в syslog (0 - off, 1 - on)
|
||||||
|
USE_LOGGER=1
|
||||||
|
### Режим полного прокси при старте скрипта (0 - off, 1 - on). Если 1, то весь трафик всегда идёт через прокси. Все пакеты попадающие в цепочку $IPT_CHAIN попадают в tor или VPN, за исключением сетей из $TOTAL_PROXY_EXCLUDE_NETS. Списки блокировок не используются для фильтрации. Работает только при PROXY_LOCAL_CLIENTS=0
|
||||||
|
DEF_TOTAL_PROXY=0
|
||||||
|
### Html-страница с инфо о текущем статусе (0 - off, 1 - on) (не используется в OpenWrt)
|
||||||
|
HTML_INFO=0
|
||||||
|
### --set-mark для отбора пакетов в VPN туннель
|
||||||
|
VPN_PKTS_MARK=8
|
||||||
|
### Максимальное кол-во элементов списка ipset
|
||||||
|
IPSET_MAXELEM=2000000
|
||||||
|
### Таймаут для записей в сете $IPSET_DNSMASQ
|
||||||
|
IPSET_DNSMASQ_TIMEOUT=3600
|
||||||
|
### Кол-во попыток обновления блэклиста (в случае неудачи)
|
||||||
|
MODULE_RUN_ATTEMPTS=3
|
||||||
|
### Таймаут между попытками обновления
|
||||||
|
MODULE_RUN_TIMEOUT=60
|
||||||
|
### Модули для получения и обработки блэклиста
|
||||||
|
BLLIST_MODULE=""
|
||||||
|
#BLLIST_MODULE="/usr/bin/ruab_parser.lua"
|
||||||
|
#BLLIST_MODULE="/usr/bin/ruab_parser.py"
|
||||||
|
|
||||||
|
### Настройки модулей-парсеров ###
|
||||||
|
|
||||||
|
### Источник для обновления списка блокировок (rublacklist, zapret-info, antifilter)
|
||||||
|
BLLIST_SOURCE="rublacklist"
|
||||||
|
### Режим обхода блокировок: ip, fqdn
|
||||||
|
BLLIST_MODE="ip"
|
||||||
|
### В случае если из источника получено менее указанного кол-ва записей, то обновления списков не происходит
|
||||||
|
BLLIST_MIN_ENTRS=30000
|
||||||
|
|
||||||
|
### Лимит ip адресов. При достижении, в конфиг ipset будет добавлена вся подсеть /24 вместо множества ip-адресов пренадлежащих этой сети (0 - off)
|
||||||
|
IP_LIMIT=0
|
||||||
|
### Подсети класса C (/24). IP адреса из этих подсетей не группируются при оптимизации (записи д.б. в виде: 68.183.221. 149.154.162. и пр.). Прим.: OPT_EXCLUDE_NETS="68.183.221. 149.154.162."
|
||||||
|
OPT_EXCLUDE_NETS=""
|
||||||
|
### Группировать идущие подряд IP адреса в подсетях /24 в диапазоны CIDR
|
||||||
|
SUMMARIZE_IP=0
|
||||||
|
### Группировать идущие подряд подсети /24 в диапазоны CIDR
|
||||||
|
SUMMARIZE_CIDR=0
|
||||||
|
### Фильтрация записей блэклиста по шаблонам из файла IP_FILTER_FILE. Записи (IP, CIDR) попадающие под шаблоны исключаются из кофига ipset (0 - off, 1 - on)
|
||||||
|
IP_FILTER=0
|
||||||
|
### Файл с шаблонами ip для опции FQDN_FILTER (каждый шаблон в отдельной строке. # в первом символе строки - комментирует строку)
|
||||||
|
IP_FILTER_FILE="/etc/ruantiblock/ip_filter"
|
||||||
|
|
||||||
|
### Лимит для субдоменов. При достижении, в конфиг dnsmasq будет добавлен весь домен 2-го ур-ня вместо множества субдоменов (0 - off)
|
||||||
|
SD_LIMIT=16
|
||||||
|
### SLD не подлежащие оптимизации (через пробел)
|
||||||
|
OPT_EXCLUDE_SLD="livejournal.com facebook.com vk.com blog.jp msk.ru net.ru org.ru net.ua com.ua org.ua co.uk amazonaws.com"
|
||||||
|
### Не оптимизировать SLD попадающие под выражения (через пробел) ("[.][a-z]{2,3}[.][a-z]{2}$")
|
||||||
|
OPT_EXCLUDE_MASKS=""
|
||||||
|
### Фильтрация записей блэклиста по шаблонам из файла FQDN_FILTER_FILE. Записи (FQDN) попадающие под шаблоны исключаются из кофига dnsmasq (0 - off, 1 - on)
|
||||||
|
FQDN_FILTER=0
|
||||||
|
### Файл с шаблонами FQDN для опции FQDN_FILTER (каждый шаблон в отдельной строке. # в первом символе строки - комментирует строку)
|
||||||
|
FQDN_FILTER_FILE="/etc/ruantiblock/fqdn_filter"
|
||||||
|
### Обрезка www[0-9]. в FQDN (0 - off, 1 - on)
|
||||||
|
STRIP_WWW=1
|
||||||
|
### Преобразование кириллических доменов в punycode (0 - off, 1 - on)
|
||||||
|
USE_IDN=0
|
||||||
|
### Перенаправлять DNS-запросы на альтернативный DNS-сервер для заблокированных FQDN (0 - off, 1 - on)
|
||||||
|
ALT_NSLOOKUP=0
|
||||||
|
### Альтернативный DNS-сервер
|
||||||
|
ALT_DNS_ADDR="8.8.8.8"
|
||||||
|
|
||||||
|
### Источники блэклиста
|
||||||
|
RBL_ALL_URL="https://reestr.rublacklist.net/api/v2/current/csv"
|
||||||
|
#RBL_ALL_URL="https://api.reserve-rbl.ru/api/v2/current/csv"
|
||||||
|
RBL_IP_URL="https://reestr.rublacklist.net/api/v2/ips/csv"
|
||||||
|
#RBL_IP_URL="https://api.reserve-rbl.ru/api/v2/ips/csv"
|
||||||
|
ZI_ALL_URL="https://raw.githubusercontent.com/zapret-info/z-i/master/dump.csv"
|
||||||
|
#ZI_ALL_URL="https://sourceforge.net/p/z-i/code-0/HEAD/tree/dump.csv?format=raw"
|
||||||
|
AF_IP_URL="https://antifilter.download/list/allyouneed.lst"
|
||||||
|
AF_FQDN_URL="https://antifilter.download/list/domains.lst"
|
||||||
|
AZ_ENCODING=""
|
||||||
|
RBL_ENCODING=""
|
||||||
|
ZI_ENCODING="CP1251"
|
||||||
|
AF_ENCODING=""
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
AWK_CMD="awk"
|
||||||
|
UCI_SECTION="ruantiblock.config"
|
||||||
|
UCI_VARS="proxy_mode proxy_local_clients ipset_clear_sets if_lan if_vpn tor_trans_port onion_dns_addr add_user_entries user_entries_dns use_logger def_total_proxy bllist_module bllist_source bllist_mode ip_limit opt_exclude_nets summarize_ip summarize_cidr ip_filter sd_limit opt_exclude_sld fqdn_filter use_idn alt_nslookup alt_dns_addr"
|
||||||
|
|
||||||
|
eval `uci show "$UCI_SECTION" | $AWK_CMD -F "=" -v UCI_VARS="$UCI_VARS" '
|
||||||
|
BEGIN {
|
||||||
|
split(UCI_VARS, split_array, " ");
|
||||||
|
for(i in split_array)
|
||||||
|
vars_array[split_array[i]]="";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
sub(/^.*[.]/, "", $1);
|
||||||
|
gsub(/["\047]/, "", $2);
|
||||||
|
if($1 in vars_array) {
|
||||||
|
print toupper($1) "=\"" $2 "\"";
|
||||||
|
delete vars_array[$1];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if(length(vars_array) > 0) {
|
||||||
|
for(i in vars_array)
|
||||||
|
print toupper(i) "=\"""\"";
|
||||||
|
};
|
||||||
|
}'`
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
Info() {
|
||||||
|
local _set
|
||||||
|
if CheckStatus; then
|
||||||
|
printf "{\"status\":\"enabled\",\"last_blacklist_update\":{"
|
||||||
|
if [ -f "$UPDATE_STATUS_FILE" ]; then
|
||||||
|
$AWK_CMD '{
|
||||||
|
if(NF < 4)
|
||||||
|
print "\"status\":false";
|
||||||
|
else
|
||||||
|
print "\"status\":true,\"date\":\""$4"\",\"ip\":\""$1"\",\"cidr\":\""$2"\",\"fqdn\":\""$3"\"";
|
||||||
|
}' "$UPDATE_STATUS_FILE"
|
||||||
|
else
|
||||||
|
printf "\"status\":false"
|
||||||
|
fi
|
||||||
|
printf "},"
|
||||||
|
IptListChain | $AWK_CMD '
|
||||||
|
BEGIN {
|
||||||
|
printf "\"iptables\":{"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
if(NR > 2)
|
||||||
|
printf "\""(($10 == "!") ? $12 : $11)"\":\""$2"\",";
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
printf "\"_dummy\":false},";
|
||||||
|
}'
|
||||||
|
printf "\"ipset\":{";
|
||||||
|
for _set in "$IPSET_TOTAL_PROXY" "$IPSET_CIDR_TMP" "$IPSET_CIDR" "$IPSET_IP_TMP" "$IPSET_IP" "$IPSET_DNSMASQ" "$IPSET_ONION"
|
||||||
|
do
|
||||||
|
$IPSET_CMD list "$_set" -terse | $AWK_CMD -F ": " '
|
||||||
|
{
|
||||||
|
if($1 ~ /^Name/)
|
||||||
|
printf "\""$2"\":[";
|
||||||
|
else if($1 ~ /^Size in memory/)
|
||||||
|
printf "\""$2"\",";
|
||||||
|
else if($1 ~ /^Number of entries/)
|
||||||
|
printf "\""$2"\"],";
|
||||||
|
}'
|
||||||
|
done
|
||||||
|
printf "\"_dummy\":false}}"
|
||||||
|
else
|
||||||
|
printf "{\"status\": \"disabled\"}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
|
||||||
|
IP_CMD="ip"
|
||||||
|
IPT_CMD="iptables"
|
||||||
|
IPT_CHAIN="$NAME"
|
||||||
|
IPT_FIRST_CHAIN="PREROUTING"
|
||||||
|
VPN_ROUTE_TABLE=99
|
||||||
|
|
||||||
|
### Tor
|
||||||
|
IPT_TABLE="nat"
|
||||||
|
IPT_FIRST_CHAIN_RULE="-i ${IF_LAN} -j ${IPT_CHAIN}"
|
||||||
|
IPT_IPSET_TARGET="dst -p tcp -j REDIRECT --to-ports ${TOR_TRANS_PORT}"
|
||||||
|
IPT_IPSETS="${IPSET_ONION} ${IPSET_CIDR} ${IPSET_IP} ${IPSET_DNSMASQ}"
|
||||||
|
|
||||||
|
if [ "$PROXY_MODE" = "2" ]; then
|
||||||
|
### VPN
|
||||||
|
IPT_TABLE="mangle"
|
||||||
|
IPT_FIRST_CHAIN_RULE="-j ${IPT_CHAIN}"
|
||||||
|
IPT_IPSET_TARGET="dst,src -j MARK --set-mark ${VPN_PKTS_MARK}"
|
||||||
|
IPT_IPSETS="${IPSET_CIDR} ${IPSET_IP} ${IPSET_DNSMASQ}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
IptCmdWrapper() {
|
||||||
|
local _i=0 _attempts=10 _return_code=1
|
||||||
|
while [ $_i -lt $_attempts ]
|
||||||
|
do
|
||||||
|
if $*; then
|
||||||
|
_return_code=$?
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
_i=`expr $_i + 1`
|
||||||
|
done
|
||||||
|
return $_return_code
|
||||||
|
}
|
||||||
|
|
||||||
|
IptVpnRouteAdd() {
|
||||||
|
VPN_IP=`$IP_CMD addr list dev $IF_VPN 2> /dev/null | $AWK_CMD '/inet/{sub("/[0-9]{1,2}$", "", $2); print $2; exit}'`
|
||||||
|
if [ -n "$VPN_IP" ]; then
|
||||||
|
echo 0 > /proc/sys/net/ipv4/conf/$IF_VPN/rp_filter
|
||||||
|
IptVpnRouteDel 2> /dev/null
|
||||||
|
$IP_CMD rule add fwmark $VPN_PKTS_MARK table $VPN_ROUTE_TABLE priority 1000
|
||||||
|
$IP_CMD route add default via $VPN_IP table $VPN_ROUTE_TABLE
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
IptVpnRouteDel() {
|
||||||
|
$IP_CMD route flush table $VPN_ROUTE_TABLE
|
||||||
|
$IP_CMD rule del table $VPN_ROUTE_TABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
IptVpnRouteStatus() {
|
||||||
|
[ -n "`$IP_CMD route show table $VPN_ROUTE_TABLE 2> /dev/null`" ] && return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
IptMainAdd() {
|
||||||
|
local _set
|
||||||
|
$IPT_CMD -t "$IPT_TABLE" -N "$IPT_CHAIN"
|
||||||
|
$IPT_CMD -t "$IPT_TABLE" -I "$IPT_FIRST_CHAIN" 1 $IPT_FIRST_CHAIN_RULE
|
||||||
|
for _set in $IPT_IPSETS
|
||||||
|
do
|
||||||
|
IptCmdWrapper $IPT_CMD -t "$IPT_TABLE" -A "$IPT_CHAIN" -m set --match-set "$_set" $IPT_IPSET_TARGET
|
||||||
|
done
|
||||||
|
if [ "$PROXY_MODE" = "2" ]; then
|
||||||
|
IptVpnRouteAdd
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
IptMainDel() {
|
||||||
|
IptCmdWrapper $IPT_CMD -t "$IPT_TABLE" -D "$IPT_FIRST_CHAIN" $IPT_FIRST_CHAIN_RULE
|
||||||
|
$IPT_CMD -t "$IPT_TABLE" -F "$IPT_CHAIN"
|
||||||
|
$IPT_CMD -t "$IPT_TABLE" -X "$IPT_CHAIN"
|
||||||
|
if [ "$PROXY_MODE" = "2" ]; then
|
||||||
|
IptVpnRouteDel 2> /dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
IPT_TP_RULE="-m set ! --match-set ${IPSET_TOTAL_PROXY} ${IPT_IPSET_TARGET}"
|
||||||
|
|
||||||
|
IptTotalProxyDel() {
|
||||||
|
IptCmdWrapper $IPT_CMD -t "$IPT_TABLE" -D "$IPT_CHAIN" $IPT_TP_RULE
|
||||||
|
}
|
||||||
|
|
||||||
|
IptTotalProxyAdd() {
|
||||||
|
$IPT_CMD -t "$IPT_TABLE" -I "$IPT_CHAIN" 1 $IPT_TP_RULE
|
||||||
|
}
|
||||||
|
|
||||||
|
IptTotalProxyStatus() {
|
||||||
|
$IPT_CMD -t "$IPT_TABLE" -L "$IPT_CHAIN" 2> /dev/null | $AWK_CMD -v IPSET_TOTAL_PROXY="$IPSET_TOTAL_PROXY" -v RET_CODE=1 '$0 ~ IPSET_TOTAL_PROXY {RET_CODE=0} END {exit RET_CODE}'
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
IPT_OUTPUT_FIRST_RULE="-j ${IPT_CHAIN}"
|
||||||
|
|
||||||
|
IptLocalClientsAdd() {
|
||||||
|
$IPT_CMD -t "$IPT_TABLE" -I OUTPUT 1 $IPT_OUTPUT_FIRST_RULE
|
||||||
|
}
|
||||||
|
|
||||||
|
IptLocalClientsDel() {
|
||||||
|
IptCmdWrapper $IPT_CMD -t "$IPT_TABLE" -D OUTPUT $IPT_OUTPUT_FIRST_RULE
|
||||||
|
}
|
||||||
|
|
||||||
|
IptListChain() {
|
||||||
|
$IPT_CMD -t "$IPT_TABLE" -v -L "$IPT_CHAIN"
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
_link="/tmp/dnsmasq.d/ruantiblock.conf"
|
||||||
|
ln -fs $DNSMASQ_DATA_FILE $_link
|
||||||
|
eval `echo "$DNSMASQ_RESTART_CMD"`
|
||||||
Executable
+713
@@ -0,0 +1,713 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# Ruantiblock
|
||||||
|
# (с) 2020 gSpot (https://github.com/gSpotx2f/ruantiblock_openwrt)
|
||||||
|
#
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
export NAME="ruantiblock"
|
||||||
|
export LANG="en_US.UTF-8"
|
||||||
|
export LANGUAGE="en"
|
||||||
|
|
||||||
|
#################### Platform-specific settings ########################
|
||||||
|
|
||||||
|
CONFIG_DIR="/etc/${NAME}"
|
||||||
|
CONFIG_FILE="${CONFIG_DIR}/${NAME}.conf"
|
||||||
|
export DATA_DIR="${CONFIG_DIR}/var"
|
||||||
|
export EXEC_DIR="/usr/bin"
|
||||||
|
### Команда для перезапуска dnsmasq
|
||||||
|
export DNSMASQ_RESTART_CMD="/etc/init.d/dnsmasq restart"
|
||||||
|
### Директория для html-страницы статуса (не используется в OpenWrt)
|
||||||
|
export HTML_DIR="/www"
|
||||||
|
|
||||||
|
########################## Default Settings ############################
|
||||||
|
|
||||||
|
### Режим обработки пакетов в правилах iptables (1 - Tor, 2 - VPN)
|
||||||
|
export PROXY_MODE=1
|
||||||
|
### Применять правила проксификации для трафика локальных сервисов роутера (0 - off, 1 - on)
|
||||||
|
export PROXY_LOCAL_CLIENTS=1
|
||||||
|
### Удаление записей из основных сетов перед началом заполнения временных сетов при обновлении (для освобождения оперативной памяти перед заполнением сетов) (0 - off, 1 - on)
|
||||||
|
export IPSET_CLEAR_SETS=0
|
||||||
|
### Входящий сетевой интерфейс для правил iptables
|
||||||
|
export IF_LAN="eth0"
|
||||||
|
### VPN интерфейс для правил iptables
|
||||||
|
export IF_VPN="tun0"
|
||||||
|
### Порт транспарентного proxy Tor (параметр TransPort в torrc)
|
||||||
|
export TOR_TRANS_PORT=9040
|
||||||
|
### DNS-сервер для резолвинга в домене .onion (Tor)
|
||||||
|
export ONION_DNS_ADDR="127.0.0.1#9053"
|
||||||
|
### Добавление в список блокировок пользовательских записей из файла $USER_ENTRIES_FILE (0 - off, 1 - on)
|
||||||
|
### В $DATA_DIR можно создать текстовый файл user_entries с записями IP, CIDR или FQDN (одна на строку). Эти записи будут добавлены в список блокировок
|
||||||
|
### В записях FQDN можно задать DNS-сервер для разрешения данного домена, через пробел (прим.: domain.com 8.8.8.8)
|
||||||
|
### Можно комментировать строки (#)
|
||||||
|
export ADD_USER_ENTRIES=0
|
||||||
|
### DNS-сервер для пользовательских записей (пустая строка - без DNS-сервера). Можно с портом: 8.8.8.8#53. Если в записи указан свой DNS-сервер - он имеет приоритет
|
||||||
|
export USER_ENTRIES_DNS=""
|
||||||
|
### Файл пользовательских записей
|
||||||
|
export USER_ENTRIES_FILE="${CONFIG_DIR}/user_entries"
|
||||||
|
### Запись событий в syslog (0 - off, 1 - on)
|
||||||
|
export USE_LOGGER=1
|
||||||
|
### Режим полного прокси при старте скрипта (0 - off, 1 - on). Если 1, то весь трафик всегда идёт через прокси. Все пакеты попадающие в цепочку $IPT_CHAIN попадают в tor или VPN, за исключением сетей из $TOTAL_PROXY_EXCLUDE_NETS. Списки блокировок не используются для фильтрации. Работает только при PROXY_LOCAL_CLIENTS=0
|
||||||
|
export DEF_TOTAL_PROXY=0
|
||||||
|
### Трафик в заданные сети идет напрямую, не попадая в Tor или VPN, в режиме total-proxy
|
||||||
|
export TOTAL_PROXY_EXCLUDE_NETS="10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 100.64.0.0/10"
|
||||||
|
### Html-страница с инфо о текущем статусе (0 - off, 1 - on) (не используется в OpenWrt)
|
||||||
|
export HTML_INFO=0
|
||||||
|
### --set-mark для отбора пакетов в VPN туннель
|
||||||
|
export VPN_PKTS_MARK=8
|
||||||
|
### Максимальное кол-во элементов списка ipset
|
||||||
|
export IPSET_MAXELEM=2000000
|
||||||
|
### Таймаут для записей в сете $IPSET_DNSMASQ
|
||||||
|
export IPSET_DNSMASQ_TIMEOUT=3600
|
||||||
|
### Кол-во попыток обновления блэклиста (в случае неудачи)
|
||||||
|
export MODULE_RUN_ATTEMPTS=3
|
||||||
|
### Таймаут между попытками обновления
|
||||||
|
export MODULE_RUN_TIMEOUT=60
|
||||||
|
### Модули для получения и обработки блэклиста
|
||||||
|
export BLLIST_MODULE=""
|
||||||
|
#export BLLIST_MODULE="${EXEC_DIR}/ruab_parser.lua"
|
||||||
|
#export BLLIST_MODULE="${EXEC_DIR}/ruab_parser.py"
|
||||||
|
|
||||||
|
##############################
|
||||||
|
|
||||||
|
### Источник для обновления списка блокировок (rublacklist, zapret-info, antifilter)
|
||||||
|
export BLLIST_SOURCE="rublacklist"
|
||||||
|
### Режим обхода блокировок: ip, fqdn
|
||||||
|
export BLLIST_MODE="ip"
|
||||||
|
### В случае если из источника получено менее указанного кол-ва записей, то обновления списков не происходит
|
||||||
|
export BLLIST_MIN_ENTRS=30000
|
||||||
|
|
||||||
|
### Лимит IP адресов. При достижении, в конфиг ipset будет добавлена вся подсеть /24 вместо множества IP адресов пренадлежащих этой сети (0 - off)
|
||||||
|
export IP_LIMIT=0
|
||||||
|
### Подсети класса C (/24). IP адреса из этих подсетей не группируются при оптимизации (записи д.б. в виде: 68.183.221. 149.154.162. и пр.). Прим.: OPT_EXCLUDE_NETS="68.183.221. 149.154.162."
|
||||||
|
export OPT_EXCLUDE_NETS=""
|
||||||
|
### Группировать идущие подряд IP адреса в подсетях /24 в диапазоны CIDR
|
||||||
|
export SUMMARIZE_IP=0
|
||||||
|
### Группировать идущие подряд подсети /24 в диапазоны CIDR
|
||||||
|
export SUMMARIZE_CIDR=0
|
||||||
|
### Фильтрация записей блэклиста по шаблонам из файла IP_FILTER_FILE. Записи (IP, CIDR) попадающие под шаблоны исключаются из кофига ipset (0 - off, 1 - on)
|
||||||
|
export IP_FILTER=0
|
||||||
|
### Файл с шаблонами IP для опции FQDN_FILTER (каждый шаблон в отдельной строке. # в первом символе строки - комментирует строку)
|
||||||
|
export IP_FILTER_FILE="${CONFIG_DIR}/ip_filter"
|
||||||
|
|
||||||
|
### Лимит для субдоменов. При достижении, в конфиг dnsmasq будет добавлен весь домен 2-го ур-ня вместо множества субдоменов (0 - off)
|
||||||
|
export SD_LIMIT=16
|
||||||
|
### SLD не подлежащие оптимизации (через пробел)
|
||||||
|
export OPT_EXCLUDE_SLD="livejournal.com facebook.com vk.com blog.jp msk.ru net.ru org.ru net.ua com.ua org.ua co.uk amazonaws.com"
|
||||||
|
### Не оптимизировать SLD попадающие под выражения (через пробел) ("[.][a-z]{2,3}[.][a-z]{2}$")
|
||||||
|
export OPT_EXCLUDE_MASKS=""
|
||||||
|
### Фильтрация записей блэклиста по шаблонам из файла ENTRIES_FILTER_FILE. Записи (FQDN) попадающие под шаблоны исключаются из кофига dnsmasq (0 - off, 1 - on)
|
||||||
|
export FQDN_FILTER=0
|
||||||
|
### Файл с шаблонами FQDN для опции FQDN_FILTER (каждый шаблон в отдельной строке. # в первом символе строки - комментирует строку)
|
||||||
|
export FQDN_FILTER_FILE="${CONFIG_DIR}/fqdn_filter"
|
||||||
|
### Обрезка www[0-9]. в FQDN (0 - off, 1 - on)
|
||||||
|
export STRIP_WWW=1
|
||||||
|
### Преобразование кириллических доменов в punycode (0 - off, 1 - on)
|
||||||
|
export USE_IDN=0
|
||||||
|
### Перенаправлять DNS-запросы на альтернативный DNS-сервер для заблокированных FQDN (0 - off, 1 - on)
|
||||||
|
export ALT_NSLOOKUP=0
|
||||||
|
### Альтернативный DNS-сервер
|
||||||
|
export ALT_DNS_ADDR="8.8.8.8"
|
||||||
|
|
||||||
|
### Источники блэклиста
|
||||||
|
export RBL_ALL_URL="https://api.reserve-rbl.ru/api/v2/current/csv"
|
||||||
|
export RBL_IP_URL="https://api.reserve-rbl.ru/api/v2/ips/csv"
|
||||||
|
export ZI_ALL_URL="https://raw.githubusercontent.com/zapret-info/z-i/master/dump.csv"
|
||||||
|
export AF_IP_URL="https://antifilter.download/list/allyouneed.lst"
|
||||||
|
export AF_FQDN_URL="https://antifilter.download/list/domains.lst"
|
||||||
|
export RBL_ENCODING=""
|
||||||
|
export ZI_ENCODING="CP1251"
|
||||||
|
export AF_ENCODING=""
|
||||||
|
|
||||||
|
############################ Configuration #############################
|
||||||
|
|
||||||
|
[ -f "$CONFIG_FILE" ] && . "$CONFIG_FILE"
|
||||||
|
|
||||||
|
CONFIG_SCRIPT="${CONFIG_DIR}/scripts/config_script"
|
||||||
|
START_SCRIPT="${CONFIG_DIR}/scripts/start_script"
|
||||||
|
STOP_SCRIPT="${CONFIG_DIR}/scripts/stop_script"
|
||||||
|
|
||||||
|
[ -f "$CONFIG_SCRIPT" ] && . "$CONFIG_SCRIPT"
|
||||||
|
|
||||||
|
AWK_CMD="awk"
|
||||||
|
IPSET_CMD=`which ipset`
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo " Error! Ipset doesn't exists" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
LOGGER_CMD=`which logger`
|
||||||
|
if [ $USE_LOGGER = "1" -a $? -ne 0 ]; then
|
||||||
|
echo " Logger doesn't exists" >&2
|
||||||
|
USE_LOGGER=0
|
||||||
|
fi
|
||||||
|
LOGGER_PARAMS="-t `basename $0`[${$}] -p user.notice"
|
||||||
|
export DNSMASQ_DATA_FILE="${DATA_DIR}/${NAME}.dnsmasq"
|
||||||
|
export IP_DATA_FILE="${DATA_DIR}/${NAME}.ip"
|
||||||
|
export IPSET_IP="${NAME}-ip"
|
||||||
|
export IPSET_IP_TMP="${IPSET_IP}-tmp"
|
||||||
|
export IPSET_CIDR="${NAME}-cidr"
|
||||||
|
export IPSET_CIDR_TMP="${IPSET_CIDR}-tmp"
|
||||||
|
export IPSET_DNSMASQ="${NAME}-dnsmasq"
|
||||||
|
export IPSET_ONION="onion"
|
||||||
|
export IPSET_TOTAL_PROXY="total-proxy"
|
||||||
|
export UPDATE_STATUS_FILE="${DATA_DIR}/update_status"
|
||||||
|
UPDATE_PID_FILE="/var/run/${NAME}_update.pid"
|
||||||
|
START_PID_FILE="/var/run/${NAME}_start.pid"
|
||||||
|
TOKEN_FILE="/var/run/${NAME}.token"
|
||||||
|
export HTML_OUTPUT="${HTML_DIR}/${NAME}.html"
|
||||||
|
IPT_FUNCTIONS="${CONFIG_DIR}/scripts/ipt_functions"
|
||||||
|
INFO_OUTPUT_FUNCTION="${CONFIG_DIR}/scripts/info_output"
|
||||||
|
|
||||||
|
######################### External functions ###########################
|
||||||
|
|
||||||
|
. "$IPT_FUNCTIONS"
|
||||||
|
if [ -f "$INFO_OUTPUT_FUNCTION" ]; then
|
||||||
|
. "$INFO_OUTPUT_FUNCTION"
|
||||||
|
else
|
||||||
|
HTML_INFO=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
############################## Functions ###############################
|
||||||
|
|
||||||
|
Help() {
|
||||||
|
cat << EOF
|
||||||
|
Usage: `basename $0` start|force-start|stop|destroy|restart|update|force-update|data-files|total-proxy-on|total-proxy-off|renew-ipt|status|status-html|--help
|
||||||
|
start : Start
|
||||||
|
force-start :
|
||||||
|
stop : Stop
|
||||||
|
destroy : Stop + destroy ipsets and clear all data files
|
||||||
|
restart : Restart
|
||||||
|
reload : Renew iptables configuration
|
||||||
|
update : Update blacklist
|
||||||
|
force-update : Force update blacklist
|
||||||
|
data-files : Create ${IP_DATA_FILE} & ${DNSMASQ_DATA_FILE} (without network functions)
|
||||||
|
total-proxy-on : Turn on total-proxy mode
|
||||||
|
total-proxy-off : Turn off total-proxy mode
|
||||||
|
total-proxy-status : total-proxy status
|
||||||
|
status : Status & some info
|
||||||
|
raw-status : Return code: 0 - enabled, 1 - error, 2 - disabled, 3 - starting, 4 - updating
|
||||||
|
html-info : Return the html-info output
|
||||||
|
-h|--help : This message
|
||||||
|
Examples:
|
||||||
|
`basename $0` start
|
||||||
|
`basename $0` force-start
|
||||||
|
`basename $0` stop
|
||||||
|
`basename $0` destroy
|
||||||
|
`basename $0` restart
|
||||||
|
`basename $0` update
|
||||||
|
`basename $0` force-update
|
||||||
|
`basename $0` data-files
|
||||||
|
`basename $0` total-proxy-on
|
||||||
|
`basename $0` total-proxy-off
|
||||||
|
`basename $0` total-proxy-status
|
||||||
|
`basename $0` status
|
||||||
|
`basename $0` html-info
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeLogRecord() {
|
||||||
|
[ $USE_LOGGER = "1" ] && $LOGGER_CMD $LOGGER_PARAMS $1
|
||||||
|
}
|
||||||
|
|
||||||
|
DnsmasqRestart() {
|
||||||
|
eval `echo "$DNSMASQ_RESTART_CMD"`
|
||||||
|
}
|
||||||
|
|
||||||
|
IsIpsetExists() {
|
||||||
|
$IPSET_CMD list "$1" -terse &> /dev/null
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushIpSets() {
|
||||||
|
local _set
|
||||||
|
for _set in "$@"
|
||||||
|
do
|
||||||
|
IsIpsetExists "$_set" && $IPSET_CMD flush "$_set"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
DestroyIpsets() {
|
||||||
|
local _set
|
||||||
|
for _set in "$@"
|
||||||
|
do
|
||||||
|
IsIpsetExists "$_set" && $IPSET_CMD destroy "$_set"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
FillTotalProxySet() {
|
||||||
|
local _entry
|
||||||
|
for _entry in $TOTAL_PROXY_EXCLUDE_NETS
|
||||||
|
do
|
||||||
|
$IPSET_CMD add "$IPSET_TOTAL_PROXY" "$_entry"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
TotalProxyOn() {
|
||||||
|
if [ "$PROXY_LOCAL_CLIENTS" != "1" ]; then
|
||||||
|
IptTotalProxyDel &> /dev/null
|
||||||
|
IptTotalProxyAdd
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " ${IPSET_TOTAL_PROXY} enabled"
|
||||||
|
MakeLogRecord "${IPSET_TOTAL_PROXY} enabled"
|
||||||
|
fi
|
||||||
|
MakeToken
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
TotalProxyOff() {
|
||||||
|
if [ "$PROXY_LOCAL_CLIENTS" != "1" ]; then
|
||||||
|
IptTotalProxyDel &> /dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo " ${IPSET_TOTAL_PROXY} is already disabled" >&2
|
||||||
|
else
|
||||||
|
echo " ${IPSET_TOTAL_PROXY} disabled"
|
||||||
|
MakeLogRecord "${IPSET_TOTAL_PROXY} disabled"
|
||||||
|
fi
|
||||||
|
MakeToken
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
TotalProxyStatus() {
|
||||||
|
IptTotalProxyStatus
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
AddIptRules() {
|
||||||
|
IptMainAdd
|
||||||
|
if [ "$PROXY_LOCAL_CLIENTS" = "1" ]; then
|
||||||
|
IptLocalClientsAdd
|
||||||
|
fi
|
||||||
|
if [ "$DEF_TOTAL_PROXY" = "1" ]; then
|
||||||
|
TotalProxyOn
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
DelIptRules() {
|
||||||
|
IptLocalClientsDel
|
||||||
|
IptMainDel
|
||||||
|
}
|
||||||
|
|
||||||
|
SetNetConfig() {
|
||||||
|
local _set
|
||||||
|
for _set in "$IPSET_TOTAL_PROXY" "$IPSET_CIDR_TMP" "$IPSET_CIDR"
|
||||||
|
do
|
||||||
|
IsIpsetExists "$_set" || $IPSET_CMD create "$_set" hash:net maxelem $IPSET_MAXELEM
|
||||||
|
done
|
||||||
|
for _set in "$IPSET_IP_TMP" "$IPSET_IP" "$IPSET_ONION"
|
||||||
|
do
|
||||||
|
IsIpsetExists "$_set" || $IPSET_CMD create "$_set" hash:ip maxelem $IPSET_MAXELEM
|
||||||
|
done
|
||||||
|
IsIpsetExists "$IPSET_DNSMASQ" || $IPSET_CMD create "$IPSET_DNSMASQ" hash:ip maxelem $IPSET_MAXELEM timeout $IPSET_DNSMASQ_TIMEOUT
|
||||||
|
FillTotalProxySet
|
||||||
|
AddIptRules
|
||||||
|
}
|
||||||
|
|
||||||
|
DropNetConfig() {
|
||||||
|
DelIptRules
|
||||||
|
FlushIpSets "$IPSET_CIDR_TMP" "$IPSET_CIDR" "$IPSET_IP_TMP" "$IPSET_IP" "$IPSET_DNSMASQ" "$IPSET_ONION" "$IPSET_TOTAL_PROXY"
|
||||||
|
}
|
||||||
|
|
||||||
|
FillIpsets() {
|
||||||
|
local _set
|
||||||
|
if [ -f "$IP_DATA_FILE" ]; then
|
||||||
|
echo " Filling ipsets..."
|
||||||
|
FlushIpSets "$IPSET_IP_TMP" "$IPSET_CIDR_TMP"
|
||||||
|
IsIpsetExists "$IPSET_IP_TMP" && IsIpsetExists "$IPSET_CIDR_TMP" && IsIpsetExists "$IPSET_IP" && IsIpsetExists "$IPSET_CIDR" &&\
|
||||||
|
cat "$IP_DATA_FILE" | $IPSET_CMD restore && { $IPSET_CMD swap "$IPSET_IP_TMP" "$IPSET_IP"; $IPSET_CMD swap "$IPSET_CIDR_TMP" "$IPSET_CIDR"; }
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " Ok"
|
||||||
|
FlushIpSets "$IPSET_IP_TMP" "$IPSET_CIDR_TMP"
|
||||||
|
else
|
||||||
|
echo " Error! Ipset wasn't updated" >&2
|
||||||
|
MakeLogRecord "Error! Ipset wasn't updated"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ClearDataFiles() {
|
||||||
|
if [ -d "$DATA_DIR" ]; then
|
||||||
|
printf "" > "$DNSMASQ_DATA_FILE"
|
||||||
|
printf "" > "$IP_DATA_FILE"
|
||||||
|
printf "0 0 0" > "$UPDATE_STATUS_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckStatus() {
|
||||||
|
local _set _ipset_return=0 _return_code=1
|
||||||
|
if [ "$1" = "ipsets" ]; then
|
||||||
|
for _set in "$IPSET_TOTAL_PROXY" "$IPSET_CIDR_TMP" "$IPSET_CIDR" "$IPSET_IP_TMP" "$IPSET_IP" "$IPSET_DNSMASQ" "$IPSET_ONION"
|
||||||
|
do
|
||||||
|
IsIpsetExists "$_set"
|
||||||
|
_ipset_return=$?
|
||||||
|
[ $_ipset_return -ne 0 ] && break
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if IptListChain &> /dev/null && [ $_ipset_return -eq 0 ]; then
|
||||||
|
_return_code=0
|
||||||
|
fi
|
||||||
|
return $_return_code
|
||||||
|
}
|
||||||
|
|
||||||
|
PreStartCheck() {
|
||||||
|
[ -d "$DATA_DIR" ] || mkdir -p "$DATA_DIR"
|
||||||
|
[ "$HTML_INFO" = "1" -a ! -d "$HTML_DIR" ] && mkdir -p "$HTML_DIR"
|
||||||
|
[ -e "$DNSMASQ_DATA_FILE" ] || printf "\n" > "$DNSMASQ_DATA_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
AddUserEntries() {
|
||||||
|
if [ "$ADD_USER_ENTRIES" = "1" ]; then
|
||||||
|
if [ -f "$USER_ENTRIES_FILE" -a -s "$USER_ENTRIES_FILE" ]; then
|
||||||
|
$AWK_CMD 'BEGIN {
|
||||||
|
null="";
|
||||||
|
while((getline ip_string <ENVIRON["IP_DATA_FILE"]) > 0) {
|
||||||
|
split(ip_string, ip_string_arr, " ");
|
||||||
|
ip_data_array[ip_string_arr[3]]=null;
|
||||||
|
};
|
||||||
|
close(ENVIRON["IP_DATA_FILE"]);
|
||||||
|
while((getline fqdn_string <ENVIRON["DNSMASQ_DATA_FILE"]) > 0) {
|
||||||
|
split(fqdn_string, fqdn_string_arr, "/");
|
||||||
|
fqdn_data_array[fqdn_string_arr[2]]=null;
|
||||||
|
};
|
||||||
|
close(ENVIRON["DNSMASQ_DATA_FILE"]);
|
||||||
|
}
|
||||||
|
function writeIpsetEntries(val, set) {
|
||||||
|
printf "add %s %s\n", set, val >> ENVIRON["IP_DATA_FILE"];
|
||||||
|
};
|
||||||
|
function writeDNSData(val, dns) {
|
||||||
|
if(length(dns) == 0 && length(ENVIRON["USER_ENTRIES_DNS"]) > 0)
|
||||||
|
dns = ENVIRON["USER_ENTRIES_DNS"];
|
||||||
|
if(length(dns) > 0)
|
||||||
|
printf "server=/%s/%s\n", val, dns >> ENVIRON["DNSMASQ_DATA_FILE"];
|
||||||
|
printf "ipset=/%s/%s\n", val, ENVIRON["IPSET_DNSMASQ"] >> ENVIRON["DNSMASQ_DATA_FILE"];
|
||||||
|
};
|
||||||
|
($0 !~ /^([\040\011]*$|#)/) {
|
||||||
|
if($0 ~ /^[0-9]{1,3}([.][0-9]{1,3}){3}$/ && !($0 in ip_data_array))
|
||||||
|
writeIpsetEntries($0, ENVIRON["IPSET_IP_TMP"]);
|
||||||
|
else if($0 ~ /^[0-9]{1,3}([.][0-9]{1,3}){3}[\057][0-9]{1,2}$/ && !($0 in ip_data_array))
|
||||||
|
writeIpsetEntries($0, ENVIRON["IPSET_CIDR_TMP"]);
|
||||||
|
else if($0 ~ /^[a-z0-9.\052-]+[.]([a-z]{2,}|xn--[a-z0-9]+)([ ][0-9]{1,3}([.][0-9]{1,3}){3}([#][0-9]{2,5})?)?$/ && !($1 in fqdn_data_array))
|
||||||
|
writeDNSData($1, $2);
|
||||||
|
}' "$USER_ENTRIES_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
GetDataFiles() {
|
||||||
|
local _return_code=1 _attempt=1 _update_string
|
||||||
|
PreStartCheck
|
||||||
|
echo "$$" > "$UPDATE_PID_FILE"
|
||||||
|
if [ -n "$BLLIST_MODULE" ]; then
|
||||||
|
while :
|
||||||
|
do
|
||||||
|
$BLLIST_MODULE
|
||||||
|
_return_code=$?
|
||||||
|
[ $_return_code -eq 0 ] && break
|
||||||
|
echo " Module run attempt ${_attempt}: failed [${BLLIST_MODULE}]"
|
||||||
|
MakeLogRecord "Module run attempt ${_attempt}: failed [${BLLIST_MODULE}]"
|
||||||
|
_attempt=`expr $_attempt + 1`
|
||||||
|
[ $_attempt -gt $MODULE_RUN_ATTEMPTS ] && break
|
||||||
|
sleep $MODULE_RUN_TIMEOUT
|
||||||
|
done
|
||||||
|
AddUserEntries
|
||||||
|
if [ $_return_code -eq 0 ]; then
|
||||||
|
_update_string=`$AWK_CMD '{
|
||||||
|
printf "Received entries: %s\n", (NF < 3) ? "No data" : "IP: "$1", CIDR: "$2", FQDN: "$3;
|
||||||
|
exit;
|
||||||
|
}' "$UPDATE_STATUS_FILE"`
|
||||||
|
echo " ${_update_string}"
|
||||||
|
MakeLogRecord "${_update_string}"
|
||||||
|
printf " `date +%d.%m.%Y-%H:%M`\n" >> "$UPDATE_STATUS_FILE"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
ClearDataFiles
|
||||||
|
ADD_USER_ENTRIES=1
|
||||||
|
AddUserEntries
|
||||||
|
_return_code=0
|
||||||
|
fi
|
||||||
|
if [ "$PROXY_MODE" = "2" ]; then
|
||||||
|
printf "\n" >> "$DNSMASQ_DATA_FILE"
|
||||||
|
else
|
||||||
|
printf "server=/onion/%s\nipset=/onion/%s\n" "${ONION_DNS_ADDR}" "${IPSET_ONION}" >> "$DNSMASQ_DATA_FILE"
|
||||||
|
fi
|
||||||
|
rm -f "$UPDATE_PID_FILE"
|
||||||
|
return $_return_code
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeToken() {
|
||||||
|
date +%s > "$TOKEN_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
Update() {
|
||||||
|
local _return_code=0
|
||||||
|
if CheckStatus ipsets; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
echo " ${NAME} ${1} - Error! ${NAME} does not running or another error has occurred" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
MakeToken
|
||||||
|
if [ -e "$UPDATE_PID_FILE" ] && [ "$1" != "force-update" ]; then
|
||||||
|
echo " ${NAME} ${1} - Error! Another instance of update is already running" >&2
|
||||||
|
MakeLogRecord "${1} - Error! Another instance of update is already running"
|
||||||
|
_return_code=2
|
||||||
|
else
|
||||||
|
echo " ${NAME} ${1}..."
|
||||||
|
MakeLogRecord "${1}..."
|
||||||
|
if [ "$IPSET_CLEAR_SETS" = "1" ]; then
|
||||||
|
FlushIpSets "$IPSET_IP" "$IPSET_CIDR" "$IPSET_DNSMASQ"
|
||||||
|
fi
|
||||||
|
GetDataFiles
|
||||||
|
case $? in
|
||||||
|
0)
|
||||||
|
echo " Blacklist updated"
|
||||||
|
MakeLogRecord "Blacklist updated"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
echo " Error! Blacklist update error" >&2
|
||||||
|
MakeLogRecord "Error! Blacklist update error"
|
||||||
|
_return_code=1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo " Module error! [${BLLIST_MODULE}]" >&2
|
||||||
|
MakeLogRecord "Module error! [${BLLIST_MODULE}]"
|
||||||
|
_return_code=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
FlushIpSets "$IPSET_DNSMASQ"
|
||||||
|
FillIpsets
|
||||||
|
_return_code=$?
|
||||||
|
DnsmasqRestart
|
||||||
|
fi
|
||||||
|
MakeToken
|
||||||
|
return $_return_code
|
||||||
|
}
|
||||||
|
|
||||||
|
Start() {
|
||||||
|
local _return_code=1
|
||||||
|
if [ -e "$START_PID_FILE" ]; then
|
||||||
|
echo " ${NAME} is currently starting..." >&2
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
echo "$$" > "$START_PID_FILE"
|
||||||
|
fi
|
||||||
|
MakeToken
|
||||||
|
if CheckStatus; then
|
||||||
|
echo " ${NAME} is already running" >&2
|
||||||
|
_return_code=1
|
||||||
|
else
|
||||||
|
echo " ${NAME} ${1}..."
|
||||||
|
MakeLogRecord "${1}..."
|
||||||
|
DropNetConfig &> /dev/null
|
||||||
|
SetNetConfig
|
||||||
|
PreStartCheck
|
||||||
|
FillIpsets
|
||||||
|
_return_code=$?
|
||||||
|
### Start script
|
||||||
|
[ -x "$START_SCRIPT" ] && $START_SCRIPT > /dev/null 2>&1 &
|
||||||
|
fi
|
||||||
|
rm -f "$START_PID_FILE"
|
||||||
|
MakeToken
|
||||||
|
return $_return_code
|
||||||
|
}
|
||||||
|
|
||||||
|
Stop() {
|
||||||
|
local _return_code=1
|
||||||
|
if CheckStatus; then
|
||||||
|
MakeToken
|
||||||
|
echo " ${NAME} ${1}..."
|
||||||
|
MakeLogRecord "${1}..."
|
||||||
|
DropNetConfig &> /dev/null
|
||||||
|
_return_code=$?
|
||||||
|
### Stop script
|
||||||
|
[ -x "$STOP_SCRIPT" ] && $STOP_SCRIPT > /dev/null 2>&1 &
|
||||||
|
MakeToken
|
||||||
|
else
|
||||||
|
echo " ${NAME} does not running" >&2
|
||||||
|
fi
|
||||||
|
return $_return_code
|
||||||
|
}
|
||||||
|
|
||||||
|
Reload() {
|
||||||
|
local _i=0 _attempts=60
|
||||||
|
MakeToken
|
||||||
|
while [ -e "$START_PID_FILE" ]
|
||||||
|
do
|
||||||
|
if [ $_i -ge $_attempts ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_i=`expr $_i + 1`
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo " ${NAME} reload..."
|
||||||
|
DelIptRules &> /dev/null
|
||||||
|
AddIptRules &> /dev/null
|
||||||
|
MakeToken
|
||||||
|
}
|
||||||
|
|
||||||
|
Status() {
|
||||||
|
local _set
|
||||||
|
if CheckStatus; then
|
||||||
|
printf "\n \033[1m${NAME} status\033[m: \033[1;32mEnabled\033[m\n\n PROXY_MODE: ${PROXY_MODE}\n DEF_TOTAL_PROXY: ${DEF_TOTAL_PROXY}\n PROXY_LOCAL_CLIENTS: ${PROXY_LOCAL_CLIENTS}\n BLLIST_MODULE: ${BLLIST_MODULE}\n"
|
||||||
|
if [ -f "$UPDATE_STATUS_FILE" ]; then
|
||||||
|
$AWK_CMD '{
|
||||||
|
update_string=(NF < 4) ? "No data" : $4" (IP: "$1" | CIDR: "$2" | FQDN: "$3")";
|
||||||
|
printf "\n Last blacklist update: %s\n", update_string;
|
||||||
|
}' "$UPDATE_STATUS_FILE"
|
||||||
|
else
|
||||||
|
printf "\n Last blacklist update: No data\n"
|
||||||
|
fi
|
||||||
|
if [ "$PROXY_MODE" = "2" ] && ! IptVpnRouteStatus; then
|
||||||
|
printf "\n \033[1;31mVPN ROUTING ERROR! (NEED THE RESTART)\033[m\n"
|
||||||
|
fi
|
||||||
|
printf "\n \033[4mIptables rules\033[m:\n\n"
|
||||||
|
IptListChain | $AWK_CMD '
|
||||||
|
{
|
||||||
|
if(NR > 2) {
|
||||||
|
match_set=(NR == 3 && $0 ~ ENVIRON["IPSET_TOTAL_PROXY"]) ? "\033[1;33m"ENVIRON["IPSET_TOTAL_PROXY"]" (Enabled!)\033[m" : $11;
|
||||||
|
printf " Match-set: %s\n Bytes: %s\n\n", match_set, $2;
|
||||||
|
};
|
||||||
|
}'
|
||||||
|
printf " \033[4mIp sets\033[m:\n\n"
|
||||||
|
for _set in "$IPSET_TOTAL_PROXY" "$IPSET_CIDR_TMP" "$IPSET_CIDR" "$IPSET_IP_TMP" "$IPSET_IP" "$IPSET_DNSMASQ" "$IPSET_ONION"
|
||||||
|
do
|
||||||
|
$IPSET_CMD list "$_set" -terse | $AWK_CMD -F ":" '
|
||||||
|
{
|
||||||
|
if($1 ~ /^(Name|Size in memory|Number of entries)/) {
|
||||||
|
printf " %s: %s\n", $1, $2;
|
||||||
|
if($1 ~ /^Number of entries/) printf "\n";
|
||||||
|
};
|
||||||
|
}'
|
||||||
|
done
|
||||||
|
else
|
||||||
|
printf "\n \033[1m${NAME} status\033[m: \033[1mDisabled\033[m\n\n"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusOutput() {
|
||||||
|
if [ "$HTML_INFO" = "1" -a -d "$HTML_DIR" ]; then
|
||||||
|
Info
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
############################ Main section ##############################
|
||||||
|
|
||||||
|
return_code=1
|
||||||
|
case "$1" in
|
||||||
|
start|force-start)
|
||||||
|
[ "$1" == "force-start" ] && rm -f "$START_PID_FILE"
|
||||||
|
Start "$1"
|
||||||
|
return_code=$?
|
||||||
|
StatusOutput
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
Stop "$1"
|
||||||
|
return_code=$?
|
||||||
|
StatusOutput
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
Stop "stop"
|
||||||
|
Start "start"
|
||||||
|
return_code=$?
|
||||||
|
StatusOutput
|
||||||
|
;;
|
||||||
|
reload)
|
||||||
|
Reload
|
||||||
|
return_code=$?
|
||||||
|
StatusOutput
|
||||||
|
;;
|
||||||
|
destroy)
|
||||||
|
Stop "$1" &> /dev/null
|
||||||
|
DestroyIpsets "$IPSET_TOTAL_PROXY" "$IPSET_CIDR_TMP" "$IPSET_CIDR" "$IPSET_IP_TMP" "$IPSET_IP" "$IPSET_DNSMASQ" "$IPSET_ONION"
|
||||||
|
ClearDataFiles
|
||||||
|
return_code=$?
|
||||||
|
rm -f "$UPDATE_PID_FILE" "$START_PID_FILE"
|
||||||
|
DnsmasqRestart
|
||||||
|
StatusOutput
|
||||||
|
;;
|
||||||
|
update|force-update)
|
||||||
|
Update "$1"
|
||||||
|
return_code=$?
|
||||||
|
StatusOutput
|
||||||
|
;;
|
||||||
|
data-files)
|
||||||
|
if [ -e "$UPDATE_PID_FILE" ] && [ "$1" != "force-update" ]; then
|
||||||
|
echo " ${NAME} - Error! Another instance of update is already running" >&2
|
||||||
|
exit 2
|
||||||
|
else
|
||||||
|
GetDataFiles
|
||||||
|
return_code=$?
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
total-proxy-on)
|
||||||
|
if ! CheckStatus; then
|
||||||
|
echo " ${NAME} does not running" >&2
|
||||||
|
return_code=1
|
||||||
|
else
|
||||||
|
TotalProxyOn
|
||||||
|
return_code=$?
|
||||||
|
fi
|
||||||
|
StatusOutput
|
||||||
|
;;
|
||||||
|
total-proxy-off)
|
||||||
|
if ! CheckStatus; then
|
||||||
|
echo " ${NAME} does not running" >&2
|
||||||
|
return_code=1
|
||||||
|
else
|
||||||
|
TotalProxyOff
|
||||||
|
return_code=$?
|
||||||
|
fi
|
||||||
|
StatusOutput
|
||||||
|
;;
|
||||||
|
total-proxy-status)
|
||||||
|
TotalProxyStatus
|
||||||
|
return_code=$?
|
||||||
|
echo $return_code
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
Status
|
||||||
|
return_code=$?
|
||||||
|
;;
|
||||||
|
raw-status)
|
||||||
|
CheckStatus
|
||||||
|
return_code=$?
|
||||||
|
case $return_code in
|
||||||
|
0)
|
||||||
|
if [ -e "$START_PID_FILE" ]; then
|
||||||
|
return_code=3
|
||||||
|
echo 3
|
||||||
|
elif [ -e "$UPDATE_PID_FILE" ]; then
|
||||||
|
return_code=4
|
||||||
|
echo 4
|
||||||
|
else
|
||||||
|
echo 0
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return_code=2
|
||||||
|
echo 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
vpn-route-status)
|
||||||
|
IptVpnRouteStatus
|
||||||
|
return_code=$?
|
||||||
|
echo $return_code
|
||||||
|
;;
|
||||||
|
html-info)
|
||||||
|
Info
|
||||||
|
return_code=$?
|
||||||
|
;;
|
||||||
|
-h|--help|help)
|
||||||
|
Help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
Help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit $return_code;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
User admin
|
||||||
|
PidFile /var/run/tor.pid
|
||||||
|
DataDirectory /var/lib/tor
|
||||||
|
VirtualAddrNetwork 10.254.0.0/16
|
||||||
|
AutomapHostsOnResolve 1
|
||||||
|
TransPort 192.168.1.1:9040
|
||||||
|
TransPort 127.0.0.1:9040
|
||||||
|
DNSPort 9053
|
||||||
|
DNSListenAddress 127.0.0.1
|
||||||
|
#SOCKSPort 192.168.1.1:9050 # Tor socks-proxy
|
||||||
|
GeoIPFile /usr/share/tor/geoip
|
||||||
|
GeoIPv6File /usr/share/tor/geoip6
|
||||||
|
ExcludeExitNodes {RU},{UA},{BY},{KZ},{MD},{TM},{UZ},{AM},{KG}
|
||||||
|
ExitPolicy reject *:*
|
||||||
|
ExitPolicy reject6 *:*
|
||||||
Binary file not shown.
Reference in New Issue
Block a user