whitelister.sh 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. #!/bin/sh
  2. # Copyright (C) 2016 Desktopd Developers.
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. cd "`dirname "$0"`"
  15. cloudflareRulesLimit=200
  16. configPath="./config"
  17. [ -f "$configPath" ] || {
  18. printf "No such configuration file: %s\n" "$configPath"
  19. exit 1
  20. }
  21. . "$configPath"
  22. [ "$cloudflareAuthEmail" ] && [ "$cloudflareAuthKey" ] || {
  23. printf "Invalid configuration file: %s\n" "$configPath"
  24. exit 1
  25. }
  26. dataDir="./data"
  27. exitListPath="${dataDir}/exit-list"
  28. exitListCachePath="${dataDir}/exit-list.cache"
  29. whitelistDBDir="${dataDir}/whitelist-db"
  30. version='1.0'
  31. faqURI='https://www.torproject.org/docs/faq-abuse.html.en'
  32. curlUserAgent="Mozilla/5.0 (compatible; ExitWhitelister/${version}; +${faqURI})"
  33. exitListURI='https://check.torproject.org/exit-addresses'
  34. onionooEndpointAPI='https://onionoo.torproject.org'
  35. cloudflareEndpointAPI='https://api.cloudflare.com/client/v4'
  36. mkdir -p "$dataDir"
  37. mkdir -p "$whitelistDBDir"
  38. getExitList () {
  39. torsocks -i curl --retry 5 -A '' "$exitListURI" \
  40. | grep 'ExitAddress' | awk '{print $2}' | sort -V | uniq
  41. }
  42. getExitListOnionoo () {
  43. torsocks -i curl -A '' --retry 5 \
  44. "${onionooEndpointAPI}/details" \
  45. -G -d running=True -d flag=Exit \
  46. -d fields=exit_probability,or_addresses -d order=-consensus_weight \
  47. -d limit="$cloudflareRulesLimit" \
  48. | grep '"or_addresses"' \
  49. | sed 's|^.*"or_addresses":\["\([^"]*\)".*$|\1| ; s|:.*||'
  50. }
  51. # Single IP address -> whitelist ID
  52. createWhitelistID () {
  53. {
  54. # POST
  55. curl \
  56. -A "$curlUserAgent" \
  57. -H "X-Auth-Email: ${cloudflareAuthEmail}" \
  58. -H "X-Auth-Key: ${cloudflareAuthKey}" \
  59. -H "Content-Type: application/json" \
  60. -H "Accept: application/json" \
  61. --data-binary '@-' \
  62. "${cloudflareEndpointAPI}/user/firewall/access_rules/rules" <<JSON
  63. {"mode":"whitelist"
  64. ,"notes":"Exit whitelist: automatically managed"
  65. ,"configuration":
  66. {"target":"ip"
  67. ,"value":"${1}"}}
  68. JSON
  69. } \
  70. | sed 's/,/,\n/' | grep '"id":' | sed 's/^.*"id":"\([^"]*\)".*$/\1/' \
  71. | head -n 1
  72. # Note: This is a quick 'n dirty attempt to JSON 'parsing'
  73. # TODO: It may be better to write a generic JSON parser even in shell script
  74. }
  75. # Whitelist ID
  76. removeWhitelistById () {
  77. # DELETE
  78. curl \
  79. -A "$curlUserAgent" \
  80. -H "X-Auth-Email: ${cloudflareAuthEmail}" \
  81. -H "X-Auth-Key: ${cloudflareAuthKey}" \
  82. -X DELETE \
  83. "${cloudflareEndpointAPI}/user/firewall/access_rules/rules/${1}"
  84. }
  85. # Single IP address
  86. addWhitelist () {
  87. id="`createWhitelistID "${1}"`"
  88. [ "$id" ] || return 1
  89. echo "$id" > "${whitelistDBDir}/${1}"
  90. }
  91. # Single IP address
  92. removeWhitelist () {
  93. id="`cat "${whitelistDBDir}/${1}" 2>/dev/null`"
  94. [ "$id" ] || return 1
  95. removeWhitelistById "$id" && shred -uz "${whitelistDBDir}/${1}"
  96. }
  97. getExitListOnionoo > "$exitListPath"
  98. [ -f "$exitListCachePath" ] || touch "$exitListCachePath"
  99. for obsoleteIP in `\
  100. diff "$exitListCachePath" "$exitListPath" \
  101. | grep '^<' | sed 's/^<\s*//' `
  102. do [ "$obsoleteIP" ] || continue
  103. printf "[-] Removing an IP from the whitelist:\t%s\n" "$obsoleteIP"
  104. removeWhitelist "$obsoleteIP" || {
  105. printf "\n[!] Failed while removing:\t%s\n" "$obsoleteIP"
  106. }
  107. sleep 1
  108. done
  109. for newIP in `\
  110. diff "$exitListCachePath" "$exitListPath" \
  111. | grep '^>' | sed 's/^>\s*//' `
  112. do [ "$newIP" ] || continue
  113. printf "[+] Adding a new IP to the whitelist:\t%s\n" "$newIP"
  114. addWhitelist "$newIP" || {
  115. printf "\n[!] Failed while adding:\t%s\n" "$newIP"
  116. }
  117. sleep 1
  118. done
  119. #exit
  120. cat "$exitListPath" > "$exitListCachePath"
  121. # vim: set ts=4 noet ai