├── .gitingore ├── README └── dnspodsh.sh /.gitingore: -------------------------------------------------------------------------------- 1 | 2 | 3 | # backups 4 | *~ 5 | # python 6 | *.pyc 7 | 8 | # Mac 9 | .DS_Store 10 | 11 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | 基于dnspod api构架的bash ddns客户端,实现DDNS功能(动态域名解析) 3 | 4 | 这个脚本是我在dnspod的页面上找到的, 5 | 我在原脚本的基础上加上了两个小功能 6 | 1.支持token登录。DNSPOD官方推荐的方法,避免在脚本中直接使用账户和密码。 7 | 2.支持从本地获取ip地址。因为我的宽带商使用了缓存服务器,所以从网上获取ip地址的话得到的是只是缓存服务器的地址,所以只能从本地获取ip。 8 | 9 | ##该脚本只支持bash 10 | 我在自己的路由器(优酷路由宝,pandorabox)里执行时发现脚本会报错。 11 | 找了好久才发现是因为pandorabox自带的是ash,所以自己安装了一个bash shell,才解决报错的问题。 12 | 13 | 这个脚本原作者是zrong(zengrong.net),他写了一篇不错的文档来介绍脚本的使用方法,地址是:http://zengrong.net/post/1524.htm 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /dnspodsh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################## 4 | # dnspodsh v0.4 5 | # 基于dnspod api构架的bash ddns客户端 6 | # 修改者:guisu2010@gmail.com 7 | # 原作者:zrong(zengrong.net) 8 | # 详细介绍:http://zengrong.net/post/1524.htm 9 | # 创建日期:2012-02-13 10 | # 更新日期:2015-05-15 11 | ############################## 12 | 13 | login_token="" 14 | login_email='' 15 | login_password='' 16 | format="json" 17 | lang="cn" 18 | userAgent="dnspodsh/0.4(guisu2010@gmail.com)" 19 | if [ -n $login_token ];then 20 | commonPost="login_token=$login_token&format=$format&lang=$lang" 21 | else 22 | commonPost="login_email=$login_email&login_password=$login_password&format=$format&lang=$lang" 23 | fi 24 | 25 | apiUrl='https://dnsapi.cn/' 26 | ipUrl='http://members.3322.org/dyndns/getip' 27 | 28 | # 要处理的域名数组,每个元素代表一个域名的一组记录 29 | # 在数组的一个元素中,以空格分隔域名和子域名 30 | # 第一个空格前为主域名,后面用空格分离多个子域名 31 | # 如果使用泛域名,必须用\*转义 32 | #domainList[0]='domain1.com \* @ www' 33 | #domainList[1]='domain2.com subdomain subdomain2' 34 | 35 | # 这里是只修改一个子域名的例子 36 | domainList[0]='example.com subdomain' 37 | 38 | # 多长时间比较一次ip地址 39 | delay=300 40 | 41 | # logfile 42 | logDir='/var/log' 43 | logFile=$logDir'/dnspodsh.log' 44 | traceFile=$logDir'/dnspodshtrace.log' 45 | 46 | # 检测ip地址是否符合要求 47 | checkip() 48 | { 49 | # ipv4地址 50 | if [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]];then 51 | return 0 52 | # ipv6地址 53 | elif [[ "$1" =~ ^([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}$|^:((:[\da-fA-F]{1,4}){1,6}|:)$|^[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,5}|:)$|^([\da-fA-F]{1,4}:){2}((:[\da-fA-F]{1,4}){1,4}|:)$|^([\da-fA-F]{1,4}:){3}((:[\da-fA-F]{1,4}){1,3}|:)$|^([\da-fA-F]{1,4}:){4}((:[\da-fA-F]{1,4}){1,2}|:)$|^([\da-fA-F]{1,4}:){5}:([\da-fA-F]{1,4})?$|^([\da-fA-F]{1,4}:){6}:$ ]];then 54 | return 0 55 | fi 56 | return 1 57 | } 58 | 59 | getUrl() 60 | { 61 | #curl -s -A $userAgent -d $commonPost$2 --trace $traceFile $apiUrl$1 62 | curl -s -A $userAgent -d $commonPost$2 $apiUrl$1 63 | } 64 | 65 | getVersion() 66 | { 67 | getUrl "Info.Version" 68 | } 69 | 70 | getUserDetail() 71 | { 72 | getUrl "User.Detail" 73 | } 74 | 75 | writeLog() 76 | { 77 | if [ -w $logDir ];then 78 | local pre=`date` 79 | for arg in $@;do 80 | pre=$pre'\t'$arg 81 | done 82 | echo -e $pre>>$logFile 83 | fi 84 | echo -e $1 85 | } 86 | 87 | getDomainList() 88 | { 89 | getUrl "Domain.List" "&type=all&offset=0&length=10" 90 | } 91 | 92 | # 根据域名id获取记录列表 93 | # $1 域名id 94 | getRecordList() 95 | { 96 | getUrl "Record.List" "&domain_id=$1&offset=0&length=20" 97 | } 98 | 99 | # 设置记录 100 | setRecord() 101 | { 102 | writeLog "set domain $3.$8 to new ip:$7" 103 | local subDomain=$3 104 | # 由于*会被扩展,在最后一步将转义的\*替换成* 105 | if [ "$subDomain" = '\*' ];then 106 | subDomain='*' 107 | fi 108 | local request="&domain_id=$1&record_id=$2&sub_domain=$subDomain&record_type=$4&record_line=$5&ttl=$6&value=$7" 109 | #echo $request 110 | local saveResult=$(getUrl 'Record.Modify' "$request") 111 | # 检测返回是否正常,但即使不正常也不退出程序 112 | if checkStatusCode "$saveResult" 0;then 113 | writeLog "set record $3.$8 success." 114 | fi 115 | #getUrl 'Record.Modify' "&domain_id=$domainid&record_id=$recordid&sub_domain=$recordName&record_type=$recordtype&record_line=$recordline&ttl=$recordttl&value=$newip" 116 | } 117 | 118 | # 设置一批记录 119 | setRecords() 120 | { 121 | numRecord=${#changedRecords[@]} 122 | for (( i=0; i < $numRecord; i++ ));do 123 | setRecord ${changedRecords[$i]} 124 | done 125 | # 删除待处理的变量 126 | unset changeRecords 127 | } 128 | 129 | # 通过key得到找到一个JSON对象字符串中的值 130 | getDataByKey() 131 | { 132 | local s='s/{[^}]*"'$2'":["]*\('$(getRegexp $2)'\)["]*[^}]*}/\1/' 133 | #echo '拼合成的regexp:'$s 134 | echo $1|sed $s 135 | } 136 | 137 | # 根据key返回要获取的正则表达式 138 | getRegexp() 139 | { 140 | case $1 in 141 | 'value') echo '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}';; 142 | 'type') echo '[A-Z]\+';; 143 | 'name') echo '[-_.A-Za-z*]\+';; 144 | 'ttl'|'id') echo '[0-9]\+';; 145 | 'line') echo '[^"]\+';; 146 | esac 147 | } 148 | 149 | # 通过一个JSON key名称,获取一个{}包围的JSON对象字符串 150 | # $1 要搜索的key名称 151 | # $2 要搜索的对应值 152 | getJSONObjByKey() 153 | { 154 | grep -o '{[^}{]*"'$1'":"'$2'"[^}]*}' 155 | } 156 | 157 | # 获取A记录类型的域名信息 158 | # 对于其它记录,同样的名称可以对应多条记录,因此使用getJSONObjByKey可能获取不到需要的数据 159 | getJSONObjByARecord() 160 | { 161 | grep -o '{[^}{]*"name":"'$1'"[^}]*"type":"A"[^}]*}' 162 | } 163 | 164 | # 获取返回代码是否正确 165 | # $1 要检测的字符串,该字符串包含{status:{code:1}}形式,代表DNSPodAPI返回正确 166 | # $2 是否要停止程序,因为dnspod在代码错误过多的情况下会封禁账号 167 | checkStatusCode() 168 | { 169 | if [[ "$1" =~ \{\"status\":\{[^}{]*\"code\":\"1\"[^}]*\} ]];then 170 | return 0 171 | fi 172 | writeLog "DNSPOD return error:$1" 173 | # 根据参数需求退出程序 174 | if [ -n "$2" ] && [ "$2" -eq 1 ];then 175 | writeLog 'exit dnspodsh' 176 | exit 1 177 | fi 178 | } 179 | 180 | # 获取与当前ip不同的,要更新的记录的数组 181 | getChangedRecords() 182 | { 183 | # 从DNSPod获取最新的域名列表 184 | local domainListInfo=$(getDomainList) 185 | if [ -z "$domainListInfo" ];then 186 | writeLog 'DNSPOD tell me domain list is null,waiting...' 187 | return 1 188 | fi 189 | checkStatusCode "$domainListInfo" 1 190 | 191 | # 主域名的id 192 | local domainid 193 | local domainName 194 | # 主域名的JSON信息 195 | local domainInfo 196 | # 主域名的所有记录列表 197 | local recordList 198 | # 一条记录的JSON信息 199 | local recordInfo 200 | # 记录的id 201 | local recordid 202 | local recordName 203 | # 记录的TTL 204 | local recordTtl 205 | # 记录的类型 206 | local recordType 207 | # 记录的线路 208 | local recordLine 209 | local j 210 | 211 | # 用于记录被改变的记录 212 | unset changedRecords 213 | 214 | local numDomain=${#domainList[@]} 215 | local domainGroup 216 | 217 | for ((i=0;i<$numDomain;i++));do 218 | domainGroup=${domainList[$i]} 219 | j=0 220 | for domain in ${domainGroup[@]};do 221 | # 列表的第一个项目,是主域名 222 | if ((j==0));then 223 | domainName=$domain 224 | domainInfo=$(echo $domainListInfo|getJSONObjByKey 'name' $domainName) 225 | domainid=$(getDataByKey "$domainInfo" 'id') 226 | recordList=$(getRecordList $domainid) 227 | if [ -z "$recordList" ];then 228 | writeLog 'DNSPOD tell me record list null,waiting...' 229 | return 1 230 | fi 231 | checkStatusCode "$recordList" 1 232 | else 233 | # 从dnspod获取要设置的子域名记录的信息 234 | recordInfo=$(echo $recordList|getJSONObjByARecord $domain) 235 | # 如果取不到记录,则不处理 236 | if [ -z "$recordInfo" ];then 237 | continue 238 | fi 239 | 240 | # 从dnspod获取要设置的子域名的ip 241 | oldip=$(getDataByKey "$recordInfo" 'value') 242 | 243 | # 检测获取到的旧ip地址是否符合ip规则 244 | if ! checkip "$oldip";then 245 | writeLog 'get old ip error!it is "$oldid".waiting...' 246 | continue 247 | fi 248 | 249 | if [ "$newip" != "$oldip" ];then 250 | recordid=$(getDataByKey "$recordInfo" 'id') 251 | recordName=$(getDataByKey "$recordInfo" 'name') 252 | recordTtl=$(getDataByKey "$recordInfo" 'ttl') 253 | recordType=$(getDataByKey "$recordInfo" 'type') 254 | # 由于从服务器获取的线路是utf编码,目前无法知道如何转换成中文,因此在这里写死。dnspod中免费用户的默认线路的名称就是“默认” 255 | #recordLine=$(getDataByKey "$recordInfo" 'line') 256 | recordLine='默认' 257 | # 判断取值是否正常,如果值为空就不处理 258 | if [ -n "$recordid" ] && [ -n "$recordTtl" ] && [ -n "$recordType" ]; then 259 | # 使用数组记录需要修改的子域名的所有值 260 | # 这里一共有8个参数,与setRecord中的参数对应 261 | changedRecords[${#changedRecords[@]}]="$domainid $recordid $domain $recordType $recordLine $recordTtl $newip $domainName" 262 | fi 263 | fi 264 | fi 265 | j=$((j+1)) 266 | done 267 | done 268 | } 269 | 270 | # 执行检测工作 271 | go() 272 | { 273 | # 由于获取到的数据多了一些多余的字符,所以提取ip地址的部分 274 | # 从api中获取当前的外网ip 275 | # newip=$(curl -s $ipUrl|grep -o $(getRegexp 'value')) 276 | newip=`/sbin/ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v 192.168|grep -v inet6|awk '{print $2}'|tr -d "addr:"` 277 | # 如果获取最新ip错误,就继续等待下一次取值 278 | if ! checkip "$newip";then 279 | writeLog 'can not get new ip,waiting...' 280 | sleep $delay 281 | continue 282 | fi 283 | echo 'wan ip:'$newip 284 | echo $commonPost 285 | # 获取需要修改的记录 286 | getChangedRecords 287 | if (( ${#changedRecords[@]} > 0 ));then 288 | writeLog "ip is changed,new ip is:$newip" 289 | setRecords 290 | fi 291 | } 292 | 293 | while [ 1 ];do 294 | go 295 | sleep $delay 296 | done 297 | --------------------------------------------------------------------------------