基于dnspod api构架的bash ddns客户端,实现DDNS功能(动态域名解析)

1.支持token登录。DNSPOD官方推荐的方法,避免在脚本中直接使用账户和密码。
2.支持从本地获取ip地址。因为我的宽带商使用了缓存服务器,所以从网上获取ip地址的话得到的是只是缓存服务器的地址,所以只能从本地获取ip。

##该脚本只支持bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#!/bin/bash

##############################
# dnspodsh v0.4
# 基于dnspod api构架的bash ddns客户端
# 修改者:guisu2010@gmail.com
# 原作者:zrong(zengrong.net)
# 详细介绍:http://zengrong.net/post/1524.htm
# 创建日期:2012-02-13
# 更新日期:2015-05-15
##############################

login_token=""
login_email=''
login_password=''
format="json"
lang="cn"
userAgent="dnspodsh/0.4(guisu2010@gmail.com)"
if [ -n $login_token ];then
    commonPost="login_token=$login_token&format=$format&lang=$lang"
else
    commonPost="login_email=$login_email&login_password=$login_password&format=$format&lang=$lang"
fi

apiUrl='https://dnsapi.cn/'
ipUrl='http://members.3322.org/dyndns/getip'

# 要处理的域名数组,每个元素代表一个域名的一组记录
# 在数组的一个元素中,以空格分隔域名和子域名
# 第一个空格前为主域名,后面用空格分离多个子域名
# 如果使用泛域名,必须用\*转义
#domainList[0]='domain1.com \* @ www'
#domainList[1]='domain2.com subdomain subdomain2'

# 这里是只修改一个子域名的例子
domainList[0]='example.com subdomain'

# 多长时间比较一次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'))
    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:"`
    # 如果获取最新ip错误,就继续等待下一次取值
    if ! checkip "$newip";then
        writeLog 'can not get new ip,waiting...'
        sleep $delay
        continue
    fi
    echo 'wan ip:'$newip
    echo $commonPost
    # 获取需要修改的记录
    getChangedRecords
    if (( ${#changedRecords[@]} > 0 ));then
        writeLog "ip is changed,new ip is:$newip"
        setRecords
    fi
}

while [ 1 ];do
    go
    sleep $delay
done

我在自己的路由器(优酷路由宝,pandorabox)里执行时发现脚本会报错。
找了好久才发现是因为pandorabox自带的是ash,所以自己安装了一个bash shell,才解决报错的问题。

这个脚本原作者是zrong(zengrong.net),他写了一篇不错的文档来介绍脚本的使用方法,地址是:http://zengrong.net/post/1524.htm

dnspodsh-master脚本

注意事项:

DNSPod在检测到5分钟内登录错误30次后,会禁用该账号的登录。所以必须在每次调用API的时候,都检测返回代码;
不要过多的调用DNSPod的API,尽量使用缓存。
将dnspodsh复制到/usr/bin下,并在/etc/rc.local中加入下面一行即可实现你自己的免费DDNS:

1
/usr/bin/dnspodsh dnspod_name dnspod_passwaord &>/dev/null

这篇是转载的,,,

原文链接:https://xiaohost.com/1746.html,转载请注明出处。
0

评论0

请先