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.

638 lines
19 KiB

  1. #!/bin/sh
  2. # dns based ad/abuse domain blocking
  3. # written by Dirk Brenken (dev@brenken.org)
  4. # This is free software, licensed under the GNU General Public License v3.
  5. # You should have received a copy of the GNU General Public License
  6. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  7. # set initial defaults
  8. #
  9. LC_ALL=C
  10. PATH="/usr/sbin:/usr/bin:/sbin:/bin"
  11. adb_ver="2.8.5"
  12. adb_sysver="$(ubus -S call system board | jsonfilter -e '@.release.description')"
  13. adb_enabled=1
  14. adb_debug=0
  15. adb_minfree=2
  16. adb_manmode=0
  17. adb_forcesrt=0
  18. adb_forcedns=0
  19. adb_backup=0
  20. adb_backupdir="/mnt"
  21. adb_whitelist="/etc/adblock/adblock.whitelist"
  22. adb_whitelist_rset="\$1 ~/^([A-Za-z0-9_-]+\.){1,}[A-Za-z]+/{print tolower(\"^\"\$1\"\\\|[.]\"\$1)}"
  23. adb_fetch="/usr/bin/wget"
  24. adb_fetchparm="--quiet --no-cache --no-cookies --max-redirect=0 --timeout=10 --no-check-certificate -O"
  25. adb_dnslist="dnsmasq unbound named kresd"
  26. adb_dnsprefix="adb_list"
  27. adb_dnsfile="${adb_dnsprefix}.overall"
  28. adb_rtfile="/tmp/adb_runtime.json"
  29. adb_sources=""
  30. adb_src_cat_shalla=""
  31. adb_action="${1}"
  32. # f_envload: load adblock environment
  33. #
  34. f_envload()
  35. {
  36. local services dns_up cnt=0
  37. # source in system libraries
  38. #
  39. if [ -r "/lib/functions.sh" ] && [ -r "/usr/share/libubox/jshn.sh" ]
  40. then
  41. . "/lib/functions.sh"
  42. . "/usr/share/libubox/jshn.sh"
  43. else
  44. f_log "error" "system libraries not found"
  45. fi
  46. # parse global section by callback
  47. #
  48. config_cb()
  49. {
  50. local type="${1}"
  51. if [ "${type}" = "adblock" ]
  52. then
  53. option_cb()
  54. {
  55. local option="${1}"
  56. local value="${2}"
  57. eval "${option}=\"${value}\""
  58. }
  59. else
  60. reset_cb
  61. fi
  62. }
  63. # parse 'source' sections
  64. #
  65. parse_config()
  66. {
  67. local value opt section="${1}" options="enabled adb_src adb_src_rset adb_src_cat"
  68. eval "adb_sources=\"${adb_sources} ${section}\""
  69. for opt in ${options}
  70. do
  71. config_get value "${section}" "${opt}"
  72. if [ -n "${value}" ]
  73. then
  74. eval "${opt}_${section}=\"${value}\""
  75. fi
  76. done
  77. }
  78. # load adblock config
  79. #
  80. config_load adblock
  81. config_foreach parse_config source
  82. # set dns backend environment
  83. #
  84. while [ ${cnt} -le 20 ]
  85. do
  86. services="$(ubus -S call service list 2>/dev/null)"
  87. if [ -n "${services}" ]
  88. then
  89. for dns in ${adb_dnslist}
  90. do
  91. dns_up="$(printf "%s" "${services}" | jsonfilter -l1 -e "@.${dns}.instances.*.running")"
  92. if [ "${dns_up}" = "true" ]
  93. then
  94. case "${dns}" in
  95. dnsmasq)
  96. adb_dns="${dns}"
  97. adb_dnsdir="${adb_dnsdir:="/tmp/dnsmasq.d"}"
  98. adb_dnshidedir="${adb_dnsdir}/.adb_hidden"
  99. adb_dnsformat="awk '{print \"local=/\"\$0\"/\"}'"
  100. break 2
  101. ;;
  102. unbound)
  103. adb_dns="${dns}"
  104. adb_dnsdir="${adb_dnsdir:="/var/lib/unbound"}"
  105. adb_dnshidedir="${adb_dnsdir}/.adb_hidden"
  106. adb_dnsformat="awk '{print \"local-zone: \042\"\$0\"\042 static\"}'"
  107. break 2
  108. ;;
  109. named)
  110. adb_dns="${dns}"
  111. adb_dnsdir="${adb_dnsdir:="/var/lib/bind"}"
  112. adb_dnshidedir="${adb_dnsdir}/.adb_hidden"
  113. adb_dnsformat="awk '{print \"\"\$0\" IN CNAME .\n*.\"\$0\" IN CNAME .\"}'"
  114. break 2
  115. ;;
  116. kresd)
  117. adb_dns="${dns}"
  118. adb_dnsdir="${adb_dnsdir:="/tmp/kresd"}"
  119. adb_dnshidedir="${adb_dnsdir}/.adb_hidden"
  120. adb_dnsformat="awk '{print \"\"\$0\" CNAME .\n*.\"\$0\" CNAME .\"}'"
  121. break 2
  122. ;;
  123. esac
  124. fi
  125. done
  126. fi
  127. sleep 1
  128. cnt=$((cnt+1))
  129. done
  130. if [ -z "${adb_dns}" ] || [ -z "${adb_dnsformat}" ] || [ ! -x "$(command -v ${adb_dns})" ] || [ ! -d "${adb_dnsdir}" ]
  131. then
  132. f_log "error" "no active/supported DNS backend found"
  133. fi
  134. # force dns to local resolver
  135. #
  136. if [ ${adb_forcedns} -eq 1 ] && [ -z "$(uci -q get firewall.adblock_dns)" ]
  137. then
  138. uci -q set firewall.adblock_dns="redirect"
  139. uci -q set firewall.adblock_dns.name="Adblock DNS"
  140. uci -q set firewall.adblock_dns.src="lan"
  141. uci -q set firewall.adblock_dns.proto="tcp udp"
  142. uci -q set firewall.adblock_dns.src_dport="53"
  143. uci -q set firewall.adblock_dns.dest_port="53"
  144. uci -q set firewall.adblock_dns.target="DNAT"
  145. elif [ ${adb_forcedns} -eq 0 ] && [ -n "$(uci -q get firewall.adblock_dns)" ]
  146. then
  147. uci -q delete firewall.adblock_dns
  148. fi
  149. if [ -n "$(uci -q changes firewall)" ]
  150. then
  151. uci -q commit firewall
  152. if [ $(/etc/init.d/firewall enabled; printf "%u" ${?}) -eq 0 ]
  153. then
  154. /etc/init.d/firewall reload >/dev/null 2>&1
  155. fi
  156. fi
  157. }
  158. # f_envcheck: check/set environment prerequisites
  159. #
  160. f_envcheck()
  161. {
  162. local ssl_lib
  163. # check 'enabled' option
  164. #
  165. if [ ${adb_enabled} -ne 1 ]
  166. then
  167. if [ -s "${adb_dnsdir}/${adb_dnsfile}" ]
  168. then
  169. f_rmdns
  170. f_dnsrestart
  171. fi
  172. f_log "info " "adblock is currently disabled, please set adb_enabled to '1' to use this service"
  173. exit 0
  174. fi
  175. # check fetch utility
  176. #
  177. ssl_lib="-"
  178. if [ -x "${adb_fetch}" ]
  179. then
  180. if [ "$(readlink -fn "${adb_fetch}")" = "/usr/bin/wget-nossl" ]
  181. then
  182. adb_fetchparm="--quiet --no-cache --no-cookies --max-redirect=0 --timeout=10 -O"
  183. elif [ "$(readlink -fn "/bin/wget")" = "/bin/busybox" ] || [ "$(readlink -fn "${adb_fetch}")" = "/bin/busybox" ]
  184. then
  185. adb_fetch="/bin/busybox"
  186. adb_fetchparm="-q -O"
  187. else
  188. ssl_lib="built-in"
  189. fi
  190. fi
  191. if [ ! -x "${adb_fetch}" ] && [ "$(readlink -fn "/bin/wget")" = "/bin/uclient-fetch" ]
  192. then
  193. adb_fetch="/bin/uclient-fetch"
  194. if [ -f "/lib/libustream-ssl.so" ]
  195. then
  196. adb_fetchparm="-q --timeout=10 --no-check-certificate -O"
  197. ssl_lib="libustream-ssl"
  198. else
  199. adb_fetchparm="-q --timeout=10 -O"
  200. fi
  201. fi
  202. if [ ! -x "${adb_fetch}" ] || [ -z "${adb_fetch}" ] || [ -z "${adb_fetchparm}" ]
  203. then
  204. f_log "error" "no download utility found, please install 'uclient-fetch' with 'libustream-mbedtls' or the full 'wget' package"
  205. fi
  206. adb_fetchinfo="${adb_fetch##*/} (${ssl_lib})"
  207. # create dns hideout directory
  208. #
  209. if [ ! -d "${adb_dnshidedir}" ]
  210. then
  211. mkdir -p -m 660 "${adb_dnshidedir}"
  212. chown -R "${adb_dns}":"${adb_dns}" "${adb_dnshidedir}" 2>/dev/null
  213. else
  214. rm -f "${adb_dnshidedir}/${adb_dnsprefix}"*
  215. fi
  216. # create adblock temp file/directory
  217. #
  218. adb_tmpload="$(mktemp -tu)"
  219. adb_tmpfile="$(mktemp -tu)"
  220. adb_tmpdir="$(mktemp -p /tmp -d)"
  221. # prepare whitelist entries
  222. #
  223. if [ -s "${adb_whitelist}" ]
  224. then
  225. awk "${adb_whitelist_rset}" "${adb_whitelist}" > "${adb_tmpdir}/tmp.whitelist"
  226. fi
  227. }
  228. # f_rmtemp: remove temporary files & directories
  229. #
  230. f_rmtemp()
  231. {
  232. if [ -d "${adb_tmpdir}" ]
  233. then
  234. rm -f "${adb_tmpload}"
  235. rm -f "${adb_tmpfile}"
  236. rm -rf "${adb_tmpdir}"
  237. fi
  238. }
  239. # f_rmdns: remove dns related files & directories
  240. #
  241. f_rmdns()
  242. {
  243. if [ -n "${adb_dns}" ]
  244. then
  245. rm -f "${adb_dnsdir}/${adb_dnsprefix}"*
  246. rm -f "${adb_backupdir}/${adb_dnsprefix}"*.gz
  247. rm -rf "${adb_dnshidedir}"
  248. > "${adb_rtfile}"
  249. fi
  250. }
  251. # f_dnsrestart: restart the dns backend
  252. #
  253. f_dnsrestart()
  254. {
  255. local dns_up mem_free cnt=0
  256. "/etc/init.d/${adb_dns}" restart >/dev/null 2>&1
  257. while [ ${cnt} -le 10 ]
  258. do
  259. dns_up="$(ubus -S call service list "{\"name\":\"${adb_dns}\"}" | jsonfilter -l1 -e "@.${adb_dns}.instances.*.running")"
  260. if [ "${dns_up}" = "true" ]
  261. then
  262. mem_free="$(awk '/^MemFree/ {print int($2/1000)}' "/proc/meminfo")"
  263. if [ ${mem_free} -ge ${adb_minfree} ]
  264. then
  265. return 0
  266. fi
  267. fi
  268. cnt=$((cnt+1))
  269. sleep 1
  270. done
  271. return 1
  272. }
  273. # f_list: backup/restore/remove block lists
  274. #
  275. f_list()
  276. {
  277. local mode="${1}" in_rc="${adb_rc}" cnt=0
  278. case "${mode}" in
  279. backup)
  280. cnt="$(wc -l < "${adb_tmpfile}")"
  281. if [ ${adb_backup} -eq 1 ] && [ -d "${adb_backupdir}" ]
  282. then
  283. gzip -cf "${adb_tmpfile}" > "${adb_backupdir}/${adb_dnsprefix}.${src_name}.gz"
  284. adb_rc=${?}
  285. fi
  286. ;;
  287. restore)
  288. if [ ${adb_backup} -eq 1 ] && [ -d "${adb_backupdir}" ] &&
  289. [ -f "${adb_backupdir}/${adb_dnsprefix}.${src_name}.gz" ]
  290. then
  291. gunzip -cf "${adb_backupdir}/${adb_dnsprefix}.${src_name}.gz" > "${adb_tmpfile}"
  292. adb_rc=${?}
  293. fi
  294. ;;
  295. remove)
  296. if [ -d "${adb_backupdir}" ]
  297. then
  298. rm -f "${adb_backupdir}/${adb_dnsprefix}.${src_name}.gz"
  299. fi
  300. adb_rc=${?}
  301. ;;
  302. merge)
  303. if [ -s "${adb_tmpfile}" ]
  304. then
  305. cat "${adb_tmpfile}" >> "${adb_tmpdir}/${adb_dnsfile}"
  306. adb_rc=${?}
  307. fi
  308. ;;
  309. format)
  310. if [ -s "${adb_tmpdir}/tmp.whitelist" ]
  311. then
  312. grep -vf "${adb_tmpdir}/tmp.whitelist" "${adb_tmpdir}/${adb_dnsfile}" | eval "${adb_dnsformat}" > "${adb_dnsdir}/${adb_dnsfile}"
  313. else
  314. eval "${adb_dnsformat}" "${adb_tmpdir}/${adb_dnsfile}" > "${adb_dnsdir}/${adb_dnsfile}"
  315. fi
  316. adb_rc=${?}
  317. ;;
  318. esac
  319. f_log "debug" "name: ${src_name}, mode: ${mode}, count: ${cnt}, in_rc: ${in_rc}, out_rc: ${adb_rc}"
  320. }
  321. # f_tldcompression: top level domain compression
  322. #
  323. f_tldcompression()
  324. {
  325. local source="${1}" temp="${adb_tmpload}"
  326. awk -F "." '{for(f=NF;f > 1;f--) printf "%s.", $f;print $1}' "${source}" 2>/dev/null | sort -u > "${temp}"
  327. awk '{if(NR==1){tld=$NF};while(getline){if($NF !~ tld"\\."){print tld;tld=$NF}}print tld}' "${temp}" 2>/dev/null > "${source}"
  328. awk -F "." '{for(f=NF;f > 1;f--) printf "%s.", $f;print $1}' "${source}" 2>/dev/null > "${temp}"
  329. sort -u "${temp}" > "${source}"
  330. }
  331. # f_switch: suspend/resume adblock processing
  332. #
  333. f_switch()
  334. {
  335. local source target status mode="${1}"
  336. if [ -d "${adb_dnshidedir}" ]
  337. then
  338. if [ -s "${adb_dnsdir}/${adb_dnsfile}" ] && [ "${mode}" = "suspend" ]
  339. then
  340. source="${adb_dnsdir}/${adb_dnsfile}"
  341. target="${adb_dnshidedir}"
  342. status="suspended"
  343. elif [ -s "${adb_dnshidedir}/${adb_dnsfile}" ] && [ "${mode}" = "resume" ]
  344. then
  345. source="${adb_dnshidedir}/${adb_dnsfile}"
  346. target="${adb_dnsdir}"
  347. status="resumed"
  348. fi
  349. if [ -n "${status}" ]
  350. then
  351. mv -f "${source}"* "${target}"
  352. f_dnsrestart
  353. f_log "info " "adblock processing ${status}"
  354. fi
  355. fi
  356. }
  357. # f_query: query block list for certain (sub-)domains
  358. #
  359. f_query()
  360. {
  361. local search result cnt
  362. local domain="${1}"
  363. local tld="${domain#*.}"
  364. if [ ! -s "${adb_dnsdir}/${adb_dnsfile}" ]
  365. then
  366. printf "%s\n" "::: no active block list found, please start / resume adblock first"
  367. elif [ -z "${domain}" ] || [ "${domain}" = "${tld}" ]
  368. then
  369. printf "%s\n" "::: invalid domain input, please submit a specific (sub-)domain, e.g. 'www.abc.xyz'"
  370. else
  371. cd "${adb_dnsdir}"
  372. while [ "${domain}" != "${tld}" ]
  373. do
  374. search="${domain//./\.}"
  375. result="$(grep -Hm5 "[/\"\.]${search}[/\"]" "${adb_dnsfile}" | awk -F ':|=|/|\"' '{printf(" + %s\n",$4)}')"
  376. printf "%s\n" "::: results for (sub-)domain '${domain}' (max. 5)"
  377. printf "%s\n" "${result:=" - no match"}"
  378. domain="${tld}"
  379. tld="${domain#*.}"
  380. done
  381. fi
  382. }
  383. # f_status: output runtime information
  384. #
  385. f_status()
  386. {
  387. local key keylist value
  388. if [ -s "${adb_rtfile}" ]
  389. then
  390. if [ -s "${adb_dnsdir}/${adb_dnsfile}" ]
  391. then
  392. value="active"
  393. else
  394. value="no domains blocked"
  395. fi
  396. printf "%s\n" "::: adblock runtime information"
  397. printf " %-15s : %s\n" "status" "${value}"
  398. json_load "$(cat "${adb_rtfile}" 2>/dev/null)"
  399. json_select data
  400. json_get_keys keylist
  401. for key in ${keylist}
  402. do
  403. json_get_var value "${key}"
  404. printf " %-15s : %s\n" "${key}" "${value}"
  405. done
  406. fi
  407. }
  408. # f_log: write to syslog, exit on error
  409. #
  410. f_log()
  411. {
  412. local class="${1}" log_msg="${2}"
  413. if [ -n "${log_msg}" ] && ([ "${class}" != "debug" ] || [ ${adb_debug} -eq 1 ])
  414. then
  415. logger -t "adblock-[${adb_ver}] ${class}" "${log_msg}"
  416. if [ "${class}" = "error" ]
  417. then
  418. logger -t "adblock-[${adb_ver}] ${class}" "Please check 'https://github.com/openwrt/packages/blob/master/net/adblock/files/README.md' (${adb_sysver})"
  419. f_rmtemp
  420. if [ -s "${adb_dnsdir}/${adb_dnsfile}" ]
  421. then
  422. f_rmdns
  423. f_dnsrestart
  424. fi
  425. exit 1
  426. fi
  427. fi
  428. }
  429. # main function for block list processing
  430. #
  431. f_main()
  432. {
  433. local src_name src_rset shalla_archive enabled url hash_old hash_new cnt=0
  434. local mem_total="$(awk '/^MemTotal/ {print int($2/1000)}' "/proc/meminfo")"
  435. f_log "info " "start adblock processing ..."
  436. f_log "debug" "action: ${adb_action}, manual_mode:${adb_manmode}, backup: ${adb_backup}, dns: ${adb_dns}, fetch: ${adb_fetchinfo}, mem_total: ${mem_total}, force_srt/_dns: ${adb_forcesrt}/${adb_forcedns}"
  437. > "${adb_rtfile}"
  438. for src_name in ${adb_sources}
  439. do
  440. eval "enabled=\"\${enabled_${src_name}}\""
  441. eval "url=\"\${adb_src_${src_name}}\""
  442. eval "src_rset=\"\${adb_src_rset_${src_name}}\""
  443. > "${adb_tmpload}"
  444. > "${adb_tmpfile}"
  445. adb_rc=4
  446. # basic pre-checks
  447. #
  448. f_log "debug" "name: ${src_name}, enabled: ${enabled}, url: ${url}, rset: ${src_rset}"
  449. if [ "${enabled}" != "1" ] || [ -z "${url}" ] || [ -z "${src_rset}" ]
  450. then
  451. f_list remove
  452. continue
  453. fi
  454. # manual / backup mode
  455. #
  456. if [ ${adb_manmode} -eq 1 ] && [ -z "${adb_action}" ] && [ "${src_name}" != "blacklist" ]
  457. then
  458. f_list restore
  459. if [ ${adb_rc} -eq 0 ] && [ -s "${adb_tmpfile}" ]
  460. then
  461. f_list merge
  462. continue
  463. fi
  464. fi
  465. # download block list
  466. #
  467. if [ "${src_name}" = "blacklist" ] && [ -s "${url}" ]
  468. then
  469. cat "${url}" > "${adb_tmpload}"
  470. adb_rc=${?}
  471. elif [ "${src_name}" = "shalla" ]
  472. then
  473. shalla_archive="${adb_tmpdir}/shallalist.tar.gz"
  474. "${adb_fetch}" ${adb_fetchparm} "${shalla_archive}" "${url}" 2>/dev/null
  475. adb_rc=${?}
  476. if [ ${adb_rc} -eq 0 ]
  477. then
  478. for category in ${adb_src_cat_shalla}
  479. do
  480. tar -xOzf "${shalla_archive}" "BL/${category}/domains" >> "${adb_tmpload}"
  481. adb_rc=${?}
  482. if [ ${adb_rc} -ne 0 ]
  483. then
  484. break
  485. fi
  486. done
  487. fi
  488. rm -f "${shalla_archive}"
  489. rm -rf "${adb_tmpdir}/BL"
  490. else
  491. "${adb_fetch}" ${adb_fetchparm} "${adb_tmpload}" "${url}" 2>/dev/null
  492. adb_rc=${?}
  493. fi
  494. # check download result and prepare list output (incl. tld compression, list backup & restore)
  495. #
  496. if [ ${adb_rc} -eq 0 ] && [ -s "${adb_tmpload}" ]
  497. then
  498. awk "${src_rset}" "${adb_tmpload}" 2>/dev/null > "${adb_tmpfile}"
  499. if [ -s "${adb_tmpfile}" ]
  500. then
  501. f_tldcompression "${adb_tmpfile}"
  502. f_list backup
  503. else
  504. f_list restore
  505. fi
  506. else
  507. f_list restore
  508. fi
  509. # list merge
  510. #
  511. if [ ${adb_rc} -eq 0 ] && [ -s "${adb_tmpfile}" ]
  512. then
  513. f_list merge
  514. if [ ${adb_rc} -ne 0 ]
  515. then
  516. f_list remove
  517. fi
  518. else
  519. f_list remove
  520. fi
  521. done
  522. # hash preparation, whitelist removal and overall sort
  523. #
  524. if [ -f "${adb_dnsdir}/${adb_dnsfile}" ]
  525. then
  526. hash_old="$(sha256sum "${adb_dnsdir}/${adb_dnsfile}" | awk '{print $1}')"
  527. fi
  528. if [ -s "${adb_tmpdir}/${adb_dnsfile}" ]
  529. then
  530. if [ ${mem_total} -ge 64 ] || [ ${adb_forcesrt} -eq 1 ]
  531. then
  532. f_tldcompression "${adb_tmpdir}/${adb_dnsfile}"
  533. fi
  534. f_list format
  535. else
  536. > "${adb_dnsdir}/${adb_dnsfile}"
  537. fi
  538. hash_new="$(sha256sum "${adb_dnsdir}/${adb_dnsfile}" | awk '{print $1}')"
  539. cnt="$(wc -l < "${adb_dnsdir}/${adb_dnsfile}")"
  540. # conditional restart of the dns backend and runtime information export
  541. #
  542. chown "${adb_dns}":"${adb_dns}" "${adb_dnsdir}/${adb_dnsfile}" 2>/dev/null
  543. f_rmtemp
  544. if [ "${hash_old}" != "${hash_new}" ]
  545. then
  546. f_dnsrestart
  547. fi
  548. if [ ${?} -eq 0 ]
  549. then
  550. json_init
  551. json_add_object "data"
  552. json_add_string "adblock_version" "${adb_ver}"
  553. json_add_string "blocked_domains" "${cnt}"
  554. json_add_string "fetch_info" "${adb_fetchinfo}"
  555. json_add_string "dns_backend" "${adb_dns}"
  556. json_add_string "last_rundate" "$(/bin/date "+%d.%m.%Y %H:%M:%S")"
  557. json_add_string "system" "${adb_sysver}"
  558. json_close_object
  559. json_dump > "${adb_rtfile}"
  560. f_log "info " "block list with overall ${cnt} domains loaded successfully (${adb_sysver})"
  561. else
  562. f_log "error" "dns backend restart with active block list failed"
  563. fi
  564. }
  565. # handle different adblock actions
  566. #
  567. f_envload
  568. case "${adb_action}" in
  569. stop)
  570. f_rmtemp
  571. f_rmdns
  572. f_dnsrestart
  573. ;;
  574. restart)
  575. f_rmtemp
  576. f_rmdns
  577. f_envcheck
  578. f_main
  579. ;;
  580. suspend)
  581. f_switch suspend
  582. ;;
  583. resume)
  584. f_switch resume
  585. ;;
  586. query)
  587. f_query "${2}"
  588. ;;
  589. status)
  590. f_status
  591. ;;
  592. *)
  593. f_envcheck
  594. f_main
  595. ;;
  596. esac
  597. exit 0