diff --git a/cfddns/cfddns/scripts/cfddns_config.sh b/cfddns/cfddns/scripts/cfddns_config.sh index 41012c89..797742b8 100755 --- a/cfddns/cfddns/scripts/cfddns_config.sh +++ b/cfddns/cfddns/scripts/cfddns_config.sh @@ -6,16 +6,36 @@ alias echo_date='echo 【$(TZ=UTC-8 date -R +%Y年%m月%d日\ %X)】:' CONFIG_FILE="/tmp/cfddns_status.json" LOG_FILE="/tmp/upload/cfddns_log.txt" LOGTIME=$(TZ=UTC-8 date -R "+%Y-%m-%d %H:%M:%S") +MAX_RECORDS=10 IPv4Regex="^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})(.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})){3}$" IPv6Regex="^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$" [ "$cfddns_ttl" = "" ] && cfddns_ttl="1" +[ "$cfddns_method_v4" = "" ] && cfddns_method_v4="curl ifconfig.me" +[ "$cfddns_method_v6" = "" ] && cfddns_method_v6="curl -6 ifconfig.me" -if [ "$cfddns_name" = "@" ];then - cfddns_name_domain=$cfddns_domain -else - cfddns_name_domain=$cfddns_name.$cfddns_domain -fi +trim_text(){ + echo "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} + +build_fqdn(){ + if [ "$1" = "@" ];then + echo "$2" + else + echo "$1.$2" + fi +} + +append_status_line(){ + if [ -z "$1" ];then + return + fi + if [ -z "$status_lines" ];then + status_lines="$1" + else + status_lines="${status_lines}
$1" + fi +} get_bol() { case "$cfddns_proxied" in @@ -29,76 +49,173 @@ get_bol() { } get_record_response() { - curl -kLsX GET "https://api.cloudflare.com/client/v4/zones/${cfddns_zid}/dns_records?type=${record_type}&name=${cfddns_name_domain}&order=type&direction=desc&match=all" \ + local current_fqdn="$1" + curl -kLsG "https://api.cloudflare.com/client/v4/zones/${cfddns_zid}/dns_records" \ -H "Authorization: Bearer ${cfddns_akey}" \ - -H "Content-type: application/json" + -H "Content-type: application/json" \ + --data-urlencode "type=${record_type}" \ + --data-urlencode "name=${current_fqdn}" \ + --data-urlencode "order=type" \ + --data-urlencode "direction=desc" \ + --data-urlencode "match=all" } update_record() { + local current_fqdn="$1" curl -kLsX PUT "https://api.cloudflare.com/client/v4/zones/${cfddns_zid}/dns_records/${cfddns_id}" \ -H "Authorization: Bearer ${cfddns_akey}" \ -H "Content-Type: application/json" \ - --data '{"type":"'${record_type}'","name":"'${cfddns_name_domain}'","content":"'${update_to_ip}'","ttl":'${cfddns_ttl}',"proxied":'$(get_bol)'}' + --data '{"type":"'${record_type}'","name":"'${current_fqdn}'","content":"'${update_to_ip}'","ttl":'${cfddns_ttl}',"proxied":'$(get_bol)'}' } get_info(){ - cfddns_result=`get_record_response` - if [ $(echo $cfddns_result | grep -c "\"success\":true") -gt 0 ];then + local current_fqdn="$1" + cfddns_result=`get_record_response "$current_fqdn"` + if [ $(echo "$cfddns_result" | grep -c "\"success\":true") -gt 0 ];then + if [ $(echo "$cfddns_result" | grep -c "\"result\":\\[\\]") -gt 0 ];then + record_error="【$LOGTIME】:${current_fqdn} 未找到IP${ip_type}解析记录!" + return 1 + fi # CFDDNS的RECORD ID - cfddns_id=`echo $cfddns_result | awk -F"","" '{print $1}' | sed 's/{.*://g' | sed 's/\"//g'` + cfddns_id=`echo "$cfddns_result" | grep -o '"id":"[^"]*"' | head -n1 | cut -d'"' -f4` # CFDDNS的RECORD IP - record_ip=`echo $cfddns_result | awk -F"","" '{print $6}' | sed -e 's/\"//g' -e 's/content://'` - echo_date CloudFlare IP${ip_type}为 $record_ip + record_ip=`echo "$cfddns_result" | grep -o '"content":"[^"]*"' | head -n1 | cut -d'"' -f4` + if [ -z "$cfddns_id" -o -z "$record_ip" -o $(echo "$cfddns_id" | grep -Ec '^[0-9a-fA-F]{32}$') -eq 0 ];then + record_error="【$LOGTIME】:${current_fqdn} 获取IP${ip_type}解析记录错误!" + return 1 + fi + echo_date "${current_fqdn} CloudFlare IP${ip_type}为 $record_ip" else - dbus set cfddns_status_${ip_type}="【$LOGTIME】:获取IP${ip_type}解析记录错误!" - exit 1 + record_error="【$LOGTIME】:${current_fqdn} 获取IP${ip_type}解析记录错误!" + return 1 fi - - localip=`$cfddns_method` 2>&1 - if [ $(echo $localip | grep -Ec $IPv4Regex) -gt 0 -o $(echo $localip | grep -Ec $IPv6Regex) -gt 0 ];then - echo_date 本地IP${ip_type}为 $localip + + localip_raw=`$cfddns_method` 2>&1 + if [ "$ip_type" = "v4" ];then + localip=`echo "$localip_raw" | tr '\r\t ' '\n\n\n' | grep -E "$IPv4Regex" | head -n1` else - dbus set cfddns_status_${ip_type}="【$LOGTIME】:获取本地IP${ip_type}错误!" - exit 1 + localip=`echo "$localip_raw" | tr '\r\t ' '\n\n\n' | grep -E "$IPv6Regex" | head -n1` + fi + if [ -n "$localip" ];then + echo_date "${current_fqdn} 本地IP${ip_type}为 $localip" + else + echo_date "${current_fqdn} 获取本地IP${ip_type}错误,命令:$cfddns_method" + echo_date "${current_fqdn} 命令输出:$localip_raw" + record_error="【$LOGTIME】:${current_fqdn} 获取本地IP${ip_type}错误!" + return 1 fi } update_ip(){ - update_result=`update_record` - if [ $(echo $update_result | grep -c "\"success\":true") -gt 0 ];then - echo_date 更新成功! + local current_fqdn="$1" + update_result=`update_record "$current_fqdn"` + if [ $(echo "$update_result" | grep -c "\"success\":true") -gt 0 ];then + echo_date "${current_fqdn} 更新成功!" else - echo_date 更新失败!请检查设置! - echo $update_result - exit 1 + echo_date "${current_fqdn} 更新失败!请检查设置!" + echo "$update_result" + record_error="【$LOGTIME】:${current_fqdn} 更新失败!请检查设置!" + return 1 fi } check_update(){ - if [ $1 -eq 4 ];then + local update_target=$1 + local current_fqdn="$2" + record_status_line="" + if [ "$update_target" -eq 4 ];then record_type="A" ip_type="v4" cfddns_method=$cfddns_method_v4 - [ "cfddns_method" == "" ] && cfddns_method="curl -s --interface ppp0 v4.ipip.net" + [ "$cfddns_method" = "" ] && cfddns_method="curl ifconfig.me" else record_type="AAAA" cfddns_method=$cfddns_method_v6 - [ "cfddns_method" == "" ] && cfddns_method="curl -s --interface ppp0 v6.ipip.net" + [ "$cfddns_method" = "" ] && cfddns_method="curl -6 ifconfig.me" ip_type="v6" fi - echo_date "CloudFlare DDNS更新启动!" - get_info - if [ "$localip" == "$record_ip" ];then - echo_date 两个IP${ip_type}相同,跳过更新! - dbus set cfddns_status_${ip_type}="【$LOGTIME】:IP${ip_type}地址:$localip 未发生变化,跳过!" + echo_date "${current_fqdn} CloudFlare DDNS更新启动!" + if ! get_info "$current_fqdn"; then + echo_date "$record_error" + record_status_line="$record_error" + echo_date "======================================" + return 1 + fi + if [ "$localip" = "$record_ip" ];then + echo_date "${current_fqdn} 两个IP${ip_type}相同,跳过更新!" + record_status_line="【$LOGTIME】:${current_fqdn} IP${ip_type}地址:$localip 未发生变化,跳过!" else update_to_ip=$localip - echo_date 两个IP${ip_type}不相同,开始更新! - update_ip - dbus set cfddns_status_${ip_type}="【$LOGTIME】:IP${ip_type}地址:$localip 更新成功!" + echo_date "${current_fqdn} 两个IP${ip_type}不相同,开始更新!" + if update_ip "$current_fqdn"; then + record_status_line="【$LOGTIME】:${current_fqdn} IP${ip_type}地址:$localip 更新成功!" + else + record_status_line="$record_error" + fi fi echo_date "======================================" } + +run_updates_for_ip(){ + local update_target=$1 + local records_raw + local records_count=0 + local records_overflow=0 + local record_name + local record_domain + local record_fqdn + records_raw=$(printf '%b' "$cfddns_records" | tr '\r' '\n' | sed '/^[[:space:]]*$/d') + status_lines="" + if [ -z "$records_raw" ];then + status_lines="【$LOGTIME】:未配置域名记录!" + if [ "$update_target" -eq 4 ];then + dbus set cfddns_status_v4="$status_lines" + else + dbus set cfddns_status_v6="$status_lines" + fi + echo_date "未配置域名记录,跳过IP${update_target}更新!" + return 1 + fi + + while IFS='|' read -r record_name record_domain; do + [ -z "$record_name$record_domain" ] && continue + records_count=$((records_count + 1)) + if [ "$records_count" -gt "$MAX_RECORDS" ];then + records_overflow=1 + break + fi + + record_name=$(trim_text "$record_name") + record_domain=$(trim_text "$record_domain") + if [ -z "$record_name" -o -z "$record_domain" ];then + echo_date "第${records_count}组域名记录格式错误,已跳过!" + append_status_line "【$LOGTIME】:第${records_count}组域名记录格式错误,已跳过!" + continue + fi + + record_fqdn=$(build_fqdn "$record_name" "$record_domain") + check_update "$update_target" "$record_fqdn" + append_status_line "$record_status_line" + done <> $LOG_FILE echo_date "检测到网络拨号..." >> $LOG_FILE - check_update 4 >> $LOG_FILE + run_updates_for_ip 4 >> $LOG_FILE if [ "$cfddns_ipv6" == "1" ];then - check_update 6 >> $LOG_FILE + run_updates_for_ip 6 >> $LOG_FILE fi else logger "[软件中心]: CloudFlare DDNS未设置开机启动,跳过!" fi ;; update) - check_update >> $LOG_FILE + if [ "$cfddns_enable" == "1" ];then + echo_date "======================================" >> $LOG_FILE + run_updates_for_ip 4 >> $LOG_FILE + if [ "$cfddns_ipv6" == "1" ];then + run_updates_for_ip 6 >> $LOG_FILE + fi + else + echo_date "CloudFlare DDNS未启用,跳过更新!" >> $LOG_FILE + fi ;; esac # ====================================submit by web==================================== @@ -133,9 +258,9 @@ case $2 in if [ "$cfddns_enable" == "1" ];then [ ! -L "/koolshare/init.d/S99cfddns.sh" ] && ln -sf /koolshare/scripts/cfddns_config.sh /koolshare/init.d/S99cfddns.sh echo_date "======================================" >> $LOG_FILE - check_update 4 >> $LOG_FILE + run_updates_for_ip 4 >> $LOG_FILE if [ "$cfddns_ipv6" == "1" ];then - check_update 6 >> $LOG_FILE + run_updates_for_ip 6 >> $LOG_FILE fi else echo_date "关闭CloudFlare DDNS!" >> $LOG_FILE diff --git a/cfddns/cfddns/version b/cfddns/cfddns/version index 9459d4ba..7e32cd56 100644 --- a/cfddns/cfddns/version +++ b/cfddns/cfddns/version @@ -1 +1 @@ -1.1 +1.3 diff --git a/cfddns/cfddns/webs/Module_cfddns.asp b/cfddns/cfddns/webs/Module_cfddns.asp index d4357df0..bee3b8a7 100644 --- a/cfddns/cfddns/webs/Module_cfddns.asp +++ b/cfddns/cfddns/webs/Module_cfddns.asp @@ -86,13 +86,15 @@ var dbus = {}; var _responseLen; var noChange = 0; var x = 5; -var params_inp = ['cfddns_email', 'cfddns_akey', 'cfddns_zid', 'cfddns_name', 'cfddns_domain', 'cfddns_ttl', 'cfddns_method_v4', 'cfddns_method_v6']; +var params_inp = ['cfddns_email', 'cfddns_akey', 'cfddns_zid', 'cfddns_ttl', 'cfddns_method_v4', 'cfddns_method_v6', 'cfddns_records']; var params_chk = ['cfddns_enable', 'cfddns_proxied', 'cfddns_ipv6']; +var MAX_RECORDS = 10; function init(){ show_menu(menu_hook); get_dbus_data(); conf2obj(); + render_records(); toggle_func(); update_visibility(); get_status(); @@ -111,6 +113,130 @@ function conf2obj(){ } } +function trim_text(val){ + return (val || "").replace(/^\s+|\s+$/g, ""); +} + +function get_fqdn(name, domain){ + if (name == "@"){ + return domain; + } + return name + "." + domain; +} + +function parse_records(raw){ + var records = []; + if (!raw){ + return records; + } + var lines = raw.replace(/\\n/g, "\n").replace(/\r/g, "\n").split("\n"); + for (var i = 0; i < lines.length; i++) { + var line = trim_text(lines[i]); + if (!line){ + continue; + } + var idx = line.indexOf("|"); + if (idx === -1){ + continue; + } + var name = trim_text(line.substring(0, idx)); + var domain = trim_text(line.substring(idx + 1)); + if (!name || !domain){ + continue; + } + records.push({"name": name, "domain": domain}); + if (records.length >= MAX_RECORDS){ + break; + } + } + return records; +} + +function update_record_counter(){ + var rows = $("#cfddns_records_container .cfddns_record_row").length; + E("cfddns_record_count").innerHTML = rows + "/" + MAX_RECORDS; + E("add_record_btn").disabled = rows >= MAX_RECORDS; +} + +function add_record_row(name, domain){ + var rows = $("#cfddns_records_container .cfddns_record_row").length; + if (rows >= MAX_RECORDS){ + alert("最多支持" + MAX_RECORDS + "组域名记录!"); + return; + } + var row = document.createElement("div"); + row.className = "cfddns_record_row"; + row.style.marginBottom = "6px"; + row.innerHTML = ' . '; + row.getElementsByClassName("cfddns_record_name")[0].value = name || ""; + row.getElementsByClassName("cfddns_record_domain")[0].value = domain || ""; + row.getElementsByClassName("cfddns_record_remove")[0].onclick = function(){ remove_record_row(this); }; + E("cfddns_records_container").appendChild(row); + update_record_counter(); +} + +function remove_record_row(btn){ + var rows = $("#cfddns_records_container .cfddns_record_row"); + if (rows.length <= 1){ + $(rows[0]).find(".cfddns_record_name").val(""); + $(rows[0]).find(".cfddns_record_domain").val(""); + update_record_counter(); + return; + } + btn.parentNode.parentNode.removeChild(btn.parentNode); + update_record_counter(); +} + +function render_records(){ + var records = parse_records(dbus["cfddns_records"] || ""); + E("cfddns_records_container").innerHTML = ""; + if (records.length === 0){ + add_record_row("", ""); + return; + } + for (var i = 0; i < records.length; i++) { + add_record_row(records[i].name, records[i].domain); + } + update_record_counter(); +} + +function collect_records(){ + var records = []; + var seen = {}; + var rows = $("#cfddns_records_container .cfddns_record_row"); + for (var i = 0; i < rows.length; i++) { + var name = trim_text($(rows[i]).find(".cfddns_record_name").val()); + var domain = trim_text($(rows[i]).find(".cfddns_record_domain").val()); + if (!name && !domain){ + continue; + } + if (!name || !domain){ + alert("第" + (i + 1) + "组域名未填写完整!"); + return null; + } + if (name.indexOf("|") !== -1 || domain.indexOf("|") !== -1){ + alert("第" + (i + 1) + "组域名包含非法字符“|”!"); + return null; + } + var fqdn = get_fqdn(name, domain).toLowerCase(); + if (seen[fqdn]){ + alert("存在重复域名记录:" + fqdn); + return null; + } + seen[fqdn] = 1; + records.push(name + "|" + domain); + } + if (records.length === 0){ + alert("请至少填写1组域名记录!"); + return null; + } + if (records.length > MAX_RECORDS){ + alert("最多支持" + MAX_RECORDS + "组域名记录!"); + return null; + } + return records.join("\n"); +} + function get_dbus_data(){ $.ajax({ type: "GET", @@ -157,6 +283,11 @@ function get_status1(){ function save(){ setInterval("get_status();",2000); + var records_data = collect_records(); + if (records_data === null){ + return; + } + E("cfddns_records").value = records_data; var dbus_new = {} $("#show_btn2").trigger("click"); // collect data from input and checkbox @@ -415,11 +546,14 @@ function reload_Soft_Center(){ - 域名(Domain Name) + 域名记录(最多10组)[?] - - . - +
+
+ + 0/10 +
+ @@ -443,13 +577,13 @@ function reload_Soft_Center(){ 获得IPv4命令(get ip)[?] - + 获得IPv6命令(get ip)[?] - + @@ -491,4 +625,3 @@ function reload_Soft_Center(){ -