luci-app-ddns: fix typo: CRTL should be CTRL
[project/luci.git] / applications / luci-app-ddns / luasrc / model / cbi / ddns / detail.lua
index 40a66ef..977dbe3 100644 (file)
@@ -1,28 +1,22 @@
 -- Copyright 2008 Steven Barth <steven@midlink.org>
 -- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
 -- Copyright 2013 Manuel Munz <freifunk at somakoma dot de>
--- Copyright 2014-2015 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
+-- Copyright 2014-2016 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
 -- Licensed to the public under the Apache License 2.0.
 
 local NX   = require "nixio"
 local NXFS = require "nixio.fs"
 local SYS  = require "luci.sys"
 local UTIL = require "luci.util"
+local HTTP = require "luci.http"
 local DISP = require "luci.dispatcher"
 local WADM = require "luci.tools.webadmin"
 local DTYP = require "luci.cbi.datatypes"
+local CTRL = require "luci.controller.ddns"    -- this application's controller
 local DDNS = require "luci.tools.ddns"         -- ddns multiused functions
 
 -- takeover arguments -- #######################################################
-local section  = arg[1]
-
--- check supported options -- ##################################################
--- saved to local vars here because doing multiple os calls slow down the system
-local has_ipv6   = DDNS.check_ipv6()   -- IPv6 support
-local has_ssl    = DDNS.check_ssl()    -- HTTPS support
-local has_proxy  = DDNS.check_proxy()  -- Proxy support
-local has_dnstcp = DDNS.check_bind_host()      -- DNS TCP support
-local has_force  = has_ssl and has_dnstcp      -- Force IP Protocoll
+local section = arg[1]
 
 -- html constants -- ###########################################################
 local font_red = "<font color='red'>"
@@ -31,15 +25,15 @@ local bold_on       = "<strong>"
 local bold_off = "</strong>"
 
 -- error text constants -- #####################################################
-err_ipv6_plain = translate("IPv6 not supported") .. " - " ..
+local err_ipv6_plain = translate("IPv6 not supported") .. " - " ..
                translate("please select 'IPv4' address version")
-err_ipv6_basic = bold_on ..
+local err_ipv6_basic = bold_on ..
                        font_red ..
                                translate("IPv6 not supported") ..
                        font_off ..
                        "<br />" .. translate("please select 'IPv4' address version") ..
                 bold_off
-err_ipv6_other = bold_on ..
+local err_ipv6_other = bold_on ..
                        font_red ..
                                translate("IPv6 not supported") ..
                        font_off ..
@@ -62,61 +56,164 @@ function err_tab_timer(self)
        return translate("Timer Settings") .. " - " .. self.title .. ": "
 end
 
+-- read services/services_ipv6 files -- ########################################
+local services4 = { }          -- IPv4 --
+local fd4 = io.open("/etc/ddns/services", "r")
+if fd4 then
+       local ln, s, t
+       repeat
+               ln = fd4:read("*l")
+               s  = ln and ln:match('^%s*".*') -- only handle lines beginning with "
+               s  = s  and  s:gsub('"','')     -- remove "
+               t  = s  and UTIL.split(s,"(%s+)",nil,true)      -- split on whitespaces
+               if t then services4[t[1]]=t[2] end
+       until not ln
+       fd4:close()
+end
+
+local services6 = { }          -- IPv6 --
+local fd6 = io.open("/etc/ddns/services_ipv6", "r")
+if fd6 then
+       local ln, s, t
+       repeat
+               ln = fd6:read("*l")
+               s  = ln and ln:match('^%s*".*') -- only handle lines beginning with "
+               s  = s  and  s:gsub('"','')     -- remove "
+               t  = s  and UTIL.split(s,"(%s+)",nil,true)      -- split on whitespaces
+               if t then services6[t[1]]=t[2] end
+       until not ln
+       fd6:close()
+end
+
+-- multi-used functions -- ####################################################
 -- function to verify settings around ip_source
 -- will use dynamic_dns_lucihelper to check if
 -- local IP can be read
 local function _verify_ip_source()
        -- section is globally defined here be calling agrument (see above)
-       local _network   = "-"
-       local _url       = "-"
-       local _interface = "-"
-       local _script    = "-"
-       local _proxy     = ""
+       local _arg
 
        local _ipv6   = usev6:formvalue(section)
        local _source = (_ipv6 == "1")
                        and src6:formvalue(section)
                        or  src4:formvalue(section)
+
+       local command = CTRL.luci_helper .. [[ -]]
+       if (_ipv6 == "1")  then command = command .. [[6]] end
+
        if _source == "network" then
-               _network = (_ipv6 == "1")
+               _arg = (_ipv6 == "1")
                        and ipn6:formvalue(section)
                        or  ipn4:formvalue(section)
+               command = command .. [[n ]] .. _arg
        elseif _source == "web" then
-               _url = (_ipv6 == "1")
+               _arg = (_ipv6 == "1")
                        and iurl6:formvalue(section)
                        or  iurl4:formvalue(section)
+               command = command .. [[u ]] .. _arg
+
                -- proxy only needed for checking url
-               _proxy = (pxy) and pxy:formvalue(section) or ""
+               _arg = (pxy) and pxy:formvalue(section) or ""
+               if (_arg and #_arg > 0) then
+                       command = command .. [[ -p ]] .. _arg
+               end
        elseif _source == "interface" then
-               _interface = ipi:formvalue(section)
+               command = command .. [[i ]] .. ipi:formvalue(section)
        elseif _source == "script" then
-               _script = ips:formvalue(section)
+               command = command .. [[s ]] .. ips:formvalue(section)
        end
+       command = command .. [[ -- get_local_ip]]
+       return (SYS.call(command) == 0)
+end
 
-       local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh get_local_ip ]] ..
-               _ipv6 .. [[ ]] .. _source .. [[ ]] .. _network .. [[ ]] ..
-               _url .. [[ ]] .. _interface .. [[ ']] .. _script.. [[' ]] .. _proxy
-       local ret = SYS.call(command)
+-- function to check if option is used inside url or script
+-- return -1 on error, 0 NOT required, 1 required
+local function _option_used(option, urlscript)
+       local surl      -- search string for url
+       local ssh       -- search string for script
+       local required  -- option used inside url or script
 
-       if ret == 0 then
-               return true     -- valid
+       if     option == "domain"    then surl, ssh = '%[DOMAIN%]', '%$domain'
+       elseif option == "username"  then surl, ssh = '%[USERNAME%]', '%$username'
+       elseif option == "password"  then surl, ssh = '%[PASSWORD%]', '%$password'
+       elseif option == "param_enc" then surl, ssh = '%[PARAMENC%]', '%$param_enc'
+       elseif option == "param_opt" then surl, ssh = '%[PARAMOPT%]', '%$param_opt'
        else
-               return nil      -- invalid
+               error("undefined option")
+               return -1       -- return on error
        end
-end
 
--- cbi-map definition -- #######################################################
-m = Map("ddns")
+       local required = false
+       -- handle url
+       if urlscript:find('http') then
+               required = ( urlscript:find(surl) )
+       -- handle script
+       else
+               if not urlscript:find("/") then
+                       -- might be inside ddns-scripts directory
+                       urlscript = "/usr/lib/ddns/" .. urlscript
+               end
+               -- problem with script exit here
+               if not NXFS.access(urlscript) then return -1 end
+
+               local f = io.input(urlscript)
+               -- still problem with script exit here
+               if not f then return -1 end
+               for l in f:lines() do
+                       repeat
+                               if l:find('^#') then break end  -- continue on comment lines
+                               required = ( l:find(surl) or l:find(ssh) )
+                       until true
+                       if required then break end
+               end
+               f:close()
+       end
+       return (required and 1 or 0)
+end
 
