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.

370 lines
12 KiB

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