You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

428 lines
15 KiB

  1. #!/bin/sh /etc/rc.common
  2. # TLD optimization written by Dirk Brenken (dev@brenken.org)
  3. PKG_VERSION=
  4. export START=94
  5. export USE_PROCD=1
  6. readonly packageName='simple-adblock'
  7. readonly serviceName="$packageName $PKG_VERSION"
  8. readonly PID="/var/run/${packageName}.pid"
  9. readonly dnsmasqFile="/var/dnsmasq.d/${packageName}"
  10. export EXTRA_COMMANDS="check killcache"
  11. export EXTRA_HELP=" check Checks if specified domain is found in current blacklist"
  12. readonly A_TMP='/var/simple-adblock.hosts.a.tmp'
  13. readonly B_TMP='/var/simple-adblock.hosts.b.tmp'
  14. readonly CACHE_TMP='/var/simple-adblock.cache'
  15. readonly h_filter='/localhost/d;/^#/d;/^[^0-9]/d;s/^0\.0\.0\.0.//;s/^127\.0\.0\.1.//;s/[[:space:]]*#.*$//;s/[[:cntrl:]]$//;s/[[:space:]]//g;/[`~!@#\$%\^&\*()=+;:"'\'',<>?/\|[{}]/d;/]/d;/\./!d;/^$/d;'
  16. readonly d_filter='/^#/d;s/[[:space:]]*#.*$//;s/[[:space:]]*$//;s/[[:cntrl:]]$//;/[[:space:]]/d;/[`~!@#\$%\^&\*()=+;:"'\'',<>?/\|[{}]/d;/]/d;/\./!d;/^$/d;'
  17. readonly f_filter='s|^|local=/|;s|$|/|'
  18. readonly checkmark='\xe2\x9c\x93'
  19. readonly xmark='\xe2\x9c\x97'
  20. readonly _OK_='\033[0;32m\xe2\x9c\x93\033[0m'
  21. readonly _FAIL_='\033[0;31m\xe2\x9c\x97\033[0m'
  22. readonly __OK__='\033[0;32m[\xe2\x9c\x93]\033[0m'
  23. readonly __FAIL__='\033[0;31m[\xe2\x9c\x97]\033[0m'
  24. readonly _ERROR_='\033[0;31mERROR\033[0m'
  25. create_lock() { [ -e "$PID" ] && return 1; touch "$PID"; }
  26. remove_lock() { [ -e "$PID" ] && rm -f "$PID"; rm -f /var/simple-adblock_tmp_* >/dev/null 2>&1; }
  27. trap remove_lock EXIT
  28. output_ok() { case $verbosity in 1) output 1 "$_OK_";; 2) output 2 "$__OK__\n";; esac; }
  29. output_okn() { case $verbosity in 1) output 1 "$_OK_\n";; 2) output 2 "$__OK__\n";; esac; }
  30. output_fail() { case $verbosity in 1) output 1 "$_FAIL_";; 2) output 2 "$__FAIL__\n";; esac; }
  31. output_failn() { case $verbosity in 1) output 1 "$_FAIL_\n";; 2) output 2 "$__FAIL__\n";; esac; }
  32. export logmsg
  33. output() {
  34. # Can take a single parameter (text) to be output at any verbosity
  35. # Or target verbosity level and text to be output at specifc verbosity
  36. if [[ $# -ne 1 ]]; then
  37. [[ ! $((verbosity & $1)) -gt 0 ]] && return 0 || shift
  38. fi
  39. [[ -t 1 ]] && echo -e -n "$1" # if we're running in console, echo text
  40. # strip text of ASCII control characters and send completed lines to log
  41. local msg=$(echo -n "${1/$serviceName /service }" | sed 's|\\033\[[0-9]\?;\?[0-9]\?[0-9]\?m||g');
  42. if [[ $(echo -e -n "$msg" | wc -l) -gt 0 ]]; then
  43. logger -t "${packageName:-service} [$$]" "$(echo -e -n ${logmsg}${msg})"
  44. logmsg=''
  45. else
  46. logmsg=${logmsg}${msg}
  47. fi
  48. }
  49. led_on(){ [[ -n "${1}" && -e "${1}/trigger" ]] && echo "default-on" > "${1}/trigger" 2>&1; }
  50. led_off(){ [[ -n "${1}" && -e "${1}/trigger" ]] && echo "none" > "${1}/trigger" 2>&1; }
  51. boot() { load_package_config; ( sleep $bootDelay && rc_procd start_service && rc_procd service_triggers | cat & ); }
  52. export serviceEnabled verbosity forceDNS debug led wan_if wan_gw wanphysdev bootDelay dl_command serviceStatus
  53. load_package_config() {
  54. config_load "$packageName"
  55. config_get_bool serviceEnabled 'config' 'enabled' 1
  56. config_get_bool forceDNS 'config' 'force_dns' 1
  57. config_get_bool debug 'config' 'debug' 0
  58. config_get bootDelay 'config' 'boot_delay' '120'
  59. config_get dlTimeout 'config' 'download_timeout' '20'
  60. config_get verbosity 'config' 'verbosity' '2'
  61. config_get led 'config' 'led'
  62. if [ -z "${verbosity##*[!0-9]*}" ] || [ $verbosity -lt 0 ] || [ $verbosity -gt 2 ]; then
  63. verbosity=1
  64. fi
  65. source /lib/functions/network.sh
  66. dl_command="wget --no-check-certificate --timeout $dlTimeout -qO-"
  67. led="${led:+/sys/class/leds/$led}"
  68. }
  69. is_enabled() {
  70. local sleepCount=1
  71. load_package_config
  72. if [ "$debug" -ne 0 ]; then
  73. exec 1>>/tmp/simple-adblock.log
  74. exec 2>&1
  75. set -x
  76. fi
  77. if [ $serviceEnabled -eq 0 ]; then
  78. if [ "$1" == "on_start" ]; then
  79. output "$packageName is currently disabled.\n"
  80. output "Run the following commands before starting service again:\n"
  81. output "uci set $packageName.config.enabled='1'; uci commit;\n"
  82. fi
  83. return 1
  84. fi
  85. network_flush_cache; network_find_wan wan_if; network_get_gateway wan_gw $wan_if;
  86. [ -n "$wan_gw" ] && return 0
  87. output "$_ERROR_: $serviceName failed to discover WAN gateway.\n"; return 1;
  88. }
  89. dnsmasq_kill() { killall -q -HUP dnsmasq; }
  90. dnsmasq_restart() { /etc/init.d/dnsmasq restart >/dev/null 2>&1; }
  91. reload_dnsmasq() {
  92. case $1 in
  93. on_start)
  94. if [ -s $dnsmasqFile ]; then
  95. output 3 'Restarting dnsmasq '
  96. if dnsmasq_restart; then
  97. led_on "$led"
  98. output_okn
  99. else
  100. output_failn
  101. output "$_ERROR_: $serviceName failed to restart dnsmasq!\n"
  102. serviceStatus="${serviceStatus:-'DNSMASQ restart error'}"
  103. return 1
  104. fi
  105. else
  106. output "$_ERROR_: $serviceName failed to create its data file!\n"
  107. serviceStatus="${serviceStatus:-'Failed to create data file'}"
  108. return 1
  109. fi
  110. ;;
  111. on_stop)
  112. [ -f $dnsmasqFile ] && mv $dnsmasqFile $CACHE_TMP
  113. output 3 'Restarting dnsmasq '
  114. if dnsmasq_restart; then
  115. led_off "$led"
  116. output_okn
  117. output "$serviceName stopped.\n"
  118. return 0
  119. else
  120. output_failn;
  121. output "$_ERROR_: $serviceName failed to restart dnsmasq!\n"
  122. return 1
  123. fi
  124. ;;
  125. quiet | *)
  126. dnsmasq_restart && return 0 || return 1
  127. ;;
  128. esac
  129. }
  130. ubus_status(){
  131. case "$1" in
  132. add)
  133. ubus_status set "$(ubus_status get)${2}"
  134. ;;
  135. del | set)
  136. ubus call service set "{ \"name\": \"${packageName}\", \"instances\": { \"status\": { \"command\": [ \"\" ], \"data\": { \"status\": \"${2}\" }}}}"
  137. # ubus call service set "{ \"name\": \"${packageName}\", \"instances\": { \"status\": { \"data\": { \"status\": \"${2}\" }}}}"
  138. ;;
  139. get)
  140. echo "$(ubus call service list "{\"name\": \"${packageName}\"}" | jsonfilter -l1 -e "@['${packageName}']['instances']['status']['data']['status']")"
  141. ;;
  142. esac
  143. }
  144. is_chaos_calmer() { ubus -S call system board | grep -q 'Chaos Calmer'; }
  145. remove_fw3_redirect() {
  146. config_get name "$1" 'name'
  147. if [[ -n "$name" && "$name" != "${name//simple_adblock}" ]]; then
  148. uci -q del "firewall.$1"
  149. fi
  150. }
  151. fw3_setup() {
  152. config_load 'firewall'
  153. config_foreach remove_fw3_redirect 'redirect'
  154. if [ "$1" == "start" ]; then
  155. uci -q add firewall redirect >/dev/null 2>&1
  156. uci -q set firewall.@redirect[-1].name='simple_adblock_dns_hijack'
  157. uci -q set firewall.@redirect[-1].target='DNAT'
  158. uci -q set firewall.@redirect[-1].src='lan'
  159. uci -q set firewall.@redirect[-1].proto='tcpudp'
  160. uci -q set firewall.@redirect[-1].src_dport='53'
  161. uci -q set firewall.@redirect[-1].dest_port='53'
  162. uci -q set firewall.@redirect[-1].dest_ip="$ip"
  163. uci -q set firewall.@redirect[-1].reflection='0'
  164. fi
  165. if [ -n "$(uci changes firewall)" ]; then
  166. uci -q commit firewall
  167. /etc/init.d/firewall restart >/dev/null 2>&1
  168. fi
  169. }
  170. process_url() {
  171. local label type D_TMP R_TMP
  172. [[ -n "$1" && -n "$2" && -n "$3" ]] || return 1
  173. # ping -W5 -c1 "$(echo $1 | awk -F '/' '{print $3}')" 1>/dev/null 2>/dev/null || { output_fail; return 1; }
  174. if [ "$2" == "hosts" ]; then
  175. label="Hosts: $(echo $1 | cut -d'/' -f3)" filter="$h_filter"
  176. else
  177. label="Domains: $(echo $1 | cut -d'/' -f3)" filter="$d_filter"
  178. fi
  179. if [ "$3" == "blocked" ]; then
  180. type='Blocked'; D_TMP="$B_TMP";
  181. else
  182. type='Allowed'; D_TMP="$A_TMP";
  183. fi
  184. while [[ -z "$R_TMP" || -e "$R_TMP" ]]; do
  185. R_TMP="/var/${packageName}_tmp_$(head -c40 /dev/urandom 2>/dev/null | tr -dc 'A-Za-z0-9' 2>/dev/null)"
  186. done
  187. touch "$R_TMP"
  188. if ! $dl_command "$1" > "$R_TMP" 2>/dev/null; then
  189. output 2 "[DL] $type $label $__FAIL__\n"
  190. output 1 "$_FAIL_"
  191. ubus_status add '-'
  192. return 1
  193. fi
  194. sed -i "$filter" "$R_TMP"
  195. cat "$R_TMP" >> "$D_TMP"
  196. rm -f "$R_TMP" >/dev/null 2>/dev/null
  197. output 2 "[DL] $type $label $__OK__\n"
  198. output 1 "$_OK_"
  199. ubus_status add '+'
  200. return 0
  201. }
  202. download_lists() {
  203. local i hf w_filter whitelist_domains blacklist_domains whitelist_domains_urls blacklist_domains_urls blacklist_hosts_urls j=0
  204. config_get whitelist_domains 'config' 'whitelist_domain'
  205. config_get blacklist_domains 'config' 'blacklist_domain'
  206. config_get whitelist_domains_urls 'config' 'whitelist_domains_url'
  207. config_get blacklist_domains_urls 'config' 'blacklist_domains_url'
  208. config_get blacklist_hosts_urls 'config' 'blacklist_hosts_url'
  209. ubus_status set 'Reloading '
  210. [ ! -d ${dnsmasqFile%/*} ] && mkdir -p ${dnsmasqFile%/*}
  211. for i in $A_TMP $B_TMP $CACHE_TMP $dnsmasqFile; do [ -f $i ] && rm -f $i; done
  212. if [ "$(awk '/^MemFree/ {print int($2/1000)}' "/proc/meminfo")" -lt 32 ]; then
  213. output 3 'Low free memory, restarting dnsmasq...'
  214. reload_dnsmasq 'quiet' && output_okn || output_failn
  215. fi
  216. touch $A_TMP; touch $B_TMP;
  217. output 1 'Downloading lists '
  218. if [ -n "$blacklist_hosts_urls" ]; then
  219. for hf in ${blacklist_hosts_urls}; do
  220. process_url "$hf" 'hosts' 'blocked' &
  221. done
  222. fi
  223. if [ -n "$blacklist_domains_urls" ]; then
  224. for hf in ${blacklist_domains_urls}; do
  225. process_url "$hf" 'domains' 'blocked' &
  226. done
  227. fi
  228. if [ -n "$whitelist_domains_urls" ]; then
  229. for hf in ${whitelist_domains_urls}; do
  230. process_url "$hf" 'domains' 'allowed' &
  231. done
  232. fi
  233. wait
  234. i="$(ubus_status get)"
  235. [ "${i//-}" != "$i" ] && serviceStatus="${serviceStatus:-'Download error'}" || unset serviceStatus
  236. i="${i//Reloading }"
  237. i="${i//-/$xmark}"
  238. i="${i//+/$checkmark}"
  239. [ "$verbosity" == "1" ] && logmsg="${logmsg}${i}"
  240. output 1 '\n'
  241. [ -n "$blacklist_domains" ] && for hf in ${blacklist_domains}; do echo "$hf" | sed "$d_filter" >> $B_TMP; done
  242. whitelist_domains="${whitelist_domains}"$'\n'"$(cat $A_TMP)"
  243. [ -n "$whitelist_domains" ] && for hf in ${whitelist_domains}; do hf=$(echo $hf | sed 's/\./\\./g'); w_filter="$w_filter/^${hf}$/d;/\\.${hf}$/d;"; done
  244. if [ -s $B_TMP ]; then
  245. output 1 'Processing downloads '
  246. output 2 'Sorting combined list '
  247. if sort $B_TMP | uniq > $A_TMP; then
  248. output_ok
  249. else
  250. output_fail
  251. serviceStatus="${serviceStatus:-'Sorting error'}"
  252. fi
  253. output 2 'Optimizing combined list '
  254. if awk -F "." '{for(f=NF;f>1;f--)printf "%s.",$f;print $1}' "$A_TMP" > "$B_TMP"; then
  255. if sort "$B_TMP" > "$A_TMP"; then
  256. if awk '{if(NR==1){tld=$NF};while(getline){if($NF!~tld"\\."){print tld;tld=$NF}}print tld}' "$A_TMP" > "$B_TMP"; then
  257. if awk -F "." '{for(f=NF;f>1;f--)printf "%s.",$f;print $1}' "$B_TMP" > "$A_TMP"; then
  258. if sort "$A_TMP" | uniq > "$B_TMP"; then
  259. output_ok
  260. else
  261. output_fail
  262. serviceStatus="${serviceStatus:-'Data file optimization error'}"
  263. mv $A_TMP $B_TMP
  264. fi
  265. else
  266. output_fail
  267. serviceStatus="${serviceStatus:-'Data file optimization error'}"
  268. fi
  269. else
  270. output_fail
  271. serviceStatus="${serviceStatus:-'Data file optimization error'}"
  272. mv $A_TMP $B_TMP
  273. fi
  274. else
  275. output_fail
  276. serviceStatus="${serviceStatus:-'Data file optimization error'}"
  277. fi
  278. else
  279. output_fail
  280. serviceStatus="${serviceStatus:-'Data file optimization error'}"
  281. mv $A_TMP $B_TMP
  282. fi
  283. output 2 'Whitelisting domains '
  284. if sed -i "$w_filter" $B_TMP; then
  285. output_ok
  286. else
  287. output_fail
  288. serviceStatus="${serviceStatus:-'Whitelist processing error'}"
  289. fi
  290. output 2 'Formatting merged file '
  291. if sed "$f_filter" $B_TMP > $A_TMP; then
  292. output_ok
  293. else
  294. output_fail
  295. serviceStatus="${serviceStatus:-'Data file formatting error'}"
  296. fi
  297. output 2 'Creating dnsmasq config '
  298. if mv $A_TMP $dnsmasqFile; then
  299. output_ok
  300. else
  301. output_fail
  302. serviceStatus="${serviceStatus:-'Error moving data file'}"
  303. fi
  304. output 2 'Removing temporary files '
  305. rm -f /var/simple-adblock_tmp_* >/dev/null 2>&1;
  306. for i in $A_TMP $B_TMP $CACHE_TMP; do if [ -s $i ]; then rm -f $i || j=1; fi; done
  307. if [ $j -eq 0 ]; then
  308. output_ok
  309. else
  310. output_fail
  311. serviceStatus="${serviceStatus:-'Error removing temporary files'}"
  312. fi
  313. output 1 '\n'
  314. fi
  315. }
  316. start_service() {
  317. local ip status
  318. if create_lock; then
  319. is_enabled 'on_start' || return 1
  320. procd_open_instance
  321. procd_set_param command /bin/true
  322. procd_set_param stdout 1
  323. procd_set_param stderr 1
  324. network_get_ipaddr ip 'lan'
  325. if [[ $forceDNS -ne 0 && -n "$ip" ]]; then
  326. if is_chaos_calmer; then
  327. fw3_setup 'start'
  328. else
  329. procd_open_data
  330. json_add_array firewall
  331. json_add_object ""
  332. json_add_string type redirect
  333. json_add_string target 'DNAT'
  334. json_add_string src 'lan'
  335. json_add_string proto 'tcpudp'
  336. json_add_string src_dport '53'
  337. json_add_string dest_port '53'
  338. json_add_string dest_ip "$ip"
  339. json_add_string name 'simple_adblock_dns_hijack'
  340. json_add_string reflection '0'
  341. json_close_object
  342. json_close_array
  343. procd_close_data
  344. fi
  345. fi
  346. procd_close_instance
  347. status="$(ubus_status get)"
  348. if [ -s "$CACHE_TMP" ] && [ "$1" != "reload" ]; then
  349. output "Starting $serviceName...\n"
  350. output 3 'Found existing data file, reusing it '
  351. mv $CACHE_TMP $dnsmasqFile && output_okn || output_failn
  352. reload_dnsmasq 'on_start' || serviceStatus="${serviceStatus:-'DNSMASQ restart error'}"
  353. elif [ "$1" == "reload" ] || [ "$status" == "${status//Success}" ]; then
  354. output "Reloading $serviceName...\n"
  355. download_lists
  356. reload_dnsmasq 'on_start' || serviceStatus="${serviceStatus:-'DNSMASQ restart error'}"
  357. elif [ ! -s "$dnsmasqFile" ]; then
  358. output "Starting $serviceName...\n"
  359. download_lists
  360. reload_dnsmasq 'on_start' || serviceStatus="${serviceStatus:-'DNSMASQ restart error'}"
  361. fi
  362. if [ -s "$dnsmasqFile" ]; then
  363. if [ -z "$serviceStatus" ]; then
  364. output "$serviceName is blocking $(wc -l < $dnsmasqFile) domains "; output_okn;
  365. serviceStatus="Success: $(wc -l < $dnsmasqFile) domains blocked"
  366. else
  367. output "$serviceName is blocking $(wc -l < $dnsmasqFile) domains with error: $serviceStatus "; output_failn;
  368. serviceStatus="$(wc -l < $dnsmasqFile) domains blocked with error: $serviceStatus"
  369. fi
  370. fi
  371. [ -n "$serviceStatus" ] && ubus_status set "$serviceStatus"
  372. remove_lock
  373. else
  374. output "$serviceName: another instance is starting up "; output_failn;
  375. return 1
  376. fi
  377. }
  378. service_started() { procd_set_config_changed firewall; }
  379. reload_service() { start_service 'reload'; }
  380. killcache() { [ -s $CACHE_TMP ] && rm -f $CACHE_TMP >/dev/null 2>/dev/null; }
  381. stop_service() {
  382. load_package_config
  383. if [ $serviceEnabled -gt 0 ]; then
  384. output "Stopping $serviceName...\n"
  385. reload_dnsmasq 'on_stop'
  386. else
  387. reload_dnsmasq 'quiet'
  388. fi
  389. ubus_status set 'Stopped'
  390. procd_set_config_changed firewall
  391. }
  392. check() {
  393. load_package_config
  394. local string="$1"
  395. if [ ! -f $dnsmasqFile ]; then
  396. echo "No local blacklist ($dnsmasqFile) found."
  397. elif [ -z "$string" ]; then
  398. echo "Usage: /etc/init.d/${packageName} check 'domain'"
  399. elif grep -m1 -q $string $dnsmasqFile; then
  400. echo "Found $(grep $string $dnsmasqFile | wc -l) matches for $string in $dnsmasqFile:"
  401. grep $string $dnsmasqFile | sed 's|local=/||;s|/$||;'
  402. else
  403. echo "The $string is not found in current blacklist."
  404. fi
  405. }