Skip to content

Instantly share code, notes, and snippets.

@zrong
Last active April 2, 2024 04:44
Show Gist options
  • Save zrong/1822396 to your computer and use it in GitHub Desktop.
Save zrong/1822396 to your computer and use it in GitHub Desktop.
在bash中使用DNSPod的API接口实现DDNS客户端
#!/bin/bash
##############################
# dnspodsh v0.3
# 基于dnspod api构架的bash ddns客户端
# 作者:zrong(zengrong.net)
# 详细介绍:http://zengrong.net/post/1524.htm
# 创建日期:2012-02-13
# 更新日期:2012-03-11
##############################
login_email=${1:?'必须提供登录名'}
login_password=${2:?'必须提供密码'}
format="json"
lang="en"
userAgent="dnspodsh/0.3([email protected])"
commonPost="login_email=$login_email&login_password=$login_password&format=$format&lang=$lang"
apiUrl='https://dnsapi.cn/'
ipUrl='http://members.3322.org/dyndns/getip'
# 要处理的域名数组,每个元素代表一个域名的一组记录
# 在数组的一个元素中,以空格分隔域名和子域名
# 第一个空格前为主域名,后面用空格分离多个子域名
# 如果使用泛域名,必须用\*转义
domainList[0]='domain1.com \* @ www'
domainList[1]='domain2.com subdomain subdomain2'
# 多长时间比较一次ip地址
delay=300
# logfile
logDir='/var/log'
logFile=$logDir'/dnspodsh.log'
traceFile=$logDir'/dnspodshtrace.log'
# 检测ip地址是否符合要求
checkip()
{
# ipv4地址
if [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]];then
return 0
# ipv6地址
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
return 0
fi
return 1
}
getUrl()
{
#curl -s -A $userAgent -d $commonPost$2 --trace $traceFile $apiUrl$1
curl -s -A $userAgent -d $commonPost$2 $apiUrl$1
}
getVersion()
{
getUrl "Info.Version"
}
getUserDetail()
{
getUrl "User.Detail"
}
writeLog()
{
if [ -w $logDir ];then
local pre=`date`
for arg in $@;do
pre=$pre'\t'$arg
done
echo -e $pre>>$logFile
fi
echo -e $1
}
getDomainList()
{
getUrl "Domain.List" "&type=all&offset=0&length=10"
}
# 根据域名id获取记录列表
# $1 域名id
getRecordList()
{
getUrl "Record.List" "&domain_id=$1&offset=0&length=20"
}
# 设置记录
setRecord()
{
writeLog "set domain $3.$8 to new ip:$7"
local subDomain=$3
# 由于*会被扩展,在最后一步将转义的\*替换成*
if [ "$subDomain" = '\*' ];then
subDomain='*'
fi
local request="&domain_id=$1&record_id=$2&sub_domain=$subDomain&record_type=$4&record_line=$5&ttl=$6&value=$7"
#echo $request
local saveResult=$(getUrl 'Record.Modify' "$request")
# 检测返回是否正常,但即使不正常也不退出程序
if checkStatusCode "$saveResult" 0;then
writeLog "set record $3.$8 success."
fi
#getUrl 'Record.Modify' "&domain_id=$domainid&record_id=$recordid&sub_domain=$recordName&record_type=$recordtype&record_line=$recordline&ttl=$recordttl&value=$newip"
}
# 设置一批记录
setRecords()
{
numRecord=${#changedRecords[@]}
for (( i=0; i < $numRecord; i++ ));do
setRecord ${changedRecords[$i]}
done
# 删除待处理的变量
unset changeRecords
}
# 通过key得到找到一个JSON对象字符串中的值
getDataByKey()
{
local s='s/{[^}]*"'$2'":["]*\('$(getRegexp $2)'\)["]*[^}]*}/\1/'
#echo '拼合成的regexp:'$s
echo $1|sed $s
}
# 根据key返回要获取的正则表达式
getRegexp()
{
case $1 in
'value') echo '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}';;
'type') echo '[A-Z]\+';;
'name') echo '[-_.A-Za-z*]\+';;
'ttl'|'id') echo '[0-9]\+';;
'line') echo '[^"]\+';;
esac
}
# 通过一个JSON key名称,获取一个{}包围的JSON对象字符串
# $1 要搜索的key名称
# $2 要搜索的对应值
getJSONObjByKey()
{
grep -o '{[^}{]*"'$1'":"'$2'"[^}]*}'
}
# 获取A记录类型的域名信息
# 对于其它记录,同样的名称可以对应多条记录,因此使用getJSONObjByKey可能获取不到需要的数据
getJSONObjByARecord()
{
grep -o '{[^}{]*"name":"'$1'"[^}]*"type":"A"[^}]*}'
}
# 获取返回代码是否正确
# $1 要检测的字符串,该字符串包含{status:{code:1}}形式,代表DNSPodAPI返回正确
# $2 是否要停止程序,因为dnspod在代码错误过多的情况下会封禁账号
checkStatusCode()
{
if [[ "$1" =~ \{\"status\":\{[^}{]*\"code\":\"1\"[^}]*\} ]];then
return 0
fi
writeLog "DNSPOD return error:$1"
# 根据参数需求退出程序
if [ -n "$2" ] && [ "$2" -eq 1 ];then
writeLog 'exit dnspodsh'
exit 1
fi
}
# 获取与当前ip不同的,要更新的记录的数组
getChangedRecords()
{
# 从DNSPod获取最新的域名列表
local domainListInfo=$(getDomainList)
if [ -z "$domainListInfo" ];then
writeLog 'DNSPOD tell me domain list is null,waiting...'
return 1
fi
checkStatusCode "$domainListInfo" 1
# 主域名的id
local domainid
local domainName
# 主域名的JSON信息
local domainInfo
# 主域名的所有记录列表
local recordList
# 一条记录的JSON信息
local recordInfo
# 记录的id
local recordid
local recordName
# 记录的TTL
local recordTtl
# 记录的类型
local recordType
# 记录的线路
local recordLine
local j
# 用于记录被改变的记录
unset changedRecords
local numDomain=${#domainList[@]}
local domainGroup
for ((i=0;i<$numDomain;i++));do
domainGroup=${domainList[$i]}
j=0
for domain in ${domainGroup[@]};do
# 列表的第一个项目,是主域名
if ((j==0));then
domainName=$domain
domainInfo=$(echo $domainListInfo|getJSONObjByKey 'name' $domainName)
domainid=$(getDataByKey "$domainInfo" 'id')
recordList=$(getRecordList $domainid)
if [ -z "$recordList" ];then
writeLog 'DNSPOD tell me record list null,waiting...'
return 1
fi
checkStatusCode "$recordList" 1
else
# 从dnspod获取要设置的子域名记录的信息
recordInfo=$(echo $recordList|getJSONObjByARecord $domain)
# 如果取不到记录,则不处理
if [ -z "$recordInfo" ];then
continue
fi
# 从dnspod获取要设置的子域名的ip
oldip=$(getDataByKey "$recordInfo" 'value')
# 检测获取到的旧ip地址是否符合ip规则
if ! checkip "$oldip";then
writeLog 'get old ip error!it is "$oldid".waiting...'
continue
fi
if [ "$newip" != "$oldip" ];then
recordid=$(getDataByKey "$recordInfo" 'id')
recordName=$(getDataByKey "$recordInfo" 'name')
recordTtl=$(getDataByKey "$recordInfo" 'ttl')
recordType=$(getDataByKey "$recordInfo" 'type')
# 由于从服务器获取的线路是utf编码,目前无法知道如何转换成中文,因此在这里写死。dnspod中免费用户的默认线路的名称就是“默认”
#recordLine=$(getDataByKey "$recordInfo" 'line')
recordLine='默认'
# 判断取值是否正常,如果值为空就不处理
if [ -n "$recordid" ] && [ -n "$recordTtl" ] && [ -n "$recordType" ]; then
# 使用数组记录需要修改的子域名的所有值
# 这里一共有8个参数,与setRecord中的参数对应
changedRecords[${#changedRecords[@]}]="$domainid $recordid $domain $recordType $recordLine $recordTtl $newip $domainName"
fi
fi
fi
j=$((j+1))
done
done
}
# 执行检测工作
go()
{
# 由于获取到的数据多了一些多余的字符,所以提取ip地址的部分
# 从api中获取当前的外网ip
newip=$(curl -s $ipUrl|grep -o $(getRegexp 'value'))
# 如果获取最新ip错误,就继续等待下一次取值
if ! checkip "$newip";then
writeLog 'can not get new ip,waiting...'
sleep $delay
continue
fi
# 获取需要修改的记录
getChangedRecords
if (( ${#changedRecords[@]} > 0 ));then
writeLog "ip is changed,new ip is:$newip"
setRecords
fi
}
while [ 1 ];do
go
sleep $delay
done
@zrong
Copy link
Author

zrong commented Mar 11, 2012

2012-03-12更新

  • 判断DNSPOD API返回值为空的情况,这种情况每天会出现多次,这种情况不退出程序,而是继续运行程序,直到下次返回数据正常;

2012-03-11更新

  • 调整了部分正则表达式的写法
  • 增加了错误判断功能

2012-03-10 更新:支持多域名多记录

  • 多域名使用数组定义,每个元素代表一个域名的一组要更新的子记录;
  • 仅支持A记录的更新,因为其他类型的记录可能有重名的情况出现;
  • 每一组记录,以空格分隔域名和子域名;
  • 第一个空格前为主域名,后面用空格分离多个子域名;
  • 如果使用泛域名,必须用*转义。

@sleepwalkera
Copy link

在 OpenWrt Backfire 10.03.1 下测试失败
显示 "DNSPOD tell me domain list is null,waiting..."
且加入rc.local后路由器无法进行路由转换

已解决,curl添加参数-k忽略证书检查

并删除rc.local中>/dev/null的重定向

@zrong
Copy link
Author

zrong commented Mar 26, 2012

to:sleepwalkera
是DNSPod返回的数据有问题,这种情况1天内会出现多次,但一般不会超过20次。
出现这种提示,不需要做什么操作,程序依然在正常运行。

@sleepwalkera
Copy link

Mon May 7 19:06:01 CST 2012 DNSPOD return error:<html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor=“white”>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx</center>
</body>
</html>
Mon May 7 19:06:01 CST 2012 exit dnspodsh

运行一段时间后出现这个,然后就退出了

@zrong
Copy link
Author

zrong commented May 15, 2012

出现这种错误是DNSPod的服务不稳定所致。程序设定为出现任何报错就退出。

@sleepwalkera
Copy link

502 Bad Gateway 错误也会导致帐号被封禁吗
我这边平均每两天出现一次

@zrong
Copy link
Author

zrong commented May 15, 2012

502应该不会封帐号,我这边也是一两天一次。

@wschacker
Copy link

很好

@ataouli
Copy link

ataouli commented Nov 28, 2012

我是debian系统 在rc.local 里面添加 没有反应 也不生产log 是不是要在 /usr/bin/dnspodsh dnspod_name dnspod_passwaord 前面+ . 变成 . /usr/bin/dnspodsh dnspod_name dnspod_passwaord

@lixiphp
Copy link

lixiphp commented Jun 17, 2013

如何配置泛解析 *.www 在domainList中?

@Yulang-cn
Copy link

你好,请问在openwrt下该怎么实现?

@liyiwu
Copy link

liyiwu commented May 22, 2018

https://github.com/liyiwu/ddnspod
我这个是在openwrt下运行的脚本,功能没有这个全,简单粗暴,够用了。目前只能更新一条记录。

@liuluo1979
Copy link

你好,请问在openwrt下该怎么实现?

https://gist.github.com/liuluo1979/01ceb2a53bcf1cb467f70c341181265e
自用脚本

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment