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.

394 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/Neilpang/acme.sh
  9. #
  10. ######## Public functions #####################
  11. # Requires FreeDNS userid and password in folowing variables...
  12. # FREEDNS_USER=username
  13. # FREEDNS_PASSWORD=password
  14. #Usage: dns_freedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
  15. dns_freedns_add() {
  16. fulldomain=$1
  17. txtvalue=$2
  18. _info "Add TXT record using FreeDNS"
  19. _debug "fulldomain: $fulldomain"
  20. _debug "txtvalue: $txtvalue"
  21. if [ -z "$FREEDNS_USER" ] || [ -z "$FREEDNS_PASSWORD" ]; then
  22. AD_API_KEY=""
  23. _err "You didn't specify the FreeDNS username and password yet."
  24. _err "Please export as FREEDNS_USER / FREEDNS_PASSWORD and try again."
  25. return 1
  26. fi
  27. login_cookies="$(_freedns_login $FREEDNS_USER $FREEDNS_PASSWORD)"
  28. if [ -z "$login_cookies" ]; then
  29. return 1
  30. fi
  31. _saveaccountconf FREEDNS_USER "$FREEDNS_USER"
  32. _saveaccountconf FREEDNS_PASSWORD "$FREEDNS_PASSWORD"
  33. htmlpage="$(_freedns_retrieve_subdomain_page $login_cookies)"
  34. if [ $? != 0 ]; then
  35. return $?
  36. fi
  37. # split our full domain name into two parts...
  38. top_domain="$(echo $fulldomain | rev | cut -d. -f -2 | rev)"
  39. sub_domain="$(echo $fulldomain | rev | cut -d. -f 3- | rev)"
  40. # Now convert the tables in the HTML to CSV. This litte gem from
  41. # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv
  42. subdomain_csv="$(echo $htmlpage |
  43. grep -i -e '</\?TABLE\|</\?TD\|</\?TR\|</\?TH' |
  44. sed 's/^[\ \t]*//g' |
  45. tr -d '\n' |
  46. sed 's/<\/TR[^>]*>/\n/Ig' |
  47. sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' |
  48. sed 's/^<T[DH][^>]*>\|<\/\?T[DH][^>]*>$//Ig' |
  49. sed 's/<\/T[DH][^>]*><T[DH][^>]*>/,/Ig' |
  50. grep 'edit.php?' |
  51. grep $top_domain)"
  52. # The above beauty ends with striping out rows that do not have an
  53. # href to edit.php and do not have the top domain we are looking for.
  54. # So all we should be left with is CSV of table of subdomains we are
  55. # interested in.
  56. # Now we have to read through this table and extract the data we need
  57. IFS=$'\n'
  58. found=0
  59. for line in $subdomain_csv
  60. do
  61. tmp="$(echo $line | cut -d ',' -f 1)"
  62. if [ $found = 0 ] && _startswith "$tmp" "<td>$top_domain"; then
  63. # this line will contain DNSdomainid for the top_domain
  64. tmp="$(echo $line | cut -d ',' -f 2)"
  65. url=${tmp#*=}
  66. url=${url%%>*}
  67. DNSdomainid=${url#*domain_id=}
  68. found=1
  69. else
  70. # lines contain DNS records for all subdomains
  71. dns_href="$(echo $line | cut -d ',' -f 2)"
  72. tmp=${dns_href#*>}
  73. DNSname=${tmp%%<*}
  74. DNStype="$(echo $line | cut -d ',' -f 3)"
  75. if [ "$DNSname" = "$fulldomain" -a "$DNStype" = "TXT" ]; then
  76. tmp=${dns_href#*=}
  77. url=${tmp%%>*}
  78. DNSdataid=${url#*data_id=}
  79. # Now get current value for the TXT record. This method may
  80. # produce inaccurate results as the value field is truncated
  81. # on this webpage. To get full value we would need to load
  82. # another page. However we don't really need this so long as
  83. # there is only one TXT record for the acme chalenge subdomain.
  84. tmp="$(echo $line | cut -d ',' -f 4)"
  85. # strip the html double-quotes off the value
  86. tmp=${tmp#&quot;}
  87. DNSvalue=${tmp%&quot;}
  88. if [ $found != 0 ]; then
  89. break
  90. # we are breaking out of the loop at the first match of DNS name
  91. # and DNS type (if we are past finding the domainid). This assumes
  92. # that there is only ever one TXT record for the LetsEncrypt/acme
  93. # challenge subdomain. This seems to be a reasonable assumption
  94. # as the acme client deletes the TXT record on successful validation.
  95. fi
  96. else
  97. DNSname=""
  98. DNStype=""
  99. fi
  100. fi
  101. done
  102. unset IFS
  103. _debug "DNSname: $DNSname DNStype: $DNStype DNSdomainid: $DNSdomainid DNSdataid: $DNSdataid"
  104. _debug "DNSvalue: $DNSvalue"
  105. if [ -z "$DNSdomainid" ]; then
  106. # If domain ID is empty then something went wrong (top level
  107. # domain not found at FreeDNS). Cannot proceed.
  108. _debug2 "$htmlpage"
  109. _debug2 "$subdomain_csv"
  110. _err "Domain $top_domain not found at FreeDNS"
  111. return 1
  112. fi
  113. if [ -z "$DNSdataid" ]; then
  114. # If data ID is empty then specific subdomain does not exist yet, need
  115. # to create it this should always be the case as the acme client
  116. # deletes the entry after domain is validated.
  117. _freedns_add_txt_record $login_cookies $DNSdomainid $sub_domain "$txtvalue"
  118. return $?
  119. else
  120. if [ "$txtvalue" = "$DNSvalue" ]; then
  121. # if value in TXT record matches value requested then DNS record
  122. # does not need to be updated. But...
  123. # Testing value match fails. Website is truncating the value field.
  124. # So for now we will always go down the else path. Though in theory
  125. # should never come here anyway as the acme client deletes
  126. # the TXT record on successful validation, so we should not even
  127. # have found a TXT record !!
  128. _info "No update necessary for $fulldomain at FreeDNS"
  129. return 0
  130. else
  131. # Delete the old TXT record (with the wrong value)
  132. _freedns_delete_txt_record $login_cookies $DNSdataid
  133. if [ $? = 0 ]; then
  134. # And add in new TXT record with the value provided
  135. _freedns_add_txt_record $login_cookies $DNSdomainid $sub_domain "$txtvalue"
  136. fi
  137. return $?
  138. fi
  139. fi
  140. return 0
  141. }
  142. #Usage: fulldomain txtvalue
  143. #Remove the txt record after validation.
  144. dns_freedns_rm() {
  145. fulldomain=$1
  146. txtvalue=$2
  147. _info "Delete TXT record using FreeDNS"
  148. _debug "fulldomain: $fulldomain"
  149. _debug "txtvalue: $txtvalue"
  150. login_cookies="$(_freedns_login $FREEDNS_USER $FREEDNS_PASSWORD)"
  151. if [ -z "$login_cookies" ]; then
  152. return 1
  153. fi
  154. htmlpage="$(_freedns_retrieve_subdomain_page $login_cookies)"
  155. if [ $? != 0 ]; then
  156. return $?
  157. fi
  158. # Now convert the tables in the HTML to CSV. This litte gem from
  159. # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv
  160. subdomain_csv="$(echo $htmlpage |
  161. grep -i -e '</\?TABLE\|</\?TD\|</\?TR\|</\?TH' |
  162. sed 's/^[\ \t]*//g' |
  163. tr -d '\n' |
  164. sed 's/<\/TR[^>]*>/\n/Ig' |
  165. sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' |
  166. sed 's/^<T[DH][^>]*>\|<\/\?T[DH][^>]*>$//Ig' |
  167. sed 's/<\/T[DH][^>]*><T[DH][^>]*>/,/Ig' |
  168. grep 'edit.php?' |
  169. grep $fulldomain)"
  170. # The above beauty ends with striping out rows that do not have an
  171. # href to edit.php and do not have the domain name we are looking for.
  172. # So all we should be left with is CSV of table of subdomains we are
  173. # interested in.
  174. # Now we have to read through this table and extract the data we need
  175. IFS=$'\n'
  176. for line in $subdomain_csv
  177. do
  178. dns_href="$(echo $line | cut -d ',' -f 2)"
  179. tmp=${dns_href#*>}
  180. DNSname=${tmp%%<*}
  181. DNStype="$(echo $line | cut -d ',' -f 3)"
  182. if [ "$DNSname" = "$fulldomain" -a "$DNStype" = "TXT" ]; then
  183. tmp=${dns_href#*=}
  184. url=${tmp%%>*}
  185. DNSdataid=${url#*data_id=}
  186. tmp="$(echo $line | cut -d ',' -f 4)"
  187. # strip the html double-quotes off the value
  188. tmp=${tmp#&quot;}
  189. DNSvalue=${tmp%&quot;}
  190. _debug "DNSvalue: $DNSvalue"
  191. # if [ "$DNSvalue" = "$txtvalue" ]; then
  192. # Testing value match fails. Website is truncating the value
  193. # field. So for now we will assume that there is only one TXT
  194. # field for the sub domain and just delete it. Currently this
  195. # is a safe assumption.
  196. _freedns_delete_txt_record $login_cookies $DNSdataid
  197. unset IFS
  198. return $?
  199. # fi
  200. fi
  201. done
  202. unset IFS
  203. # If we get this far we did not find a match.
  204. # Not necessarily an error, but log anyway.
  205. _debug2 "$subdomain_csv"
  206. _info "Cannot delete TXT record for $fulldomain/$txtvalue. Does not exist at FreeDNS"
  207. return 0
  208. }
  209. #################### Private functions below ##################################
  210. # usage: _freedns_login username password
  211. # echos string "cookie:value;cookie:value" etc
  212. # returns 0 success
  213. _freedns_login() {
  214. username=$1
  215. password=$2
  216. url="https://freedns.afraid.org/zc.php?step=2"
  217. _debug "Login to FreeDNS as user $username"
  218. # Not using acme.sh _post() function because I need to capture the cookies.
  219. cookie_file="$(curl --silent \
  220. --user-agent "$USER_AGENT" \
  221. --data "username=$(_freedns_urlencode "$username")&password=$(_freedns_urlencode "$password")&submit=Login&action=auth" \
  222. --cookie-jar - \
  223. $url )"
  224. if [ $? != 0 ]; then
  225. _err "FreeDNS login failed for user $username bad RC from cURL: $?"
  226. return $?
  227. fi
  228. # convert from cookie file format to cookie string
  229. cookies=""
  230. found=0
  231. IFS=$'\n'
  232. for line in $cookie_file
  233. do
  234. # strip spaces from start and end of line
  235. line="$(echo "$line" | xargs)"
  236. if [ $found = 0 ]; then
  237. # first line, validate that it is a cookie file
  238. if _contains "$line" "Netscape HTTP Cookie File"; then
  239. found=1
  240. else
  241. _debug2 "$cookie_file"
  242. _err "FreeDNS login failed for user $username bad cookie file"
  243. unset IFS
  244. return 1
  245. fi
  246. else
  247. # after first line skip blank line or comments
  248. if [ -n "$line" -a "$(echo $line | cut -c 1)" != "#" ]; then
  249. if [ -n "$cookies" ]; then
  250. cookies="$cookies;"
  251. fi
  252. cookies="$cookies$(echo $line | cut -d ' ' -f 6)=$(echo $line | cut -d ' ' -f 7)"
  253. fi
  254. fi
  255. done
  256. unset IFS
  257. # if cookies is not empty then logon successful
  258. if [ -z "$cookies" ]; then
  259. _err "FreeDNS login failed for user $username"
  260. return 1
  261. fi
  262. _debug "FreeDNS login cookies: $cookies"
  263. echo "$cookies"
  264. return 0
  265. }
  266. # usage _freedns_retrieve_subdomain_page login_cookies
  267. # echo page retrieved (html)
  268. # returns 0 success
  269. _freedns_retrieve_subdomain_page() {
  270. cookies=$1
  271. url="https://freedns.afraid.org/subdomain/"
  272. _debug "Retrieve subdmoain page from FreeDNS"
  273. # Not using acme.sh _get() function becuase I need to pass in the cookies.
  274. htmlpage="$(curl --silent \
  275. --user-agent "$USER_AGENT" \
  276. --cookie "$cookies" \
  277. $url )"
  278. if [ $? != 0 ]; then
  279. _err "FreeDNS retrieve subdomins failed bad RC from cURL: $?"
  280. return $?
  281. fi
  282. if [ -z "$htmlpage" ]; then
  283. _err "FreeDNS returned empty subdomain page"
  284. return 1
  285. fi
  286. _debug2 "$htmlpage"
  287. echo "$htmlpage"
  288. return 0
  289. }
  290. _freedns_add_txt_record() {
  291. cookies=$1
  292. domain_id=$2
  293. subdomain=$3
  294. value="$(_freedns_urlencode "$4")"
  295. url="http://freedns.afraid.org/subdomain/save.php?step=2"
  296. # Not using acme.sh _get() function becuase I need to pass in the cookies.
  297. htmlpage="$(curl --silent \
  298. --user-agent "$USER_AGENT" \
  299. --cookie "$cookies" \
  300. --data "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" \
  301. $url )"
  302. if [ $? != 0 ]; then
  303. _err "FreeDNS failed to add TXT record for $subdomain bad RC from cURL: $?"
  304. return $?
  305. fi
  306. # returned page should be empty on success
  307. if [ -n "$htmlpage" ]; then
  308. _debug2 "$htmlpage"
  309. _err "FreeDNS failed to add TXT record for $subdomain"
  310. return 1
  311. fi
  312. _info "Added acme challenge TXT record for $fulldomain at FreeDNS"
  313. return 0
  314. }
  315. _freedns_delete_txt_record() {
  316. cookies=$1
  317. data_id=$2
  318. url="https://freedns.afraid.org/subdomain/delete2.php"
  319. # Not using acme.sh _get() function becuase I need to pass in the cookies.
  320. htmlpage="$(curl --silent \
  321. --user-agent "$USER_AGENT" \
  322. --cookie "$cookies" \
  323. "$url?data_id%5B%5D=$data_id&submit=delete+selected" )"
  324. if [ $? != 0 ]; then
  325. _err "FreeDNS failed to delete TXT record for $subdomain bad RC from cURL: $?"
  326. return $?
  327. fi
  328. # returned page should be empty on success
  329. if [ -n "$htmlpage" ]; then
  330. _debug2 "$htmlpage"
  331. _err "FreeDNS failed to delete TXT record $data_id"
  332. return 1
  333. fi
  334. _info "Deleted acme challenge TXT record for $fulldomain at FreeDNS"
  335. return 0
  336. }
  337. # urlencode magic from...
  338. # http://askubuntu.com/questions/53770/how-can-i-encode-and-decode-percent-encoded-strings-on-the-command-line
  339. # The _urlencode function in acme.sh does not work !
  340. _freedns_urlencode() {
  341. # urlencode <string>
  342. length="${#1}"
  343. for (( i = 0; i < length; i++ )); do
  344. c="${1:i:1}"
  345. case $c in
  346. [a-zA-Z0-9.~_-]) printf "$c" ;;
  347. *) printf '%%%02X' "'$c"
  348. esac
  349. done
  350. }