Initial commit

This commit is contained in:
gSpot
2020-06-19 20:43:08 +03:00
parent 4cc2914f10
commit cf2a9062b6
34 changed files with 5644 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
## Ruantiblock
Обход блокировок в OpenWrt с помощью Tor или VPN.
Подробно об установке и настройке: [https://github.com/gSpotx2f/ruantiblock_openwrt/wiki](https://github.com/gSpotx2f/ruantiblock_openwrt/wiki)
___________________
+441
View File
@@ -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
+161
View File
@@ -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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
View File
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' }, '&#8727;')
]);
for(let i = 2; i <= 12 ; i += 2) {
cron_hour_interval.append(E('option', { 'value': String(i) }, '&#8727;/' + 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' }, '&#8727;')
);
for(let i = 2; i < 8 ; i++) {
cron_day_interval.append(E('option', { 'value': String(i) }, '&#8727;/' + i));
};
cron_day_interval.append(E('option', { 'value': '14' }, '&#8727;/14'));
cron_day_interval.append(E('option', { 'value': '28' }, '&#8727;/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', {}, '&#160;')),
]),
])
)
),
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();
},
}, '&#8593;'),
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();
},
}, '&#8595;'),
]),
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);
})
},
}),
});
@@ -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
+373
View File
@@ -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
View File
@@ -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
View File
@@ -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
+35
View File
@@ -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'
+8
View File
@@ -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
+22
View File
@@ -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"
}
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
_link="/tmp/dnsmasq.d/ruantiblock.conf"
ln -fs $DNSMASQ_DATA_FILE $_link
eval `echo "$DNSMASQ_RESTART_CMD"`
+713
View File
@@ -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;
+15
View File
@@ -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.