dns_freedns.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. #!/usr/bin/env sh
  2. #This file name is "dns_freedns.sh"
  3. #So, here must be a method dns_freedns_add()
  4. #Which will be called by acme.sh to add the txt record to your api system.
  5. #returns 0 means success, otherwise error.
  6. #
  7. #Author: David Kerr
  8. #Report Bugs here: https://github.com/dkerr64/acme.sh
  9. #or here... https://github.com/Neilpang/acme.sh/issues/2305
  10. #
  11. ######## Public functions #####################
  12. # Export FreeDNS userid and password in following variables...
  13. # FREEDNS_User=username
  14. # FREEDNS_Password=password
  15. # login cookie is saved in acme account config file so userid / pw
  16. # need to be set only when changed.
  17. #Usage: dns_freedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
  18. dns_freedns_add() {
  19. fulldomain="$1"
  20. txtvalue="$2"
  21. _info "Add TXT record using FreeDNS"
  22. _debug "fulldomain: $fulldomain"
  23. _debug "txtvalue: $txtvalue"
  24. if [ -z "$FREEDNS_User" ] || [ -z "$FREEDNS_Password" ]; then
  25. FREEDNS_User=""
  26. FREEDNS_Password=""
  27. if [ -z "$FREEDNS_COOKIE" ]; then
  28. _err "You did not specify the FreeDNS username and password yet."
  29. _err "Please export as FREEDNS_User / FREEDNS_Password and try again."
  30. return 1
  31. fi
  32. using_cached_cookies="true"
  33. else
  34. FREEDNS_COOKIE="$(_freedns_login "$FREEDNS_User" "$FREEDNS_Password")"
  35. if [ -z "$FREEDNS_COOKIE" ]; then
  36. return 1
  37. fi
  38. using_cached_cookies="false"
  39. fi
  40. _debug "FreeDNS login cookies: $FREEDNS_COOKIE (cached = $using_cached_cookies)"
  41. _saveaccountconf FREEDNS_COOKIE "$FREEDNS_COOKIE"
  42. # We may have to cycle through the domain name to find the
  43. # TLD that we own...
  44. i=1
  45. wmax="$(echo "$fulldomain" | tr '.' ' ' | wc -w)"
  46. while [ "$i" -lt "$wmax" ]; do
  47. # split our full domain name into two parts...
  48. sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")"
  49. i="$(_math "$i" + 1)"
  50. top_domain="$(echo "$fulldomain" | cut -d. -f "$i"-100)"
  51. _debug "sub_domain: $sub_domain"
  52. _debug "top_domain: $top_domain"
  53. DNSdomainid="$(_freedns_domain_id "$top_domain")"
  54. if [ "$?" = "0" ]; then
  55. _info "Domain $top_domain found at FreeDNS, domain_id $DNSdomainid"
  56. break
  57. else
  58. _info "Domain $top_domain not found at FreeDNS, try with next level of TLD"
  59. fi
  60. done
  61. if [ -z "$DNSdomainid" ]; then
  62. # If domain ID is empty then something went wrong (top level
  63. # domain not found at FreeDNS).
  64. _err "Domain $top_domain not found at FreeDNS"
  65. return 1
  66. fi
  67. # Add in new TXT record with the value provided
  68. _debug "Adding TXT record for $fulldomain, $txtvalue"
  69. _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue"
  70. return $?
  71. }
  72. #Usage: fulldomain txtvalue
  73. #Remove the txt record after validation.
  74. dns_freedns_rm() {
  75. fulldomain="$1"
  76. txtvalue="$2"
  77. _info "Delete TXT record using FreeDNS"
  78. _debug "fulldomain: $fulldomain"
  79. _debug "txtvalue: $txtvalue"
  80. # Need to read cookie from conf file again in case new value set
  81. # during login to FreeDNS when TXT record was created.
  82. FREEDNS_COOKIE="$(_readaccountconf "FREEDNS_COOKIE")"
  83. _debug "FreeDNS login cookies: $FREEDNS_COOKIE"
  84. TXTdataid="$(_freedns_data_id "$fulldomain" "TXT")"
  85. if [ "$?" != "0" ]; then
  86. _info "Cannot delete TXT record for $fulldomain, record does not exist at FreeDNS"
  87. return 1
  88. fi
  89. _debug "Data ID's found, $TXTdataid"
  90. # now we have one (or more) TXT record data ID's. Load the page
  91. # for that record and search for the record txt value. If match
  92. # then we can delete it.
  93. lines="$(echo "$TXTdataid" | wc -l)"
  94. _debug "Found $lines TXT data records for $fulldomain"
  95. i=0
  96. while [ "$i" -lt "$lines" ]; do
  97. i="$(_math "$i" + 1)"
  98. dataid="$(echo "$TXTdataid" | sed -n "${i}p")"
  99. _debug "$dataid"
  100. htmlpage="$(_freedns_retrieve_data_page "$FREEDNS_COOKIE" "$dataid")"
  101. if [ "$?" != "0" ]; then
  102. if [ "$using_cached_cookies" = "true" ]; then
  103. _err "Has your FreeDNS username and password changed? If so..."
  104. _err "Please export as FREEDNS_User / FREEDNS_Password and try again."
  105. fi
  106. return 1
  107. fi
  108. echo "$htmlpage" | grep "value=\""$txtvalue"\"" >/dev/null
  109. if [ "$?" = "0" ]; then
  110. # Found a match... delete the record and return
  111. _info "Deleting TXT record for $fulldomain, $txtvalue"
  112. _freedns_delete_txt_record "$FREEDNS_COOKIE" "$dataid"
  113. return $?
  114. fi
  115. done
  116. # If we get this far we did not find a match
  117. # Not necessarily an error, but log anyway.
  118. _info "Cannot delete TXT record for $fulldomain, $txtvalue. Does not exist at FreeDNS"
  119. return 0
  120. }
  121. #################### Private functions below ##################################
  122. # usage: _freedns_login username password
  123. # print string "cookie=value" etc.
  124. # returns 0 success
  125. _freedns_login() {
  126. export _H1="Accept-Language:en-US"
  127. username="$1"
  128. password="$2"
  129. url="https://freedns.afraid.org/zc.php?step=2"
  130. _debug "Login to FreeDNS as user $username"
  131. htmlpage="$(_post "username=$(printf '%s' "$username" | _url_encode)&password=$(printf '%s' "$password" | _url_encode)&submit=Login&action=auth" "$url")"
  132. if [ "$?" != "0" ]; then
  133. _err "FreeDNS login failed for user $username bad RC from _post"
  134. return 1
  135. fi
  136. cookies="$(grep -i '^Set-Cookie.*dns_cookie.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
  137. # if cookies is not empty then logon successful
  138. if [ -z "$cookies" ]; then
  139. _debug3 "htmlpage: $htmlpage"
  140. _err "FreeDNS login failed for user $username. Check $HTTP_HEADER file"
  141. return 1
  142. fi
  143. printf "%s" "$cookies"
  144. return 0
  145. }
  146. # usage _freedns_retrieve_subdomain_page login_cookies
  147. # echo page retrieved (html)
  148. # returns 0 success
  149. _freedns_retrieve_subdomain_page() {
  150. export _H1="Cookie:$1"
  151. export _H2="Accept-Language:en-US"
  152. url="https://freedns.afraid.org/subdomain/"
  153. _debug "Retrieve subdomain page from FreeDNS"
  154. htmlpage="$(_get "$url")"
  155. if [ "$?" != "0" ]; then
  156. _err "FreeDNS retrieve subdomains failed bad RC from _get"
  157. return 1
  158. elif [ -z "$htmlpage" ]; then
  159. _err "FreeDNS returned empty subdomain page"
  160. return 1
  161. fi
  162. _debug3 "htmlpage: $htmlpage"
  163. printf "%s" "$htmlpage"
  164. return 0
  165. }
  166. # usage _freedns_retrieve_data_page login_cookies data_id
  167. # echo page retrieved (html)
  168. # returns 0 success
  169. _freedns_retrieve_data_page() {
  170. export _H1="Cookie:$1"
  171. export _H2="Accept-Language:en-US"
  172. data_id="$2"
  173. url="https://freedns.afraid.org/subdomain/edit.php?data_id=$2"
  174. _debug "Retrieve data page for ID $data_id from FreeDNS"
  175. htmlpage="$(_get "$url")"
  176. if [ "$?" != "0" ]; then
  177. _err "FreeDNS retrieve data page failed bad RC from _get"
  178. return 1
  179. elif [ -z "$htmlpage" ]; then
  180. _err "FreeDNS returned empty data page"
  181. return 1
  182. fi
  183. _debug3 "htmlpage: $htmlpage"
  184. printf "%s" "$htmlpage"
  185. return 0
  186. }
  187. # usage _freedns_add_txt_record login_cookies domain_id subdomain value
  188. # returns 0 success
  189. _freedns_add_txt_record() {
  190. export _H1="Cookie:$1"
  191. export _H2="Accept-Language:en-US"
  192. domain_id="$2"
  193. subdomain="$3"
  194. value="$(printf '%s' "$4" | _url_encode)"
  195. url="https://freedns.afraid.org/subdomain/save.php?step=2"
  196. htmlpage="$(_post "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")"
  197. if [ "$?" != "0" ]; then
  198. _err "FreeDNS failed to add TXT record for $subdomain bad RC from _post"
  199. return 1
  200. elif ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then
  201. _debug3 "htmlpage: $htmlpage"
  202. _err "FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file"
  203. return 1
  204. elif _contains "$htmlpage" "security code was incorrect"; then
  205. _debug3 "htmlpage: $htmlpage"
  206. _err "FreeDNS failed to add TXT record for $subdomain as FreeDNS requested security code"
  207. _err "Note that you cannot use automatic DNS validation for FreeDNS public domains"
  208. return 1
  209. fi
  210. _debug3 "htmlpage: $htmlpage"
  211. _info "Added acme challenge TXT record for $fulldomain at FreeDNS"
  212. return 0
  213. }
  214. # usage _freedns_delete_txt_record login_cookies data_id
  215. # returns 0 success
  216. _freedns_delete_txt_record() {
  217. export _H1="Cookie:$1"
  218. export _H2="Accept-Language:en-US"
  219. data_id="$2"
  220. url="https://freedns.afraid.org/subdomain/delete2.php"
  221. htmlheader="$(_get "$url?data_id%5B%5D=$data_id&submit=delete+selected" "onlyheader")"
  222. if [ "$?" != "0" ]; then
  223. _err "FreeDNS failed to delete TXT record for $data_id bad RC from _get"
  224. return 1
  225. elif ! _contains "$htmlheader" "200 OK"; then
  226. _debug2 "htmlheader: $htmlheader"
  227. _err "FreeDNS failed to delete TXT record $data_id"
  228. return 1
  229. fi
  230. _info "Deleted acme challenge TXT record for $fulldomain at FreeDNS"
  231. return 0
  232. }
  233. # usage _freedns_domain_id domain_name
  234. # echo the domain_id if found
  235. # return 0 success
  236. _freedns_domain_id() {
  237. # Start by escaping the dots in the domain name
  238. search_domain="$(echo "$1" | sed 's/\./\\./g')"
  239. # Sometimes FreeDNS does not return the subdomain page but rather
  240. # returns a page regarding becoming a premium member. This usually
  241. # happens after a period of inactivity. Immediately trying again
  242. # returns the correct subdomain page. So, we will try twice to
  243. # load the page and obtain our domain ID
  244. attempts=2
  245. while [ "$attempts" -gt "0" ]; do
  246. attempts="$(_math "$attempts" - 1)"
  247. htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")"
  248. if [ "$?" != "0" ]; then
  249. if [ "$using_cached_cookies" = "true" ]; then
  250. _err "Has your FreeDNS username and password changed? If so..."
  251. _err "Please export as FREEDNS_User / FREEDNS_Password and try again."
  252. fi
  253. return 1
  254. fi
  255. domain_id="$(echo "$htmlpage" | tr -d "[:space:]" | sed 's/<tr>/@<tr>/g' | tr '@' '\n' \
  256. | grep "<td>$search_domain</td>\|<td>$search_domain(.*)</td>" \
  257. | _egrep_o "edit\.php\?edit_domain_id=[0-9a-zA-Z]+" \
  258. | cut -d = -f 2)"
  259. # The above beauty extracts domain ID from the html page...
  260. # strip out all blank space and new lines. Then insert newlines
  261. # before each table row <tr>
  262. # search for the domain within each row (which may or may not have
  263. # a text string in brackets (.*) after it.
  264. # And finally extract the domain ID.
  265. if [ -n "$domain_id" ]; then
  266. printf "%s" "$domain_id"
  267. return 0
  268. fi
  269. _debug "Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)"
  270. done
  271. _debug "Domain $search_domain not found after retry"
  272. return 1
  273. }
  274. # usage _freedns_data_id domain_name record_type
  275. # echo the data_id(s) if found
  276. # return 0 success
  277. _freedns_data_id() {
  278. # Start by escaping the dots in the domain name
  279. search_domain="$(echo "$1" | sed 's/\./\\./g')"
  280. record_type="$2"
  281. # Sometimes FreeDNS does not return the subdomain page but rather
  282. # returns a page regarding becoming a premium member. This usually
  283. # happens after a period of inactivity. Immediately trying again
  284. # returns the correct subdomain page. So, we will try twice to
  285. # load the page and obtain our domain ID
  286. attempts=2
  287. while [ "$attempts" -gt "0" ]; do
  288. attempts="$(_math "$attempts" - 1)"
  289. htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")"
  290. if [ "$?" != "0" ]; then
  291. if [ "$using_cached_cookies" = "true" ]; then
  292. _err "Has your FreeDNS username and password changed? If so..."
  293. _err "Please export as FREEDNS_User / FREEDNS_Password and try again."
  294. fi
  295. return 1
  296. fi
  297. data_id="$(echo "$htmlpage" | tr -d "[:space:]" | sed 's/<tr>/@<tr>/g' | tr '@' '\n' \
  298. | grep "<td[a-zA-Z=#]*>$record_type</td>" \
  299. | grep "<ahref.*>$search_domain</a>" \
  300. | _egrep_o "edit\.php\?data_id=[0-9a-zA-Z]+" \
  301. | cut -d = -f 2)"
  302. # The above beauty extracts data ID from the html page...
  303. # strip out all blank space and new lines. Then insert newlines
  304. # before each table row <tr>
  305. # search for the record type withing each row (e.g. TXT)
  306. # search for the domain within each row (which is within a <a..>
  307. # </a> anchor. And finally extract the domain ID.
  308. if [ -n "$data_id" ]; then
  309. printf "%s" "$data_id"
  310. return 0
  311. fi
  312. _debug "Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)"
  313. done
  314. _debug "Domain $search_domain not found after retry"
  315. return 1
  316. }