--- first need to close <a> from cbi map template our <a> closed by template
-m.title = [[</a><a href="]] .. DISP.build_url("admin", "services", "ddns") .. [[">]] ..
-               translate("Dynamic DNS")
+-- function to verify if option is valid
+local function _option_validate(self, value)
+       -- section is globally defined here be calling agrument (see above)
+       local fusev6 = usev6:formvalue(section) or "0"
+       local fsvc4  = svc4:formvalue(section) or "-"
+       local fsvc6  = svc6:formvalue(section) or "-"
+       local urlsh, used
+
+       -- IP-Version dependent custom service selected
+       if (fusev6 == "0" and fsvc4 == "-") or
+          (fusev6 == "1" and fsvc6 == "-") then
+               -- read custom url
+               urlsh = uurl:formvalue(section) or ""
+               -- no url then read custom script
+               if (#urlsh == 0) then
+                       urlsh = ush:formvalue(section) or ""
+               end
+       -- IPv4 read from services4 table
+       elseif (fusev6 == "0") then
+               urlsh = services4[fsvc4] or ""
+       -- IPv6 read from services6 table
+       else
+               urlsh = services6[fsvc6] or ""
+       end
+       -- problem with url or script exit here
+       -- error handled somewhere else
+       if (#urlsh == 0) then return "" end
 
-m.description = translate("Dynamic DNS allows that your router can be reached with " ..
-                       "a fixed hostname while having a dynamically changing " ..
-                       "IP address.")
+       used = _option_used(self.option, urlsh)
+       -- on error or not used return empty sting
+       if used < 1 then return "" end
+       -- needed but no data then return error
+       if not value or (#value == 0) then
+               return nil, err_tab_basic(self) .. translate("missing / required")
+       end
+       return value
+end
 
-m.redirect = DISP.build_url("admin", "services", "ddns")
+-- cbi-map definition -- #######################################################
+local m        = Map("ddns")
+m.title                = CTRL.app_title_back()
+m.description  = CTRL.app_description()
+m.redirect     = DISP.build_url("admin", "services", "ddns")
 
 m.on_after_commit = function(self)
        if self.changed then    -- changes ?
@@ -127,19 +224,40 @@ m.on_after_commit = function(self)
        end
 end
 
+-- provider switch was requested, save and reload page
+if m:formvalue("cbid.ddns.%s._switch" % section) then  -- section == arg[1]
+       local fsvc
+       local fusev6 = m:formvalue("cbid.ddns.%s.use_ipv6" % section) or "0"
+       if fusev6 == "1" then
+               fsvc = m:formvalue("cbid.ddns.%s.ipv6_service_name" % section) or ""
+       else
+               fsvc = m:formvalue("cbid.ddns.%s.ipv4_service_name" % section) or ""
+       end
+
+       if fusev6 ~= (m:get(section, "use_ipv6") or "0") then   -- IPv6 was changed
+               m:set(section, "use_ipv6", fusev6)              -- save it
+       end
+
+       if fsvc ~= "-" then                                     -- NOT "custom"
+               m:set(section, "service_name", fsvc)            -- save it
+       else                                                    -- else
+               m:del(section, "service_name")                  -- delete it
+       end
+       m.uci:save(m.config)
+
+       -- reload page
+       HTTP.redirect( DISP.build_url("admin", "services", "ddns", "detail", section) )
+       return
+end
+
 -- read application settings -- ################################################
--- date format; if not set use ISO format
-date_format = m.uci:get(m.config, "global", "date_format") or "%F %R"
 -- log directory
-log_dir = m.uci:get(m.config, "global", "log_dir") or "/var/log/ddns"
+local logdir = m.uci:get(m.config, "global", "ddns_logdir") or "/var/log/ddns"
 
 -- cbi-section definition -- ###################################################
-ns = m:section( NamedSection, section, "service",
+local ns = m:section( NamedSection, section, "service",
        translate("Details for") .. ([[: <strong>%s</strong>]] % section),
-       translate("Configure here the details for selected Dynamic DNS service.")
-       .. [[<br /><a href="http://wiki.openwrt.org/doc/uci/ddns#version_1x" target="_blank">]]
-       .. translate("For detailed information about parameter settings look here.")
-       .. [[</a>]] )
+       translate("Configure here the details for selected Dynamic DNS service.") )
 ns.instance = section  -- arg [1]
 ns:tab("basic", translate("Basic Settings"), nil )
 ns:tab("advanced", translate("Advanced Settings"), nil )
@@ -147,17 +265,33 @@ ns:tab("timer", translate("Timer Settings"), nil )
 ns:tab("logview", translate("Log File Viewer"), nil )
 
 -- TAB: Basic  #####################################################################################
--- enabled  -- #################################################################
+-- enabled -- #################################################################
 en = ns:taboption("basic", Flag, "enabled",
        translate("Enabled"),
        translate("If this service section is disabled it could not be started." .. "<br />" ..
                "Neither from LuCI interface nor from console") )
 en.orientation = "horizontal"
-function en.parse(self, section)
-       DDNS.flag_parse(self, section)
+
+-- IPv4/IPv6 - lookup_host -- #################################################
+luh = ns:taboption("basic", Value, "lookup_host",
+               translate("Lookup Hostname"),
+               translate("Hostname/FQDN to validate, if IP update happen or necessary") )
+luh.rmempty    = false
+luh.placeholder = "myhost.example.com"
+function luh.validate(self, value)
+       if not value
+       or not (#value > 0)
+       or not DTYP.hostname(value) then
+               return nil, err_tab_basic(self) .. translate("invalid FQDN / required - Sample") .. ": 'myhost.example.com'"
+       else
+               return UTIL.trim(value)
+       end
+end
+function luh.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
 end
 
--- use_ipv6 (NEW)  -- ##########################################################
+-- use_ipv6 -- ################################################################
 usev6 = ns:taboption("basic", ListValue, "use_ipv6",
        translate("IP address version"),
        translate("Defines which IP address 'IPv4/IPv6' is send to the DDNS provider") )
@@ -165,151 +299,128 @@ usev6.widget  = "radio"
 usev6.default = "0"
 usev6:value("0", translate("IPv4-Address") )
 function usev6.cfgvalue(self, section)
-       local value = AbstractValue.cfgvalue(self, section)
-       if has_ipv6 or (value == "1" and not has_ipv6) then
+       local value = AbstractValue.cfgvalue(self, section) or "0"
+       if DDNS.has_ipv6 or (value == "1" and not DDNS.has_ipv6) then
                self:value("1", translate("IPv6-Address") )
        end
-       if value == "1" and not has_ipv6 then
+       if value == "1" and not DDNS.has_ipv6 then
                self.description = err_ipv6_basic
        end
        return value
 end
 function usev6.validate(self, value)
-       if (value == "1" and has_ipv6) or value == "0" then
+       if (value == "1" and DDNS.has_ipv6) or value == "0" then
                return value
        end
        return nil, err_tab_basic(self) .. err_ipv6_plain
 end
-function usev6.write(self, section, value)
-       if value == "0" then    -- force rmempty
-               return self.map:del(section, self.option)
-       else
-               return self.map:set(section, self.option, value)
-       end
+function usev6.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
 end
 
--- IPv4 - service_name -- ######################################################
+-- IPv4 - service_name -- #####################################################
 svc4 = ns:taboption("basic", ListValue, "ipv4_service_name",
        translate("DDNS Service provider") .. " [IPv4]" )
 svc4.default   = "-"
 svc4:depends("use_ipv6", "0")  -- only show on IPv4
-
-local services4 = { }
-local fd4 = io.open("/usr/lib/ddns/services", "r")
-
-if fd4 then
-       local ln
-       repeat
-               ln = fd4:read("*l")
-               local s = ln and ln:match('^%s*"([^"]+)"')
-               if s then services4[#services4+1] = s end
-       until not ln
-       fd4:close()
-end
-
-for _, v in UTIL.vspairs(services4) do svc4:value(v) end
-svc4:value("-", translate("-- custom --") )
-
 function svc4.cfgvalue(self, section)
        local v =  DDNS.read_value(self, section, "service_name")
-       if not v or #v == 0 then
-               return "-"
-       else
-               return v
+       if v and (#v > 0) then
+               for s, u in UTIL.kspairs(services4) do
+                       if v == s then return v end
+               end
        end
+       return "-"
 end
 function svc4.validate(self, value)
-       if usev6:formvalue(section) == "0" then -- do only on IPv4
+       if usev6:formvalue(section) ~= "1" then -- do only on IPv4
                return value
        else
-               return ""       -- supress validate error
+               return ""       -- suppress validate error
        end
 end
 function svc4.write(self, section, value)
-       if usev6:formvalue(section) == "0" then -- do only IPv4 here
+       if usev6:formvalue(section) ~= "1" then -- do only IPv4 here
                self.map:del(section, self.option)      -- to be shure
                if value ~= "-" then                    -- and write "service_name
                        self.map:del(section, "update_url")     -- delete update_url
+                       self.map:del(section, "update_script")  -- delete update_script
                        return self.map:set(section, "service_name", value)
                else
                        return self.map:del(section, "service_name")
                end
        end
 end
+function svc4.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv6 - service_name -- ######################################################
+-- IPv6 - service_name -- #####################################################
 svc6 = ns:taboption("basic", ListValue, "ipv6_service_name",
        translate("DDNS Service provider") .. " [IPv6]" )
 svc6.default   = "-"
 svc6:depends("use_ipv6", "1")  -- only show on IPv6
-if not has_ipv6 then
+if not DDNS.has_ipv6 then
        svc6.description = err_ipv6_basic
 end
-
-local services6 = { }
-local fd6 = io.open("/usr/lib/ddns/services_ipv6", "r")
-
-if fd6 then
-       local ln
-       repeat
-               ln = fd6:read("*l")
-               local s = ln and ln:match('^%s*"([^"]+)"')
-               if s then services6[#services6+1] = s end
-       until not ln
-       fd6:close()
-end
-
-for _, v in UTIL.vspairs(services6) do svc6:value(v) end
-svc6:value("-", translate("-- custom --") )
-
 function svc6.cfgvalue(self, section)
        local v =  DDNS.read_value(self, section, "service_name")
-       if not v or #v == 0 then
-               return "-"
-       else
-               return v
+       if v and (#v > 0) then
+               for s, u in UTIL.kspairs(services4) do
+                       if v == s then return v end
+               end
        end
+       return "-"
 end
 function svc6.validate(self, value)
        if usev6:formvalue(section) == "1" then -- do only on IPv6
-               if has_ipv6 then return value end
+               if DDNS.has_ipv6 then return value end
                return nil, err_tab_basic(self) .. err_ipv6_plain
        else
-               return ""       -- supress validate error
+               return ""       -- suppress validate error
        end
 end
 function svc6.write(self, section, value)
-       if usev6:formvalue(section) == "1" then -- do only when IPv6
+       if usev6:formvalue(section) == "1" then         -- do only when IPv6
                self.map:del(section, self.option)      -- delete "ipv6_service_name" helper
                if value ~= "-" then                    -- and write "service_name
                        self.map:del(section, "update_url")     -- delete update_url
+                       self.map:del(section, "update_script")  -- delete update_script
                        return self.map:set(section, "service_name", value)
                else
                        return self.map:del(section, "service_name")
                end
        end
 end
+function svc6.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
+
+-- IPv4/IPv6 - change Provider -- #############################################
+svs            = ns:taboption("basic", Button, "_switch")
+svs.title      = translate("Really change DDNS provider?")
+svs.inputtitle = translate("Change provider")
+svs.inputstyle = "apply"
 
--- IPv4/IPv6 - update_url -- ###################################################
+-- IPv4/IPv6 - update_url -- ##################################################
 uurl = ns:taboption("basic", Value, "update_url",
        translate("Custom update-URL"),
        translate("Update URL to be used for updating your DDNS Provider." .. "<br />" ..
                "Follow instructions you will find on their WEB page.") )
-uurl:depends("ipv4_service_name", "-")
-uurl:depends("ipv6_service_name", "-")
 function uurl.validate(self, value)
-       local script = ush:formvalue(section)
+       local fush   = ush:formvalue(section)
+       local fusev6 = usev6:formvalue(section)
 
-       if (usev6:formvalue(section) == "0" and svc4:formvalue(section) ~= "-") or
-          (usev6:formvalue(section) == "1" and svc6:formvalue(section) ~= "-") then
+       if (fusev6 ~= "1" and svc4:formvalue(section) ~= "-") or
+          (fusev6 == "1" and svc6:formvalue(section) ~= "-") then
                return ""       -- suppress validate error
-       elseif not value then
-               if not script or not (#script > 0) then
+       elseif not value or (#value == 0) then
+               if not fush or (#fush == 0) then
                        return nil, err_tab_basic(self) .. translate("missing / required")
                else
                        return ""       -- suppress validate error / update_script is given
                end
-       elseif (#script > 0) then
+       elseif (#fush > 0) then
                return nil, err_tab_basic(self) .. translate("either url or script could be set")
        end
 
@@ -326,83 +437,162 @@ function uurl.validate(self, value)
 
        return value
 end
+function uurl.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv4/IPv6 - update_script -- ################################################
+-- IPv4/IPv6 - update_script -- ###############################################
 ush = ns:taboption("basic", Value, "update_script",
        translate("Custom update-script"),
        translate("Custom update script to be used for updating your DDNS Provider.") )
-ush:depends("ipv4_service_name", "-")
-ush:depends("ipv6_service_name", "-")
 function ush.validate(self, value)
-       local url = uurl:formvalue(section)
+       local fuurl  = uurl:formvalue(section)
+       local fusev6 = usev6:formvalue(section)
 
-       if (usev6:formvalue(section) == "0" and svc4:formvalue(section) ~= "-") or
-          (usev6:formvalue(section) == "1" and svc6:formvalue(section) ~= "-") then
+       if (fusev6 ~= "1" and svc4:formvalue(section) ~= "-") or
+          (fusev6 == "1" and svc6:formvalue(section) ~= "-") then
                return ""       -- suppress validate error
-       elseif not value then
-               if not url or not (#url > 0) then
+       elseif not value or (#value == 0) then
+               if not fuurl or (#fuurl == 0) then
                        return nil, err_tab_basic(self) .. translate("missing / required")
                else
                        return ""       -- suppress validate error / update_url is given
                end
-       elseif (#url > 0) then
+       elseif (#fuurl > 0) then
                return nil, err_tab_basic(self) .. translate("either url or script could be set")
        elseif not NXFS.access(value) then
                return nil, err_tab_basic(self) .. translate("File not found")
        end
        return value
 end
+function ush.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv4/IPv6 - domain -- #######################################################
+-- IPv4/IPv6 - domain -- ######################################################
 dom = ns:taboption("basic", Value, "domain",
-               translate("Hostname/Domain"),
+               translate("Domain"),
                translate("Replaces [DOMAIN] in Update-URL") )
-dom.rmempty    = false
-dom.placeholder        = "mypersonaldomain.dyndns.org"
+dom.placeholder = "myhost.example.com"
 function dom.validate(self, value)
-       if not value
-       or not (#value > 0)
-       or not DTYP.hostname(value) then
-               return nil, err_tab_basic(self) ..      translate("invalid - Sample") .. ": 'mypersonaldomain.dyndns.org'"
-       else
-               return value
-       end
+       return _option_validate(self, value)
+end
+function dom.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
 end
 
--- IPv4/IPv6 - username -- #####################################################
+-- IPv4/IPv6 - username -- ####################################################
 user = ns:taboption("basic", Value, "username",
                translate("Username"),
-               translate("Replaces [USERNAME] in Update-URL") )
-user.rmempty = false
+               translate("Replaces [USERNAME] in Update-URL (URL-encoded)") )
 function user.validate(self, value)
-       if not value then
-               return nil, err_tab_basic(self) .. translate("missing / required")
-       end
-       return value
+       return _option_validate(self, value)
+end
+function user.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
 end
 
--- IPv4/IPv6 - password -- #####################################################
+-- IPv4/IPv6 - password -- ####################################################
 pw = ns:taboption("basic", Value, "password",
                translate("Password"),
-               translate("Replaces [PASSWORD] in Update-URL") )
-pw.rmempty  = false
+               translate("Replaces [PASSWORD] in Update-URL (URL-encoded)") )
 pw.password = true
 function pw.validate(self, value)
-       if not value then
-               return nil, err_tab_basic(self) .. translate("missing / required")
+       return _option_validate(self, value)
+end
+function pw.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
+
+-- IPv4/IPv6 - param_enc -- ###################################################
+pe = ns:taboption("basic", Value, "param_enc",
+               translate("Optional Encoded Parameter"),
+               translate("Optional: Replaces [PARAMENC] in Update-URL (URL-encoded)") )
+function pe.validate(self, value)
+       return _option_validate(self, value)
+end
+function pe.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
+
+-- IPv4/IPv6 - param_enc -- ###################################################
+po = ns:taboption("basic", Value, "param_opt",
+               translate("Optional Parameter"),
+               translate("Optional: Replaces [PARAMOPT] in Update-URL (NOT URL-encoded)") )
+function po.validate(self, value)
+       return _option_validate(self, value)
+end
+function po.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
+
+-- handled service dependent show/display -- ##################################
+-- IPv4 --
+local cv4 = svc4:cfgvalue(section)
+if cv4 ~= "-" then
+       svs:depends ("ipv4_service_name", "-" ) -- show only if "-"
+       ush:depends ("ipv4_service_name", "?")
+       uurl:depends("ipv4_service_name", "?")
+else
+       uurl:depends("ipv4_service_name", "-")
+       ush:depends ("ipv4_service_name", "-")
+       dom:depends("ipv4_service_name", "-" )
+       user:depends("ipv4_service_name", "-" )
+       pw:depends("ipv4_service_name", "-" )
+       pe:depends("ipv4_service_name", "-" )
+       po:depends("ipv4_service_name", "-" )
+end
+for s, u in UTIL.kspairs(services4) do
+       svc4:value(s)   -- fill DropDown-List
+       if cv4 ~= s then
+               svs:depends("ipv4_service_name", s )
+       else
+               dom:depends ("ipv4_service_name", ((_option_used(dom.option, u) == 1) and s or "?") )
+               user:depends("ipv4_service_name", ((_option_used(user.option, u) == 1) and s or "?") )
+               pw:depends  ("ipv4_service_name", ((_option_used(pw.option, u) == 1) and s or "?") )
+               pe:depends  ("ipv4_service_name", ((_option_used(pe.option, u) == 1) and s or "?") )
+               po:depends  ("ipv4_service_name", ((_option_used(po.option, u) == 1) and s or "?") )
        end
-       return value
 end
+svc4:value("-", translate("-- custom --") )
 
--- IPv4/IPv6 - use_https (NEW) -- ##############################################
-if has_ssl or ( ( m:get(section, "use_https") or "0" ) == "1" ) then
+-- IPv6 --
+local cv6 = svc6:cfgvalue(section)
+if cv6 ~= "-" then
+       svs:depends ("ipv6_service_name", "-" )
+       uurl:depends("ipv6_service_name", "?")
+       ush:depends ("ipv6_service_name", "?")
+else
+       uurl:depends("ipv6_service_name", "-")
+       ush:depends ("ipv6_service_name", "-")
+       dom:depends("ipv6_service_name", "-" )
+       user:depends("ipv6_service_name", "-" )
+       pw:depends("ipv6_service_name", "-" )
+       pe:depends("ipv6_service_name", "-" )
+       po:depends("ipv6_service_name", "-" )
+end
+for s, u in UTIL.kspairs(services6) do
+       svc6:value(s)   -- fill DropDown-List
+       if cv6 ~= s then
+               svs:depends("ipv6_service_name", s )
+       else
+               dom:depends ("ipv6_service_name", ((_option_used(dom.option, u) == 1) and s or "?") )
+               user:depends("ipv6_service_name", ((_option_used(user.option, u) == 1) and s or "?") )
+               pw:depends  ("ipv6_service_name", ((_option_used(pw.option, u) == 1) and s or "?") )
+               pe:depends  ("ipv6_service_name", ((_option_used(pe.option, u) == 1) and s or "?") )
+               po:depends  ("ipv6_service_name", ((_option_used(po.option, u) == 1) and s or "?") )
+       end
+end
+svc6:value("-", translate("-- custom --") )
+
+-- IPv4/IPv6 - use_https -- ###################################################
+if DDNS.has_ssl or ( ( m:get(section, "use_https") or "0" ) == "1" ) then
        https = ns:taboption("basic", Flag, "use_https",
                translate("Use HTTP Secure") )
        https.orientation = "horizontal"
-       https.rmempty = false -- force validate function
        function https.cfgvalue(self, section)
                local value = AbstractValue.cfgvalue(self, section)
-               if not has_ssl and value == "1" then
+               if not DDNS.has_ssl and value == "1" then
                        self.description = bold_on .. font_red ..
                                translate("HTTPS not supported") .. font_off .. "<br />" ..
                                translate("please disable") .. " !" .. bold_off
@@ -411,11 +601,8 @@ if has_ssl or ( ( m:get(section, "use_https") or "0" ) == "1" ) then
                end
                return value
        end
-       function https.parse(self, section)
-               DDNS.flag_parse(self, section)
-       end
        function https.validate(self, value)
-               if (value == "1" and has_ssl ) or value == "0" then return value end
+               if (value == "1" and DDNS.has_ssl ) or value == "0" then return value end
                return nil, err_tab_basic(self) .. translate("HTTPS not supported") .. " !"
        end
        function https.write(self, section, value)
@@ -428,34 +615,38 @@ if has_ssl or ( ( m:get(section, "use_https") or "0" ) == "1" ) then
        end
 end
 
--- IPv4/IPv6 - cacert (NEW) -- #################################################
-if has_ssl then
+-- IPv4/IPv6 - cacert -- ######################################################
+if DDNS.has_ssl then
        cert = ns:taboption("basic", Value, "cacert",
                translate("Path to CA-Certificate"),
                translate("directory or path/file") .. "<br />" ..
                translate("or") .. bold_on .. " IGNORE " .. bold_off ..
                translate("to run HTTPS without verification of server certificates (insecure)") )
        cert:depends("use_https", "1")
-       cert.rmempty = false -- force validate function
-       cert.default = "/etc/ssl/certs"
+       cert.placeholder = "/etc/ssl/certs"
+       cert.forcewrite = true
        function cert.validate(self, value)
-               if https:formvalue(section) == "0" then
-                       return ""       -- supress validate error if NOT https
+               if https:formvalue(section) ~= "1" then
+                       return ""       -- suppress validate error if NOT https
                end
                if value then   -- otherwise errors in datatype check
                        if DTYP.directory(value)
                        or DTYP.file(value)
-                       or value == "IGNORE" then
+                       or (value == "IGNORE")
+                       or (#value == 0) then
                                return value
                        end
                end
                return nil, err_tab_basic(self) ..
                        translate("file or directory not found or not 'IGNORE'") .. " !"
        end
+       function cert.parse(self, section, novld)
+               DDNS.value_parse(self, section, novld)
+       end
 end
 
--- TAB: Advanced  ##################################################################################
--- IPv4 - ip_source -- #########################################################
+-- TAB: Advanced  #################################################################################
+-- IPv4 - ip_source -- ########################################################
 src4 = ns:taboption("advanced", ListValue, "ipv4_source",
        translate("IP address source") .. " [IPv4]",
        translate("Defines the source to read systems IPv4-Address from, that will be send to the DDNS provider") )
@@ -501,8 +692,11 @@ function src4.write(self, section, value)
        self.map:del(section, self.option)               -- delete "ipv4_source" helper
        return self.map:set(section, "ip_source", value) -- and write "ip_source
 end
+function src4.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv6 - ip_source -- #########################################################
+-- IPv6 - ip_source -- ########################################################
 src6 = ns:taboption("advanced", ListValue, "ipv6_source",
        translate("IP address source") .. " [IPv6]",
        translate("Defines the source to read systems IPv6-Address from, that will be send to the DDNS provider") )
@@ -512,16 +706,16 @@ src6:value("network", translate("Network"))
 src6:value("web", translate("URL"))
 src6:value("interface", translate("Interface"))
 src6:value("script", translate("Script"))
-if not has_ipv6 then
+if not DDNS.has_ipv6 then
        src6.description = err_ipv6_other
 end
 function src6.cfgvalue(self, section)
        return DDNS.read_value(self, section, "ip_source")
 end
 function src6.validate(self, value)
-       if usev6:formvalue(section) == "0" then
+       if usev6:formvalue(section) ~= "1" then
                return ""       -- ignore on IPv4 selected
-       elseif not has_ipv6 then
+       elseif not DDNS.has_ipv6 then
                return nil, err_tab_adv(self) .. err_ipv6_plain
        elseif not _verify_ip_source() then
                return nil, err_tab_adv(self) ..
@@ -531,7 +725,7 @@ function src6.validate(self, value)
        end
 end
 function src6.write(self, section, value)
-       if usev6:formvalue(section) == "0" then
+       if usev6:formvalue(section) ~= "1" then
                return true     -- ignore on IPv4 selected
        elseif value == "network" then
                self.map:del(section, "ip_url")         -- delete not need parameters
@@ -553,8 +747,11 @@ function src6.write(self, section, value)
        self.map:del(section, self.option)               -- delete "ipv4_source" helper
        return self.map:set(section, "ip_source", value) -- and write "ip_source
 end
+function src6.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv4 - ip_network (default "wan") -- ########################################
+-- IPv4 - ip_network (default "wan") -- #######################################
 ipn4 = ns:taboption("advanced", ListValue, "ipv4_network",
        translate("Network") .. " [IPv4]",
        translate("Defines the network to read systems IPv4-Address from") )
@@ -587,14 +784,17 @@ function ipn4.write(self, section, value)
                return self.map:set(section, "ip_network", value) -- and write "ip_network"
        end
 end
+function ipn4.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv6 - ip_network (default "wan6") -- #######################################
+-- IPv6 - ip_network (default "wan6") -- ######################################
 ipn6 = ns:taboption("advanced", ListValue, "ipv6_network",
        translate("Network") .. " [IPv6]" )
 ipn6:depends("ipv6_source", "network")
 ipn6.default = "wan6"
 WADM.cbi_add_networks(ipn6)
-if has_ipv6 then
+if DDNS.has_ipv6 then
        ipn6.description = translate("Defines the network to read systems IPv6-Address from")
 else
        ipn6.description = err_ipv6_other
@@ -603,19 +803,19 @@ function ipn6.cfgvalue(self, section)
        return DDNS.read_value(self, section, "ip_network")
 end
 function ipn6.validate(self, value)
-       if usev6:formvalue(section) == "0"
+       if usev6:formvalue(section) ~= "1"
         or src6:formvalue(section) ~= "network" then
                -- ignore if IPv4 selected OR
                -- ignore everything except "network"
                return ""
-       elseif has_ipv6 then
+       elseif DDNS.has_ipv6 then
                return value
        else
                return nil, err_tab_adv(self) .. err_ipv6_plain
        end
 end
 function ipn6.write(self, section, value)
-       if usev6:formvalue(section) == "0"
+       if usev6:formvalue(section) ~= "1"
         or src6:formvalue(section) ~= "network" then
                -- ignore if IPv4 selected OR
                -- ignore everything except "network"
@@ -627,8 +827,11 @@ function ipn6.write(self, section, value)
                return self.map:set(section, "ip_network", value) -- and write "ip_network"
        end
 end
+function ipn6.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv4 - ip_url (default "checkip.dyndns.com") -- #############################
+-- IPv4 - ip_url (default "checkip.dyndns.com") -- ############################
 iurl4 = ns:taboption("advanced", Value, "ipv4_url",
        translate("URL to detect") .. " [IPv4]",
        translate("Defines the Web page to read systems IPv4-Address from") )
@@ -669,13 +872,16 @@ function iurl4.write(self, section, value)
                return self.map:set(section, "ip_url", value)   -- and write "ip_url"
        end
 end
+function iurl4.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv6 - ip_url (default "checkipv6.dyndns.com") -- ###########################
+-- IPv6 - ip_url (default "checkipv6.dyndns.com") -- ##########################
 iurl6 = ns:taboption("advanced", Value, "ipv6_url",
        translate("URL to detect") .. " [IPv6]" )
 iurl6:depends("ipv6_source", "web")
 iurl6.default = "http://checkipv6.dyndns.com"
-if has_ipv6 then
+if DDNS.has_ipv6 then
        iurl6.description = translate("Defines the Web page to read systems IPv6-Address from")
 else
        iurl6.description = err_ipv6_other
@@ -684,12 +890,12 @@ function iurl6.cfgvalue(self, section)
        return DDNS.read_value(self, section, "ip_url")
 end
 function iurl6.validate(self, value)
-       if usev6:formvalue(section) == "0"
+       if usev6:formvalue(section) ~= "1"
         or src6:formvalue(section) ~= "web" then
                -- ignore if IPv4 selected OR
                -- ignore everything except "web"
                return ""
-       elseif not has_ipv6 then
+       elseif not DDNS.has_ipv6 then
                return nil, err_tab_adv(self) .. err_ipv6_plain
        elseif not value or #value == 0 then
                return nil, err_tab_adv(self) .. translate("missing / required")
@@ -707,7 +913,7 @@ function iurl6.validate(self, value)
        end
 end
 function iurl6.write(self, section, value)
-       if usev6:formvalue(section) == "0"
+       if usev6:formvalue(section) ~= "1"
         or src6:formvalue(section) ~= "web" then
                -- ignore if IPv4 selected OR
                -- ignore everything except "web"
@@ -717,8 +923,11 @@ function iurl6.write(self, section, value)
                return self.map:set(section, "ip_url", value)   -- and write "ip_url"
        end
 end
+function iurl6.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv4 + IPv6 - ip_interface -- ###############################################
+-- IPv4 + IPv6 - ip_interface -- ##############################################
 ipi = ns:taboption("advanced", ListValue, "ip_interface",
        translate("Interface"),
        translate("Defines the interface to read systems IP-Address from") )
@@ -733,16 +942,18 @@ for _, v in pairs(SYS.net.devices()) do
        end
 end
 function ipi.validate(self, value)
-       if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "interface")
-       or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "interface") then
+       local fusev6 = usev6:formvalue(section)
+       if (fusev6 ~= "1" and src4:formvalue(section) ~= "interface")
+       or (fusev6 == "1" and src6:formvalue(section) ~= "interface") then
                return ""
        else
                return value
        end
 end
 function ipi.write(self, section, value)
-       if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "interface")
-       or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "interface") then
+       local fusev6 = usev6:formvalue(section)
+       if (fusev6 ~= "1" and src4:formvalue(section) ~= "interface")
+       or (fusev6 == "1" and src6:formvalue(section) ~= "interface") then
                return true
        else
                -- get network from device to
@@ -752,21 +963,24 @@ function ipi.write(self, section, value)
                return self.map:set(section, self.option, value)
        end
 end
+function ipi.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv4 + IPv6 - ip_script (NEW) -- ############################################
+-- IPv4 + IPv6 - ip_script -- #################################################
 ips = ns:taboption("advanced", Value, "ip_script",
        translate("Script"),
        translate("User defined script to read systems IP-Address") )
 ips:depends("ipv4_source", "script")   -- IPv4
 ips:depends("ipv6_source", "script")   -- or IPv6
-ips.rmempty    = false
 ips.placeholder = "/path/to/script.sh"
 function ips.validate(self, value)
+       local fusev6 = usev6:formvalue(section)
        local split
        if value then split = UTIL.split(value, " ") end
 
-       if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "script")
-       or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "script") then
+       if (fusev6 ~= "1" and src4:formvalue(section) ~= "script")
+       or (fusev6 == "1" and src6:formvalue(section) ~= "script") then
                return ""
        elseif not value or not (#value > 0) or not NXFS.access(split[1], "x") then
                return nil, err_tab_adv(self) ..
@@ -776,15 +990,19 @@ function ips.validate(self, value)
        end
 end
 function ips.write(self, section, value)
-       if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "script")
-       or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "script") then
+       local fusev6 = usev6:formvalue(section)
+       if (fusev6 ~= "1" and src4:formvalue(section) ~= "script")
+       or (fusev6 == "1" and src6:formvalue(section) ~= "script") then
                return true
        else
                return self.map:set(section, self.option, value)
        end
 end
+function ips.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv4 - interface - default "wan" -- #########################################
+-- IPv4 - interface - default "wan" -- ########################################
 -- event network to monitor changes/hotplug/dynamic_dns_updater.sh
 -- only needs to be set if "ip_source"="web" or "script"
 -- if "ip_source"="network" or "interface" we use their network
@@ -799,27 +1017,32 @@ function eif4.cfgvalue(self, section)
        return DDNS.read_value(self, section, "interface")
 end
 function eif4.validate(self, value)
+       local fsrc4 = src4:formvalue(section) or ""
        if usev6:formvalue(section) == "1"
-        or src4:formvalue(section) == "network"
-        or src4:formvalue(section) == "interface" then
+        or fsrc4 == "network"
+        or fsrc4 == "interface" then
                return ""       -- ignore IPv6, network, interface
        else
                return value
        end
 end
 function eif4.write(self, section, value)
+       local fsrc4 = src4:formvalue(section) or ""
        if usev6:formvalue(section) == "1"
-        or src4:formvalue(section) == "network"
-        or src4:formvalue(section) == "interface" then
+        or fsrc4 == "network"
+        or fsrc4 == "interface" then
                return true     -- ignore IPv6, network, interface
        else
                self.map:del(section, self.option)               -- delete "ipv4_interface" helper
                return self.map:set(section, "interface", value) -- and write "interface"
        end
 end
+function eif4.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv6 - interface (NEW) - default "wan6" -- ##################################
--- event network to monitor changes/hotplug (NEW)
+-- IPv6 - interface - default "wan6" -- #######################################
+-- event network to monitor changes/hotplug
 -- only needs to be set if "ip_source"="web" or "script"
 -- if "ip_source"="network" or "interface" we use their network
 eif6 = ns:taboption("advanced", ListValue, "ipv6_interface",
@@ -828,7 +1051,7 @@ eif6:depends("ipv6_source", "web")
 eif6:depends("ipv6_source", "script")
 eif6.default = "wan6"
 WADM.cbi_add_networks(eif6)
-if not has_ipv6 then
+if not DDNS.has_ipv6 then
        eif6.description = err_ipv6_other
 else
        eif6.description = translate("Network on which the ddns-updater scripts will be started")
@@ -837,40 +1060,44 @@ function eif6.cfgvalue(self, section)
        return DDNS.read_value(self, section, "interface")
 end
 function eif6.validate(self, value)
-       if usev6:formvalue(section) == "0"
-        or src4:formvalue(section) == "network"
-        or src4:formvalue(section) == "interface" then
+       local fsrc6 = src6:formvalue(section) or ""
+       if usev6:formvalue(section) ~= "1"
+        or fsrc6 == "network"
+        or fsrc6 == "interface" then
                return ""       -- ignore IPv4, network, interface
-       elseif not has_ipv6 then
+       elseif not DDNS.has_ipv6 then
                return nil, err_tab_adv(self) .. err_ipv6_plain
        else
                return value
        end
 end
 function eif6.write(self, section, value)
-       if usev6:formvalue(section) == "0"
-        or src4:formvalue(section) == "network"
-        or src4:formvalue(section) == "interface" then
+       local fsrc6 = src6:formvalue(section) or ""
+       if usev6:formvalue(section) ~= "1"
+        or fsrc6 == "network"
+        or fsrc6 == "interface" then
                return true     -- ignore IPv4, network, interface
        else
                self.map:del(section, self.option)               -- delete "ipv6_interface" helper
                return self.map:set(section, "interface", value) -- and write "interface"
        end
 end
+function eif6.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- IPv4/IPv6 - bind_network -- #################################################
-if has_ssl or ( ( m:get(section, "bind_network") or "" ) ~= "" ) then
+-- IPv4/IPv6 - bind_network -- ################################################
+if DDNS.has_bindnet or ( ( m:get(section, "bind_network") or "" ) ~= "" ) then
        bnet = ns:taboption("advanced", ListValue, "bind_network",
                translate("Bind Network") )
        bnet:depends("ipv4_source", "web")
        bnet:depends("ipv6_source", "web")
-       bnet.rmempty = true
        bnet.default = ""
        bnet:value("", translate("-- default --"))
        WADM.cbi_add_networks(bnet)
        function bnet.cfgvalue(self, section)
                local value = AbstractValue.cfgvalue(self, section)
-               if not has_ssl and value ~= "" then
+               if not DDNS.has_bindnet and value ~= "" then
                        self.description = bold_on .. font_red ..
                                translate("Binding to a specific network not supported") .. font_off .. "<br />" ..
                                translate("please set to 'default'") .. " !" .. bold_off
@@ -881,21 +1108,24 @@ if has_ssl or ( ( m:get(section, "bind_network") or "" ) ~= "" ) then
                return value
        end
        function bnet.validate(self, value)
-               if (value ~= "" and has_ssl ) or value == "" then return value end
+               if ( (value ~= "") and DDNS.has_bindnet ) or (value == "") then return value end
                return nil, err_tab_adv(self) .. translate("Binding to a specific network not supported") .. " !"
        end
+       function bnet.parse(self, section, novld)
+               DDNS.value_parse(self, section, novld)
+       end
 end
 
--- IPv4 + IPv6 - force_ipversion (NEW) -- ######################################
+-- IPv4 + IPv6 - force_ipversion -- ###########################################
 -- optional to force wget/curl and host to use only selected IP version
 -- command parameter "-4" or "-6"
-if has_force or ( ( m:get(section, "force_ipversion") or "0" ) ~= "0" ) then
+if DDNS.has_forceip or ( ( m:get(section, "force_ipversion") or "0" ) ~= "0" ) then
        fipv = ns:taboption("advanced", Flag, "force_ipversion",
                translate("Force IP Version") )
        fipv.orientation = "horizontal"
        function fipv.cfgvalue(self, section)
                local value = AbstractValue.cfgvalue(self, section)
-               if not has_force and value ~= "0" then
+               if not DDNS.has_forceip and value ~= "0" then
                        self.description = bold_on .. font_red ..
                                translate("Force IP Version not supported") .. font_off .. "<br />" ..
                                translate("please disable") .. " !" .. bold_off
@@ -905,57 +1135,57 @@ if has_force or ( ( m:get(section, "force_ipversion") or "0" ) ~= "0" ) then
                return value
        end
        function fipv.validate(self, value)
-               if (value == "1" and has_force) or value == "0" then return value end
+               if (value == "1" and DDNS.has_forceip) or value == "0" then return value end
                return nil, err_tab_adv(self) .. translate("Force IP Version not supported")
        end
-       function fipv.parse(self, section)
-               DDNS.flag_parse(self, section)
-       end
-       function fipv.write(self, section, value)
-               if value == "1" then
-                       return self.map:set(section, self.option, value)
-               else
-                       return self.map:del(section, self.option)
-               end
-       end
 end
 
--- IPv4 + IPv6 - dns_server (NEW) -- ###########################################
--- optional DNS Server to use resolving my IP if "ip_source"="web"
-dns = ns:taboption("advanced", Value, "dns_server",
-       translate("DNS-Server"),
-       translate("OPTIONAL: Use non-default DNS-Server to detect 'Registered IP'.") .. "<br />" ..
-       translate("Format: IP or FQDN"))
-dns.placeholder = "mydns.lan"
-function dns.validate(self, value)
-       -- if .datatype is set, then it is checked before calling this function
-       if not value then
-               return ""       -- ignore on empty
-       elseif not DTYP.host(value) then
-               return nil, err_tab_adv(self) .. translate("use hostname, FQDN, IPv4- or IPv6-Address")
-       else
-               local ipv6  = usev6:formvalue(section)
-               local force = (fipv) and fipv:formvalue(section) or "0"
-               local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh verify_dns ]] ..
-                       value .. [[ ]] .. ipv6 .. [[ ]] .. force
-               local ret = SYS.call(command)
-               if     ret == 0 then return value       -- everything OK
-               elseif ret == 2 then return nil, err_tab_adv(self) .. translate("nslookup can not resolve host")
-               elseif ret == 3 then return nil, err_tab_adv(self) .. translate("nc (netcat) can not connect")
-               elseif ret == 4 then return nil, err_tab_adv(self) .. translate("Forced IP Version don't matched")
-               else                 return nil, err_tab_adv(self) .. translate("unspecific error")
+-- IPv4 + IPv6 - dns_server -- ################################################
+-- optional DNS Server to use resolving my IP
+if DDNS.has_dnsserver or ( ( m:get(section, "dns_server") or "" ) ~= "" ) then
+       dns = ns:taboption("advanced", Value, "dns_server",
+               translate("DNS-Server"),
+               translate("OPTIONAL: Use non-default DNS-Server to detect 'Registered IP'.") .. "<br />" ..
+               translate("Format: IP or FQDN"))
+       dns.placeholder = "mydns.lan"
+       function dns.validate(self, value)
+               -- if .datatype is set, then it is checked before calling this function
+               if not value or (#value == 0) then
+                       return ""       -- ignore on empty
+               elseif not DDNS.has_dnsserver then
+                       return nil, err_tab_adv(self) .. translate("Specifying a DNS-Server is not supported")
+               elseif not DTYP.host(value) then
+                       return nil, err_tab_adv(self) .. translate("use hostname, FQDN, IPv4- or IPv6-Address")
+               else
+                       local ipv6  = usev6:formvalue(section) or "0"
+                       local force = fipv:formvalue(section)  or "0"
+                       local command = CTRL.luci_helper .. [[ -]]
+                       if (ipv6 == 1)  then command = command .. [[6]] end
+                       if (force == 1) then command = command .. [[f]] end
+                       command = command .. [[d ]] .. value .. [[ -- verify_dns]]
+
+                       local ret = SYS.call(command)
+                       if     ret == 0 then return value       -- everything OK
+                       elseif ret == 2 then return nil, err_tab_adv(self) .. translate("nslookup can not resolve host")
+                       elseif ret == 3 then return nil, err_tab_adv(self) .. translate("nc (netcat) can not connect")
+                       elseif ret == 4 then return nil, err_tab_adv(self) .. translate("Forced IP Version don't matched")
+                       else                 return nil, err_tab_adv(self) .. translate("unspecific error")
+                       end
                end
        end
+       function dns.parse(self, section, novld)
+               DDNS.value_parse(self, section, novld)
+       end
 end
 
--- IPv4 + IPv6 - force_dnstcp (NEW) -- #########################################
-if has_dnstcp or ( ( m:get(section, "force_dnstcp") or "0" ) ~= "0" ) then
+-- IPv4 + IPv6 - force_dnstcp -- ##############################################
+if DDNS.has_bindhost or ( ( m:get(section, "force_dnstcp") or "0" ) ~= "0" ) then
        tcp = ns:taboption("advanced", Flag, "force_dnstcp",
                translate("Force TCP on DNS") )
        tcp.orientation = "horizontal"
        function tcp.cfgvalue(self, section)
                local value = AbstractValue.cfgvalue(self, section)
-               if not has_dnstcp and value ~= "0" then
+               if not DDNS.has_bindhost and value ~= "0" then
                        self.description = bold_on .. font_red ..
                                translate("DNS requests via TCP not supported") .. font_off .. "<br />" ..
                                translate("please disable") .. " !" .. bold_off
@@ -965,25 +1195,22 @@ if has_dnstcp or ( ( m:get(section, "force_dnstcp") or "0" ) ~= "0" ) then
                return value
        end
        function tcp.validate(self, value)
-               if (value == "1" and has_dnstcp ) or value == "0" then
+               if (value == "1" and DDNS.has_bindhost ) or value == "0" then
                        return value
                end
                return nil, err_tab_adv(self) .. translate("DNS requests via TCP not supported")
        end
-       function tcp.parse(self, section)
-               DDNS.flag_parse(self, section)
-       end
 end
 
--- IPv4 + IPv6 - proxy (NEW) -- ################################################
+-- IPv4 + IPv6 - proxy -- #####################################################
 -- optional Proxy to use for http/https requests  [user:password@]proxyhost[:port]
-if has_proxy or ( ( m:get(section, "proxy") or "" ) ~= "" ) then
+if DDNS.has_proxy or ( ( m:get(section, "proxy") or "" ) ~= "" ) then
        pxy = ns:taboption("advanced", Value, "proxy",
                translate("PROXY-Server") )
        pxy.placeholder="user:password@myproxy.lan:8080"
        function pxy.cfgvalue(self, section)
                local value = AbstractValue.cfgvalue(self, section)
-               if not has_proxy and value ~= "" then
+               if not DDNS.has_proxy and value ~= "" then
                        self.description = bold_on .. font_red ..
                                translate("PROXY-Server not supported") .. font_off .. "<br />" ..
                                translate("please remove entry") .. "!" .. bold_off
@@ -997,13 +1224,15 @@ if has_proxy or ( ( m:get(section, "proxy") or "" ) ~= "" ) then
        end
        function pxy.validate(self, value)
                -- if .datatype is set, then it is checked before calling this function
-               if not value then
+               if not value or (#value == 0) then
                        return ""       -- ignore on empty
-               elseif has_proxy then
+               elseif DDNS.has_proxy then
                        local ipv6  = usev6:formvalue(section) or "0"
-                       local force = (fipv) and fipv:formvalue(section) or "0"
-                       local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh verify_proxy ]] ..
-                               value .. [[ ]] .. ipv6 .. [[ ]] .. force
+                       local force = fipv:formvalue(section) or "0"
+                       local command = CTRL.luci_helper .. [[ -]]
+                       if (ipv6 == 1)  then command = command .. [[6]] end
+                       if (force == 1) then command = command .. [[f]] end
+                       command = command .. [[p ]] .. value .. [[ -- verify_proxy]]
                        local ret = SYS.call(command)
                        if     ret == 0 then return value
                        elseif ret == 2 then return nil, err_tab_adv(self) .. translate("nslookup can not resolve host")
@@ -1016,9 +1245,12 @@ if has_proxy or ( ( m:get(section, "proxy") or "" ) ~= "" ) then
                        return nil, err_tab_adv(self) .. translate("PROXY-Server not supported")
                end
        end
+       function pxy.parse(self, section, novld)
+               DDNS.value_parse(self, section, novld)
+       end
 end
 
--- use_syslog -- ###############################################################
+-- use_syslog -- ##############################################################
 slog = ns:taboption("advanced", ListValue, "use_syslog",
        translate("Log to syslog"),
        translate("Writes log messages to syslog. Critical Errors will always be written to syslog.") )
@@ -1028,26 +1260,24 @@ slog:value("1", translate("Info"))
 slog:value("2", translate("Notice"))
 slog:value("3", translate("Warning"))
 slog:value("4", translate("Error"))
+function slog.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- use_logfile (NEW) -- ########################################################
+-- use_logfile -- #############################################################
 logf = ns:taboption("advanced", Flag, "use_logfile",
        translate("Log to file"),
        translate("Writes detailed messages to log file. File will be truncated automatically.") .. "<br />" ..
-       translate("File") .. [[: "]] .. log_dir .. [[/]] .. section .. [[.log"]] )
+       translate("File") .. [[: "]] .. logdir .. [[/]] .. section .. [[.log"]] )
 logf.orientation = "horizontal"
-logf.rmempty = false   -- we want to save in /etc/config/ddns file on "0" because
-logf.default = "1"     -- if not defined write to log by default
-function logf.parse(self, section)
-       DDNS.flag_parse(self, section)
-end
+logf.default     = "1"         -- if not defined write to log by default
 
--- TAB: Timer  #####################################################################################
--- check_interval -- ###########################################################
+-- TAB: Timer  ####################################################################################
+-- check_interval -- ##########################################################
 ci = ns:taboption("timer", Value, "check_interval",
        translate("Check Interval") )
 ci.template = "ddns/detail_value"
-ci.default  = 10
-ci.rmempty = false     -- validate ourselves for translatable error messages
+ci.default  = "10"
 function ci.validate(self, value)
        if not DTYP.uinteger(value)
        or tonumber(value) < 1 then
@@ -1062,7 +1292,7 @@ function ci.validate(self, value)
        end
 end
 function ci.write(self, section, value)
-       -- simulate rmempty=true remove default
+       -- remove when default
        local secs = DDNS.calc_seconds(value, cu:formvalue(section))
        if secs ~= 600 then     --default 10 minutes
                return self.map:set(section, self.option, value)
@@ -1071,20 +1301,22 @@ function ci.write(self, section, value)
                return self.map:del(section, self.option)
        end
 end
+function ci.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- check_unit -- ###############################################################
+-- check_unit -- ##############################################################
 cu = ns:taboption("timer", ListValue, "check_unit", "not displayed, but needed otherwise error",
        translate("Interval to check for changed IP" .. "<br />" ..
                "Values below 5 minutes == 300 seconds are not supported") )
 cu.template = "ddns/detail_lvalue"
 cu.default  = "minutes"
-cu.rmempty  = false    -- want to control write process
 cu:value("seconds", translate("seconds"))
 cu:value("minutes", translate("minutes"))
 cu:value("hours", translate("hours"))
 --cu:value("days", translate("days"))
 function cu.write(self, section, value)
-       -- simulate rmempty=true remove default
+       -- remove when default
        local secs = DDNS.calc_seconds(ci:formvalue(section), value)
        if secs ~= 600 then     --default 10 minutes
                return self.map:set(section, self.option, value)
@@ -1092,13 +1324,16 @@ function cu.write(self, section, value)
                return true
        end
 end
+function cu.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- force_interval (modified) -- ################################################
+-- force_interval (modified) -- ###############################################
 fi = ns:taboption("timer", Value, "force_interval",
        translate("Force Interval") )
 fi.template = "ddns/detail_value"
-fi.default  = 72       -- see dynamic_dns_updater.sh script
-fi.rmempty = false     -- validate ourselves for translatable error messages
+fi.default  = "72"     -- see dynamic_dns_updater.sh script
+--fi.rmempty = false   -- validate ourselves for translatable error messages
 function fi.validate(self, value)
        if not DTYP.uinteger(value)
        or tonumber(value) < 0 then
@@ -1132,15 +1367,18 @@ function fi.write(self, section, value)
                return self.map:del(section, self.option)
        end
 end
+function fi.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- force_unit -- ###############################################################
+-- force_unit -- ##############################################################
 fu = ns:taboption("timer", ListValue, "force_unit", "not displayed, but needed otherwise error",
        translate("Interval to force updates send to DDNS Provider" .. "<br />" ..
                "Setting this parameter to 0 will force the script to only run once" .. "<br />" ..
                "Values lower 'Check Interval' except '0' are not supported") )
 fu.template = "ddns/detail_lvalue"
 fu.default  = "hours"
-fu.rmempty  = false    -- want to control write process
+--fu.rmempty  = false  -- want to control write process
 --fu:value("seconds", translate("seconds"))
 fu:value("minutes", translate("minutes"))
 fu:value("hours", translate("hours"))
@@ -1154,13 +1392,17 @@ function fu.write(self, section, value)
                return true
        end
 end
+function fu.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- retry_count (NEW) -- ########################################################
-rc = ns:taboption("timer", Value, "retry_count",
-       translate("Error Retry Counter"),
-       translate("On Error the script will stop execution after given number of retrys") )
-rc.default = 5
-rc.rmempty = false     -- validate ourselves for translatable error messages
+-- retry_count -- #############################################################
+rc = ns:taboption("timer", Value, "retry_count")
+rc.title       = translate("Error Retry Counter")
+rc.description = translate("On Error the script will stop execution after given number of retrys")
+               .. "<br />"
+               .. translate("The default setting of '0' will retry infinite.")
+rc.default     = "0"
 function rc.validate(self, value)
        if not DTYP.uinteger(value) then
                return nil, err_tab_timer(self) .. translate("minimum value '0'")
@@ -1168,21 +1410,15 @@ function rc.validate(self, value)
                return value
        end
 end
-function rc.write(self, section, value)
-       -- simulate rmempty=true remove default
-       if tonumber(value) ~= self.default then
-               return self.map:set(section, self.option, value)
-       else
-               return self.map:del(section, self.option)
-       end
+function rc.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
 end
 
--- retry_interval -- ###########################################################
+-- retry_interval -- ##########################################################
 ri = ns:taboption("timer", Value, "retry_interval",
        translate("Error Retry Interval") )
 ri.template = "ddns/detail_value"
-ri.default  = 60
-ri.rmempty  = false    -- validate ourselves for translatable error messages
+ri.default  = "60"
 function ri.validate(self, value)
        if not DTYP.uinteger(value)
        or tonumber(value) < 1 then
@@ -1201,13 +1437,16 @@ function ri.write(self, section, value)
                return self.map:del(section, self.option)
        end
 end
+function ri.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- retry_unit -- ###############################################################
+-- retry_unit -- ##############################################################
 ru = ns:taboption("timer", ListValue, "retry_unit", "not displayed, but needed otherwise error",
        translate("On Error the script will retry the failed action after given time") )
 ru.template = "ddns/detail_lvalue"
 ru.default  = "seconds"
-ru.rmempty  = false    -- want to control write process
+--ru.rmempty  = false  -- want to control write process
 ru:value("seconds", translate("seconds"))
 ru:value("minutes", translate("minutes"))
 --ru:value("hours", translate("hours"))
@@ -1221,14 +1460,17 @@ function ru.write(self, section, value)
                return true -- will be deleted by retry_interval
        end
 end
+function ru.parse(self, section, novld)
+       DDNS.value_parse(self, section, novld)
+end
 
--- TAB: LogView  (NEW) #############################################################################
+-- TAB: LogView  ##################################################################################
 lv = ns:taboption("logview", DummyValue, "_logview")
 lv.template = "ddns/detail_logview"
 lv.inputtitle = translate("Read / Reread log file")
 lv.rows = 50
 function lv.cfgvalue(self, section)
-       local lfile=log_dir .. "/" .. section .. ".log"
+       local lfile=logdir .. "/" .. section .. ".log"
        if NXFS.access(lfile) then
                return lfile .. "\n" .. translate("Please press [Read] button")
        end