Merge pull request #3267 from Ansuel/rework-ddns

luci-app-ddns: convert to client side implementation
This commit is contained in:
Florian Eckert 2019-11-12 08:23:23 +01:00 committed by GitHub
commit 45240e088e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 14047 additions and 20336 deletions

View file

@ -8,30 +8,12 @@
include $(TOPDIR)/rules.mk
# PKG_NAME:=luci-app-ddns
# Version == major.minor.patch
# increase on new functionality (minor) or patches (patch)
PKG_VERSION:=2.4.9
# Release == build
# increase on changes of translation files
PKG_RELEASE:=7
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=Ansuel Smith <ansuelsmth@gmail.com>
# LuCI specific settings
LUCI_TITLE:=LuCI Support for Dynamic DNS Client (ddns-scripts)
LUCI_DEPENDS:=+luci-compat +luci-lib-ipkg +luci-mod-admin-full +ddns-scripts
# LUCI_PKGARCH:=all
define Package/$(PKG_NAME)/config
# shown in make menuconfig <Help>
help
$(LUCI_TITLE)
Version: $(PKG_VERSION)-$(PKG_RELEASE)
endef
LUCI_PKGARCH:=all
include ../../luci.mk

View file

@ -0,0 +1,994 @@
'use strict';
'require uci';
'require rpc';
'require fs';
'require form';
'require tools.widgets as widgets';
var callGetLogServices, callInitAction, callDDnsGetStatus;
var NextUpdateStrings = {};
NextUpdateStrings = {
'Verify' : _("Verify"),
'Run once' : _("Run once"),
'Disabled' : _("Disabled"),
'Stopped' : _("Stopped")
}
var time_res = {};
time_res['seconds'] = 1;
time_res['minutes'] = 60;
time_res['hours'] = 3600;
callGetLogServices = rpc.declare({
object: 'luci.ddns',
method: 'get_services_log',
params: [ 'service_name' ],
expect: { },
});
callInitAction = rpc.declare({
object: 'luci',
method: 'setInitAction',
params: [ 'name', 'action' ],
expect: { result: false }
});
callDDnsGetStatus = rpc.declare({
object: 'luci.ddns',
method: 'get_ddns_state',
expect: { }
});
return L.view.extend({
callDDnsGetEnv: rpc.declare({
object: 'luci.ddns',
method: 'get_env',
expect: { }
}),
callDDnsGetServicesStatus: rpc.declare({
object: 'luci.ddns',
method: 'get_services_status',
expect: { }
}),
poll_status: function(map, data) {
var status = data[1] || [], service = data[0] || [], rows = map.querySelectorAll('.cbi-section-table-row[data-sid]'),
section_id, cfg_detail_ip, cfg_update, cfg_status, host, ip, last_update,
next_update, service_status, reload, cfg_enabled, stop,
ddns_enabled = map.querySelector('[data-name="_enabled"]').querySelector('.cbi-value-field'),
ddns_toggle = map.querySelector('[data-name="_toggle"]').querySelector('button');
ddns_toggle.innerHTML = status['_enabled'] ? _('Stop DDNS') : _('Start DDNS')
L.dom.content(ddns_enabled, function() {
return E([], [
E('div', {}, status['_enabled'] ? _('DDNS Autostart enabled') : [
_('DDNS Autostart disabled'),
E('div', { 'class' : 'cbi-value-description' },
_("Currently DDNS updates are not started at boot or on interface events.") + "<br>" +
_("This is the default if you run DDNS scripts by yourself (i.e. via cron with force_interval set to '0')"))
]),]);
});
for (var i = 0; i < rows.length; i++) {
section_id = rows[i].getAttribute('data-sid');
cfg_detail_ip = rows[i].querySelector('[data-name="_cfg_detail_ip"]');
cfg_update = rows[i].querySelector('[data-name="_cfg_update"]');
cfg_status = rows[i].querySelector('[data-name="_cfg_status"]');
reload = rows[i].querySelector('.cbi-section-actions .reload');
stop = rows[i].querySelector('.cbi-section-actions .stop');
cfg_enabled = uci.get('ddns', section_id, 'enabled');
reload.disabled = (status['_enabled'] == 0 || cfg_enabled == 0);
host = uci.get('ddns', section_id, 'lookup_host') || _('Configuration Error');
ip = _('No Data');
last_update = _('Never');
next_update = _('Unknown');
service_status = '<b>' + _('Not Running') + '</b>';
if (service[section_id]) {
stop.disabled = (!service[section_id].pid || (service[section_id].pid && cfg_enabled == '1'));
if (service[section_id].ip)
ip = service[section_id].ip;
if (service[section_id].last_update)
last_update = service[section_id].last_update;
if (service[section_id].next_update)
next_update = NextUpdateStrings[service[section_id].next_update] || service[section_id].next_update;
if (service[section_id].pid)
service_status = '<b>' + _('Running') + '</b> : ' + service[section_id].pid;
}
cfg_detail_ip.innerHTML = host + '<br>' + ip;
cfg_update.innerHTML = last_update + '<br>' + next_update;
cfg_status.innerHTML = service_status;
}
return;
},
load: function() {
return Promise.all([
this.callDDnsGetServicesStatus(),
callDDnsGetStatus(),
this.callDDnsGetEnv(),
fs.lines('/etc/ddns/services'),
fs.lines('/etc/ddns/services_ipv6'),
uci.load('ddns')
]);
},
render: function(data) {
var resolved = data[0] || [];
var status = data[1] || [];
var env = data[2] || [];
var logdir = uci.get('ddns', 'global', 'ddns_logdir') || "/var/log/ddns";
var services4 = [];
var services6 = [];
data[3].forEach(function(item) {
if (!item.startsWith("#")) {
services4.push(item.split('\t')[0].slice(1,-1));
}
});
data[4].forEach(function(item) {
if (!item.startsWith("#")) {
services6.push(item.split('\t')[0].slice(1,-1));
}
});
var m, s, o;
m = new form.Map('ddns', _('Dynamic DNS'),);
var is = m.section(form.NamedSection, 'global', 'ddns', _('Information'));
s = is;
o = s.option(form.DummyValue, '_version', _('Dynamic DNS Version'));
o.cfgvalue = function() {
return status[this.option];
};
o = s.option(form.DummyValue, '_enabled', _('State'));
o.cfgvalue = function() {
var res = status[this.option];
if (!res) {
this.description = _("Currently DDNS updates are not started at boot or on interface events.") + "<br>" +
_("This is the default if you run DDNS scripts by yourself (i.e. via cron with force_interval set to '0')")
}
return res ? _('DDNS Autostart enabled') : _('DDNS Autostart disabled')
};
o = s.option(form.DummyValue, '_toggle', '&nbsp');
o.cfgvalue = function() {
var action = status['_enabled'] ? 'stop' : 'start';
return E([], [
E('button', {
'class': 'cbi-button cbi-button-apply',
'click': L.ui.createHandlerFn(this, function() {
return callDDnsGetStatus().then(L.bind(function(data) {
return callInitAction('ddns', action == 'stop' ? 'disable' : 'enable').then(function() {
return callInitAction('ddns', action);
});
}, this)).then(L.bind(m.render, m));
})
}, _(action.toUpperCase() + ' DDns'))]);
};
o = s.option(form.DummyValue, '_restart', '&nbsp');
o.cfgvalue = function() {
return E([], [
E('button', {
'class': 'cbi-button cbi-button-apply',
'click': L.ui.createHandlerFn(this, function() {
return callInitAction('ddns', 'restart').then(L.bind(m.render, m));
})
}, _('Restart DDns'))]);
};
// DDns hints
if (!env['has_ipv6']) {
o = s.option(form.DummyValue, '_no_ipv6');
o.rawhtml = true;
o.title = '<b>' + _("IPv6 not supported") + '</b>';
o.cfgvalue = function() { return _("IPv6 is currently not (fully) supported by this system") + "<br>" +
_("Please follow the instructions on OpenWrt's homepage to enable IPv6 support") + "<br>" +
_("or update your system to the latest OpenWrt Release")};
}
if (!env['has_ssl']) {
o = s.option(form.DummyValue, '_no_https');
o.titleref = L.url("admin", "system", "opkg")
o.rawhtml = true;
o.title = '<b>' + _("HTTPS not supported") + '</b>';
o.cfgvalue = function() { return _("Neither GNU Wget with SSL nor cURL installed to support secure updates via HTTPS protocol.") +
"<br />- " +
_("You should install 'wget' or 'curl' or 'uclient-fetch' with 'libustream-*ssl' package.") +
"<br />- " +
_("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")};
}
if (!env['has_bindnet']) {
o = s.option(form.DummyValue, '_no_bind_network');
o.titleref = L.url("admin", "system", "opkg")
o.rawhtml = true;
o.title = '<b>' + _("Binding to a specific network not supported") + '</b>';
o.cfgvalue = function() { return _("Neither GNU Wget with SSL nor cURL installed to select a network to use for communication.") +
"<br />- " +
_("You should install 'wget' or 'curl' package.") +
"<br />- " +
_("GNU Wget will use the IP of given network, cURL will use the physical interface.") +
"<br />- " +
_("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")};
}
if (!env['has_proxy']) {
o = s.option(form.DummyValue, '_no_proxy');
o.titleref = L.url("admin", "system", "opkg")
o.rawhtml = true;
o.title = '<b>' + _("cURL without Proxy Support") + '</b>';
o.cfgvalue = function() { return _("cURL is installed, but libcurl was compiled without proxy support.") +
"<br />- " +
_("You should install 'wget' or 'uclient-fetch' package or replace libcurl.") +
"<br />- " +
_("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")};
}
if (!env['has_forceip']) {
o = s.option(form.DummyValue, '_no_force_ip');
o.titleref = L.url("admin", "system", "opkg")
o.rawhtml = true;
o.title = '<b>' + _("Force IP Version not supported") + '</b>';
o.cfgvalue = function() { return _("BusyBox's nslookup and Wget do not support to specify " +
"the IP version to use for communication with DDNS Provider!") +
"<br />- " + _("You should install 'wget' or 'curl' or 'uclient-fetch' package.")
};
}
if (!env['has_bindhost']) {
o = s.option(form.DummyValue, '_no_dnstcp');
o.titleref = L.url("admin", "system", "opkg")
o.rawhtml = true;
o.title = '<b>' + _("DNS requests via TCP not supported") + '</b>';
o.cfgvalue = function() { return _("BusyBox's nslookup and hostip do not support to specify to use TCP " +
"instead of default UDP when requesting DNS server!") +
"<br />- " +
_("You should install 'bind-host' or 'knot-host' or 'drill' package for DNS requests.")};
}
if (!env['has_dnsserver']) {
o = s.option(form.DummyValue, '_no_dnsserver');
o.titleref = L.url("admin", "system", "opkg")
o.rawhtml = true;
o.title = '<b>' + _("Using specific DNS Server not supported") + '</b>';
o.cfgvalue = function() { return _("BusyBox's nslookup in the current compiled version " +
"does not handle given DNS Servers correctly!") +
"<br />- " +
_("You should install 'bind-host' or 'knot-host' or 'drill' or 'hostip' package, " +
"if you need to specify a DNS server to detect your registered IP.")};
}
if (env['has_ssl'] && !env['has_cacerts']) {
o = s.option(form.DummyValue, '_no_certs');
o.titleref = L.url("admin", "system", "opkg")
o.rawhtml = true;
o.title = '<b>' + _("No certificates found") + '</b>';
o.cfgvalue = function() { return _("If using secure communication you should verify server certificates!") +
"<br />- " +
_("Install 'ca-certificates' package or needed certificates " +
"by hand into /etc/ssl/certs default directory")};
}
// DDns services
s = m.section(form.GridSection, 'service', _('Services'));
s.anonymous = true;
s.addremove = true;
s.addbtntitle = _('Add new services...');
s.anonymous = true;
s.addremove = true;
s.sortable = true;
s.handleAdd = function(ev) {
var m2 = new form.Map('ddns'),
s2 = m2.section(form.NamedSection, '_new_');
s2.render = function() {
return Promise.all([
{},
this.renderUCISection('_new_')
]).then(this.renderContents.bind(this));
};
name = s2.option(form.Value, 'name', _('Name'));
name.rmempty = false;
name.datatype = 'uciname';
name.placeholder = _('New DDns Service…');
name.validate = function(section_id, value) {
if (uci.get('ddns', value) != null)
return _('The service name is already used');
return true;
};
m2.render().then(L.bind(function(nodes) {
L.ui.showModal(_('Add new services...'), [
nodes,
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': L.ui.hideModal
}, _('Cancel')), ' ',
E('button', {
'class': 'cbi-button cbi-button-positive important',
'click': L.ui.createHandlerFn(this, function(ev) {
var nameval = name.isValid('_new_') ? name.formvalue('_new_') : null;
if (nameval == null || nameval == '')
return;
return m.save(function() {
uci.add('ddns', 'service', nameval);
}).then(L.bind(m.children[1].renderMoreOptionsModal, m.children[1], nameval));
})
}, _('Create service'))
])
], 'cbi-modal');
nodes.querySelector('[id="%s"] input[type="text"]'.format(name.cbid('_new_'))).focus();
}, this));
};
s.renderRowActions = function(section_id) {
var tdEl = this.super('renderRowActions', [ section_id, _('Edit') ]),
cfg_enabled = uci.get('ddns', section_id, 'enabled'),
reload_opt = {
'class': 'cbi-button cbi-button-neutral reload',
'click': L.ui.createHandlerFn(this, function() {
return fs.exec('/usr/lib/ddns/dynamic_dns_lucihelper.sh',
[ '-S', section_id, '--', 'start' ]).then(L.bind(m.render, m));
}),
'title': _('Reload this service'),
},
stop_opt = {
'class': 'cbi-button cbi-button-neutral stop',
'click': L.ui.createHandlerFn(this, function() {
return fs.exec('/usr/lib/ddns/dynamic_dns_lucihelper.sh',
[ '-S', section_id, '--', 'start' ]).then(L.bind(m.render, m));
}),
'title': _('Stop this service'),
};
if (status['_enabled'] == 0 || cfg_enabled == 0)
reload_opt['disabled'] = 'disabled';
if (!resolved[section_id] || !resolved[section_id].pid ||
(resolved[section_id].pid && cfg_enabled == '1'))
stop_opt['disabled'] = 'disabled';
L.dom.content(tdEl.lastChild, [
E('button', stop_opt, _('Stop')),
E('button', reload_opt, _('Reload')),
tdEl.lastChild.childNodes[0],
tdEl.lastChild.childNodes[1],
tdEl.lastChild.childNodes[2]
]);
return tdEl;
};
o = s.option(form.DummyValue, '_cfg_name', _('Name'));
o.modalonly = false;
o.textvalue = function(section_id) {
return '<b>' + section_id + '</b>';
}
o = s.option(form.DummyValue, '_cfg_detail_ip', _('Lookup Hostname') + "<br>" + _('Registered IP'));
o.rawhtml = true;
o.modalonly = false;
o.textvalue = function(section_id) {
var host = uci.get('ddns', section_id, 'lookup_host') || _('Configuration Error'),
ip = _('No Data');
if (resolved[section_id] && resolved[section_id].ip)
ip = resolved[section_id].ip;
return host + '<br>' + ip;
};
o = s.option(form.Flag, 'enabled', _('Enabled'));
o.rmempty = false;
o.editable = true;
o.modalonly = false;
o = s.option(form.DummyValue, '_cfg_update', _('Last Update') + "<br>" + _('Next Update'));
o.rawhtml = true;
o.modalonly = false;
o.textvalue = function(section_id) {
var last_update = _('Never'), next_update = _('Unknown');
if (resolved[section_id]) {
if (resolved[section_id].last_update)
last_update = resolved[section_id].last_update;
if (resolved[section_id].next_update)
next_update = NextUpdateStrings[resolved[section_id].next_update] || resolved[section_id].next_update;
}
return last_update + '<br>' + next_update;
};
s.modaltitle = function(section_id) {
return _('DDns Service') + ' » ' + section_id;
};
o = s.option(form.DummyValue, '_cfg_status', _('Status'));
o.modalonly = false;
o.textvalue = function(section_id) {
var text = '<b>' + _('Not Running') + '</b>';
if (resolved[section_id] && resolved[section_id].pid)
text = '<b>' + _('Running') + '</b> : ' + resolved[section_id].pid;
return text;
}
s.tab('basic', _('Basic Settings'));
s.tab('advanced', _('Advanced Settings'));
s.tab('timer', _('Timer Settings'));
s.tab('logview', _('Log File Viewer'));
// TAB: BASIC
// enabled
o = s.taboption('basic', form.Flag, 'enabled', _('Enabled'),_("If this service section is disabled it could not be started." + "<br />" +
"Neither from LuCI interface nor from console"));
o.modalonly = true;
o.rmempty = false;
o.default = '1';
// lookup_host
o = s.taboption('basic', form.Value, 'lookup_host', _("Lookup Hostname"),
_("Hostname/FQDN to validate, if IP update happen or necessary") );
o.rmempty = false;
o.placeholder = "myhost.example.com";
o.datatype = 'and(minlength(3),hostname("strict"))';
o.modalonly = true;
// use_ipv6
o = s.taboption('basic', form.ListValue, 'use_ipv6', _("IP address version"),
_("Defines which IP address 'IPv4/IPv6' is send to the DDNS provider"));
o.default = '0';
o.modalonly = true;
o.rmempty = false;
o.value("0", _("IPv4-Address"))
if (env["has_ipv6"])
o.value("1", _("IPv6-Address"))
// service_name
o = s.taboption('basic', form.ListValue, 'ipv4_service_name', _("DDNS Service provider") + " [IPv4]");
o.depends("use_ipv6", "0")
o.modalonly = true;
for (var i = 0; i < services4.length; i++)
o.value(services4[i]);
o.value('-',"-- " + _("custom") + " --");
o.cfgvalue = function(section_id) {
return uci.get('ddns', section_id, 'service_name');
}
o.write = function(section_id, formvalue) {
if (formvalue != '-') {
uci.set('ddns', section_id, 'update_url', null);
uci.set('ddns', section_id, 'update_script', null);
return uci.set('ddns', section_id, 'service_name', formvalue);
}
return uci.set('ddns', section_id, 'service_name', null);
};
o = s.taboption('basic', form.ListValue, 'ipv6_service_name', _("DDNS Service provider") + " [IPv6]");
o.depends("use_ipv6", "1")
o.modalonly = true;
for (var i = 0; i < services6.length; i++)
o.value(services6[i]);
o.value('-',"-- " + _("custom") + " --");
o.cfgvalue = function(section_id) {
return uci.get('ddns', section_id, 'service_name');
}
o.write = function(section_id, formvalue) {
if (formvalue != '-') {
uci.set('ddns', section_id, 'update_url', null);
uci.set('ddns', section_id, 'update_script', null);
return uci.set('ddns', section_id, 'service_name', formvalue);
}
return uci.set('ddns', section_id, 'service_name', null);
};
// update_url
o = s.taboption('basic', form.Value, 'update_url', _("Custom update-URL"),
_("Update URL to be used for updating your DDNS Provider." + "<br />" +
"Follow instructions you will find on their WEB page."));
o.modalonly = true;
o.rmempty = false;
o.depends("ipv6_service_name","-");
o.depends("ipv4_service_name","-");
// update_script
o = s.taboption('basic', form.Value, 'update_script', _("Custom update-script"),
_("Custom update script to be used for updating your DDNS Provider."));
o.modalonly = true;
o.rmempty = false;
o.depends("ipv6_service_name","-");
o.depends("ipv4_service_name","-");
// domain
o = s.taboption('basic', form.Value, 'domain', _("Domain"),
_("Replaces [USERNAME] in Update-URL (URL-encoded)"));
o.modalonly = true;
o.rmempty = false;
// username
o = s.taboption('basic', form.Value, 'username', _("Username"),
_("Replaces [USERNAME] in Update-URL (URL-encoded)"));
o.modalonly = true;
o.rmempty = false;
// password
o = s.taboption('basic', form.Value, 'password', _("Password"),
_("Replaces [PASSWORD] in Update-URL (URL-encoded)"));
o.password = true;
o.modalonly = true;
o.rmempty = false;
// param_enc
o = s.taboption('basic', form.Value, 'param_enc', _("Optional Encoded Parameter"),
_("Optional: Replaces [PARAMENC] in Update-URL (URL-encoded)"));
o.optional = true;
o.modalonly = true;
// param_opt
o = s.taboption('basic', form.Value, 'param_opt', _("Optional Parameter"),
_("Optional: Replaces [PARAMOPT] in Update-URL (NOT URL-encoded)"));
o.optional = true;
o.modalonly = true;
// use_https
if (env['has_ssl']) {
o = s.taboption('basic', form.Flag, 'use_https', _("Use HTTP Secure"),
_("Enable secure communication with DDNS provider"));
o.optional = true;
o.modalonly = true;
o = s.taboption('basic', form.Value, 'cacert',
_("Path to CA-Certificate"),
_("directory or path/file") + "<br>" +
_("or") + '<b>' + " IGNORE " + '</b>' +
_("to run HTTPS without verification of server certificates (insecure)"));
o.modalonly = true;
o.depends("use_https", "1");
o.placeholder = "/etc/ssl/certs";
o.rmempty = false;
};
// TAB Advanced
// ip_source
o = s.taboption('advanced', form.ListValue, 'ip_source', _("IP address source"),
_("Defines the source to read systems IP-Address from, that will be send to the DDNS provider"));
o.modalonly = true;
o.default = "network";
o.value("network", _("Network"));
o.value("web", _("URL"));
o.value("interface", _("Interface"));
o.value("script", _("Script"));
o.write = function(section_id, formvalue) {
switch(formvalue) {
case 'network':
uci.set('ddns', section_id, "ip_url",null);
uci.set('ddns', section_id, "ip_interface",null);
uci.set('ddns', section_id, "ip_script",null);
break;
case 'web':
uci.set('ddns', section_id, "ip_network",null);
uci.set('ddns', section_id, "ip_interface",null);
uci.set('ddns', section_id, "ip_script",null);
break;
case 'interface':
uci.set('ddns', section_id, "ip_network",null);
uci.set('ddns', section_id, "ip_url",null);
uci.set('ddns', section_id, "ip_script",null);
break;
case 'script':
uci.set('ddns', section_id, "ip_network",null);
uci.set('ddns', section_id, "ip_url",null);
uci.set('ddns', section_id, "ip_interface",null);
break;
default:
break;
};
return uci.set('ddns', section_id, 'ip_source', formvalue )
};
// ip_network
o = s.taboption('advanced', widgets.ZoneSelect, 'ip_network', _("Network"),
_("Defines the network to read systems IP-Address from"));
o.depends('ip_source','network');
o.modalonly = true;
o.default = 'wan';
o.multiple = false;
// ip_url
o = s.taboption('advanced', form.Value, 'ip_url', _("URL to detect"),
_("Defines the Web page to read systems IP-Address from" + '<br>' +
_('Example for IPv4' + ': http://checkip.dyndns.com') + '<br>' +
_('Example for IPv6' + ': http://checkipv6.dyndns.com')));
o.depends("ip_source", "web")
o.modalonly = true;
// ip_interface
o = s.taboption('advanced', widgets.ZoneSelect, 'ip_interface', _("Interface"),
_("Defines the interface to read systems IP-Address from"));
o.modalonly = true;
o.depends("ip_source", "interface")
o.multiple = false;
o.default = 'wan';
// ip_script
o = s.taboption('advanced', form.Value, 'ip_script', _("Script"),
_("User defined script to read systems IP-Address"));
o.modalonly = true;
o.depends("ip_source", "script")
o.placeholder = "/path/to/script.sh"
// interface
o = s.taboption('advanced', widgets.ZoneSelect, 'interface', _("Event Network"),
_("Network on which the ddns-updater scripts will be started"));
o.modalonly = true;
o.multiple = false;
o.default = 'wan';
o.depends("ip_source", "web");
o.depends("ip_source", "script");
// interface_show
o = s.taboption('advanced', form.DummyValue, '_interface', _("Event Network"),
_("Network on which the ddns-updater scripts will be started"));
o.depends("ip_source", "interface");
o.depends("ip_source", "network");
o.forcewrite = true;
o.modalonly = true;
o.cfgvalue = function(section_id) {
return uci.get('ddns', section_id, 'interface') || _('This will be autoset to the selected interface');
};
o.write = function(section_id) {
var opt = this.section.children.filter(function(o) { return o.option == 'ip_source' })[0].formvalue(section_id);
var val = this.section.children.filter(function(o) { return o.option == 'ip_'+opt })[0].formvalue(section_id);
return uci.set('ddns', section_id, 'interface', val);
};
// bind_network
if (env['has_bindnet']) {
o = s.taboption('advanced', widgets.ZoneSelect, 'bind_network', _("Bind Network"),
_('OPTIONAL: Network to use for communication') + '<br>' +
_("Network on which the ddns-updater scripts will be started"));
o.depends("ip_source", "web");
o.optional = true;
o.rmempty = true;
o.modalonly = true;
}
// force_ipversion
if (env['has_forceip']) {
o = s.taboption('advanced', form.Flag, 'force_ipversion', _("Force IP Version"),
_('OPTIONAL: Force the usage of pure IPv4/IPv6 only communication.'));
o.optional = true;
o.rmempty = true;
o.modalonly = true;
}
// dns_server
if (env['has_dnsserver']) {
o = s.taboption("advanced", form.Value, "dns_server",
_("DNS-Server"),
_("OPTIONAL: Use non-default DNS-Server to detect 'Registered IP'.") + "<br />" +
_("Format: IP or FQDN"));
o.placeholder = "mydns.lan"
o.optional = true;
o.rmempty = true;
o.modalonly = true;
}
// force_dnstcp
if (env['has_bindhost']) {
o = s.taboption("advanced", form.Flag, "force_dnstcp",
_("Force TCP on DNS"),
_("OPTIONAL: Force the use of TCP instead of default UDP on DNS requests."));
o.optional = true;
o.rmempty = true;
o.modalonly = true;
}
// proxy
if (env['has_proxy']) {
o = s.taboption("advanced", form.Value, "proxy", _("PROXY-Server"),
_("OPTIONAL: Proxy-Server for detection and updates.") + "<br />" +
_("Format") + ": " + '<b>' + "[user:password@]proxyhost:port" + '</b>' + "<br />" +
_("IPv6 address must be given in square brackets") + ": " +
'<b>' + " [2001:db8::1]:8080" + '</b>');
o.optional = true;
o.rmempty = true;
o.modalonly = true;
}
// use_syslog
o = s.taboption("advanced", form.ListValue, "use_syslog", _("Log to syslog"),
_("Writes log messages to syslog. Critical Errors will always be written to syslog."));
o.modalonly = true;
o.placeholder = "2"
o.optional = true;
o.value("0", _("No logging"))
o.value("1", _("Info"))
o.value("2", _("Notice"))
o.value("3", _("Warning"))
o.value("4", _("Error"))
// use_logfile
o = s.taboption("advanced", form.Flag, "use_logfile", _("Log to file"));
o.default = '1';
o.optional = true;
o.modalonly = true;
o.cfgvalue = function(section_id) {
this.description = _("Writes detailed messages to log file. File will be truncated automatically.") + "<br>" +
_("File") + ': "' + logdir + '/' + section_id + '.log"';
return uci.get('ddns', section_id, 'use_logfile');
};
// TAB Timer
// check_interval
o = s.taboption("timer", form.Value, "check_interval", _("Check Interval"));
o.placeholder = "30";
o.modalonly = true;
o.datatype = 'uinteger';
o.validate = function(section_id, formvalue) {
var unit = this.section.children.filter(function(o) { return o.option == 'check_unit' })[0].formvalue(section_id),
time_to_sec = time_res[unit || 'minutes'] * formvalue;
if (formvalue && time_to_sec < 300)
return _('Values below 5 minutes == 300 seconds are not supported');
return true;
};
// check_interval
o = s.taboption("timer", form.ListValue, "check_unit",'Check Unit');
o.description = _("Interval unit to check for changed IP");
o.modalonly = true;
o.default = "minutes"
o.value("seconds", _("seconds"));
o.value("minutes", _("minutes"));
o.value("hours", _("hours"));
// force_interval
o = s.taboption("timer", form.Value, "force_interval", _("Force Interval"));
o.description = _("Interval to force updates send to DDNS Provider" + "<br />" +
"Setting this parameter to 0 will force the script to only run once");
o.placeholder = "72";
o.optional = true;
o.modalonly = true;
o.datatype = 'uinteger';
o.validate = function(section_id, formvalue) {
if (!formvalue)
return true;
var check_unit = this.section.children.filter(function(o) { return o.option == 'check_unit' })[0].formvalue(section_id),
check_val = this.section.children.filter(function(o) { return o.option == 'check_interval' })[0].formvalue(section_id),
force_unit = this.section.children.filter(function(o) { return o.option == 'force_unit' })[0].formvalue(section_id),
check_to_sec = time_res[check_unit || 'minutes'] * ( check_val || '30'),
force_to_sec = time_res[force_unit || 'minutes'] * formvalue;
if (force_to_sec != 0 && force_to_sec < check_to_sec)
return _("Values lower 'Check Interval' except '0' are not supported");
return true;
};
// force_unit
o = s.taboption("timer", form.ListValue, "force_unit",'Force Unit');
o.description = _("Interval unit to force updates send to DDNS Provider");
o.modalonly = true;
o.optional = true;
o.default = "minutes"
o.value("minutes", _("minutes"));
o.value("hours", _("hours"));
o.value("hours", _("days"));
// retry_count
o = s.taboption("timer", form.Value, "retry_count", _("Error Retry Counter"));
o.description = _("On Error the script will stop execution after given number of retrys")
+ "<br />"
+ _("The default setting of '0' will retry infinite.");
o.placeholder = "0";
o.optional = true;
o.modalonly = true;
o.datatype = 'uinteger';
// retry_interval
o = s.taboption("timer", form.Value, "retry_interval", _("Error Retry Interval"));
o.description = _("On Error the script will stop execution after given number of retrys")
+ "<br />"
+ _("The default setting of '0' will retry infinite.");
o.placeholder = "60";
o.optional = true;
o.modalonly = true;
o.datatype = 'uinteger';
// retry_unit
o = s.taboption("timer", form.ListValue, "retry_unit",'Retry Unit');
o.description = _("On Error the script will retry the failed action after given time");
o.modalonly = true;
o.optional = true;
o.default = "seconds"
o.value("seconds", _("seconds"));
o.value("minutes", _("minutes"));
// TAB logview
o = s.taboption("logview", form.DummyValue, '_read_log', '');
o.depends('use_logfile','1');
o.modalonly = true;
o.cfgvalue = function(section_id) {
return E([], [
E('button', {
'class': 'cbi-button cbi-button-apply',
'click': L.ui.createHandlerFn(this, function() {
var o = this.section.children.filter(function(o) { return o.option == '_logview' })[0];
return callGetLogServices(section_id).then(L.bind(o.update_log, o));
})
}, _('Read / Reread log file'))]);
};
o = s.taboption("logview", form.DummyValue, "_logview");
o.depends('use_logfile','1');
o.modalonly = true;
o.update_log = L.bind(function(view, log_data) {
return document.getElementById('log_area').innerHTML = log_data.result;
}, o, this)
o.render = L.bind(function() {
return E([
E('p', {}, _('This is the current content of the log file in ') + logdir + ' for this service.'),
E('p', {}, E('textarea', { 'style': 'width:100%', 'rows': 20, 'readonly' : 'readonly', 'id' : 'log_area' }, _('Please press [Read] button') ))
]);
}, o, this)
// Advanced Configuration Section
s = m.section(form.NamedSection, 'global', 'ddns', _('Global Configuration'));
s.description = _('Configure here the details for all Dynamic DNS services including this LuCI application.')
+ '<br /><strong>'
+ _("It is NOT recommended for casual users to change settings on this page.")
+ '</strong><br />'
+ '<a href="https://openwrt.org/docs/guide-user/base-system/ddns#section_ddns" target="_blank">'
+ _('For detailed information about parameter settings look here.')
+ '</a>';
s.addremove = false;
o = s.option(form.Flag, 'upd_privateip', _("Allow non-public IP's"));
o.description = _("Non-public and by default blocked IP's") + ':'
+ '<br /><strong>IPv4: </strong>'
+ '0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192.168/16'
+ '<br /><strong>IPv6: </strong>'
+ '::/32, f000::/4"';
o.default = "0";
o.optional = true;
o = s.option(form.Value, 'ddns_dateformat', _('Date format'));
o.description = '<a href="http://www.cplusplus.com/reference/ctime/strftime/" target="_blank">'
+ _("For supported codes look here")
+ '</a><br>' +
_('Current setting: ') + '<b>' + status['_curr_dateformat'] + '</b>';
o.default = "%F %R"
o.optional = true;
o.rmempty = true;
o = s.option(form.Value, 'ddns_rundir', _('Status directory'));
o.description = _('Directory contains PID and other status information for each running section.');
o.default = "/var/run/ddns";
o.optional = true;
o.rmempty = true;
o = s.option(form.Value, 'ddns_logdir', _('Log directory'));
o.description = _('Directory contains Log files for each running section.');
o.default = "/var/log/ddns";
o.optional = true;
o.rmempty = true;
o.validate = function(section_id, formvalue) {
if (formvalue.indexOf('../') !== -1)
return _('"../" not allowed in path for Security Reason.')
return true;
}
o = s.option(form.Value, 'ddns_loglines', _('Log length'));
o.description = _('Number of last lines stored in log files');
o.datatype = 'min(1)';
o.default = '250';
if (env['has_wget'] && env['has_curl']) {
o = s.option(form.Flag, 'use_curl', _('Use cURL'));
o.description = _('If Wget and cURL package are installed, Wget is used for communication by default.');
o.default = "0";
o.optional = true;
o.rmempty = true;
}
return m.render().then(L.bind(function(m, nodes) {
L.Poll.add(L.bind(function() {
return Promise.all([
this.callDDnsGetServicesStatus(),
callDDnsGetStatus()
]).then(L.bind(this.poll_status, this, nodes));
}, this), 5);
return nodes;
}, this, m));
}
});

View file

@ -0,0 +1,46 @@
'use strict';
'require rpc';
'require uci';
return L.Class.extend({
title: _('Dynamic DNS'),
callDDnsGetServicesStatus: rpc.declare({
object: 'luci.ddns',
method: 'get_services_status',
expect: { }
}),
load: function() {
return Promise.all([
this.callDDnsGetServicesStatus(),
uci.load('ddns')
]);
},
render: function(data) {
var services = data[0];
var table = E('div', { 'class': 'table' }, [
E('div', { 'class': 'tr table-titles' }, [
E('div', { 'class': 'th' }, _('Configuration')),
E('div', { 'class': 'th' }, _('Next Update')),
E('div', { 'class': 'th' }, _('Lookup Hostname')),
E('div', { 'class': 'th' }, _('Registered IP')),
E('div', { 'class': 'th' }, _('Network'))
])
]);
cbi_update_table(table, Object.keys(services).map(function(key, index) {
return [
key,
services[key].next_update ? _(services[key].next_update) : _('Unknown'),
uci.get('ddns',key,'lookup_host'),
services[key].ip ? services[key].ip : _('No Data'),
(uci.get('ddns',key,'use_ipv6') == '1' ? 'IPv6' : 'IPv4') + ' / ' + uci.get('ddns',key,'interface')
];
}), E('em', _('There is no service configured.')));
return E([table]);
}
});

View file

@ -6,334 +6,6 @@
module("luci.controller.ddns", package.seeall)
local NX = require "nixio"
local NXFS = require "nixio.fs"
local DISP = require "luci.dispatcher"
local HTTP = require "luci.http"
local I18N = require "luci.i18n" -- not globally avalible here
local IPKG = require "luci.model.ipkg"
local SYS = require "luci.sys"
local UCI = require "luci.model.uci"
local UTIL = require "luci.util"
local DDNS = require "luci.tools.ddns" -- ddns multiused functions
luci_helper = "/usr/lib/ddns/dynamic_dns_lucihelper.sh"
local srv_name = "ddns-scripts"
local srv_ver_min = "2.7.7" -- minimum version of service required
local app_name = "luci-app-ddns"
local app_title = "Dynamic DNS"
local app_version = "2.4.9-1"
local translate = I18N.translate
function index()
local nxfs = require "nixio.fs" -- global definitions not available
local sys = require "luci.sys" -- in function index()
local muci = require "luci.model.uci"
-- no config create an empty one
if not nxfs.access("/etc/config/ddns") then
nxfs.writefile("/etc/config/ddns", "")
end
-- preset new option "lookup_host" if not already defined
local uci = muci.cursor()
local commit = false
uci:foreach("ddns", "service", function (s)
if not s["lookup_host"] and s["domain"] then
uci:set("ddns", s[".name"], "lookup_host", s["domain"])
commit = true
end
end)
if commit then uci:commit("ddns") end
uci:unload("ddns")
entry( {"admin", "services", "ddns"}, cbi("ddns/overview"), _("Dynamic DNS"), 59)
entry( {"admin", "services", "ddns", "detail"}, cbi("ddns/detail"), nil ).leaf = true
entry( {"admin", "services", "ddns", "hints"}, cbi("ddns/hints",
{hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), nil ).leaf = true
entry( {"admin", "services", "ddns", "global"}, cbi("ddns/global"), nil ).leaf = true
entry( {"admin", "services", "ddns", "logview"}, call("logread") ).leaf = true
entry( {"admin", "services", "ddns", "startstop"}, post("startstop") ).leaf = true
entry( {"admin", "services", "ddns", "status"}, call("status") ).leaf = true
entry( {"admin", "services", "ddns"}, view("ddns/overview"), _("Dynamic DNS"), 59)
end
-- Application specific information functions
function app_description()
local tmp = {}
tmp[#tmp+1] = translate("Dynamic DNS allows that your router can be reached with \
a fixed hostname while having a dynamically changing IP address.")
tmp[#tmp+1] = [[<br />]]
tmp[#tmp+1] = translate("OpenWrt Wiki") .. ": "
tmp[#tmp+1] = [[<a href="https://openwrt.org/docs/guide-user/services/ddns/client" target="_blank">]]
tmp[#tmp+1] = translate("DDNS Client Documentation")
tmp[#tmp+1] = [[</a>]]
tmp[#tmp+1] = " --- "
tmp[#tmp+1] = [[<a href="https://openwrt.org/docs/guide-user/base-system/ddns" target="_blank">]]
tmp[#tmp+1] = translate("DDNS Client Configuration")
tmp[#tmp+1] = [[</a>]]
return table.concat(tmp)
end
function app_title_back()
local tmp = {}
tmp[#tmp+1] = [[<a href="]]
tmp[#tmp+1] = DISP.build_url("admin", "services", "ddns")
tmp[#tmp+1] = [[">]]
tmp[#tmp+1] = translate(app_title)
tmp[#tmp+1] = [[</a>]]
return table.concat(tmp)
end
-- Standardized application/service functions
function app_title_main()
local tmp = {}
tmp[#tmp+1] = [[<a href="javascript:alert(']]
tmp[#tmp+1] = translate("Version Information")
tmp[#tmp+1] = [[\n\n]] .. app_name
tmp[#tmp+1] = [[\n]] .. translate("Version") .. [[: ]] .. app_version
tmp[#tmp+1] = [[\n\n]] .. srv_name .. [[ ]] .. translate("required") .. [[:]]
tmp[#tmp+1] = [[\n]] .. translate("Version") .. [[: ]]
tmp[#tmp+1] = srv_ver_min .. [[ ]] .. translate("or higher")
tmp[#tmp+1] = [[\n\n]] .. srv_name .. [[ ]] .. translate("installed") .. [[:]]
tmp[#tmp+1] = [[\n]] .. translate("Version") .. [[: ]]
tmp[#tmp+1] = (service_version() or translate("NOT installed"))
tmp[#tmp+1] = [[\n\n]]
tmp[#tmp+1] = [[')">]]
tmp[#tmp+1] = translate(app_title)
tmp[#tmp+1] = [[</a>]]
return table.concat(tmp)
end
function service_version()
local srv_ver_cmd = luci_helper .. " -V | awk {'print $2'} "
local ver
if IPKG then
ver = IPKG.info(srv_name)[srv_name].Version
else
ver = UTIL.exec(srv_ver_cmd)
end
if ver and #ver > 0 then return ver or nil end
end
function service_ok()
return IPKG.compare_versions((service_version() or "0"), ">=", srv_ver_min)
end
-- internal function to read all sections status and return data array
local function _get_status()
local uci = UCI.cursor()
local service = SYS.init.enabled("ddns") and 1 or 0
local url_start = DISP.build_url("admin", "system", "startup")
local data = {} -- Array to transfer data to javascript
data[#data+1] = {
enabled = service, -- service enabled
url_up = url_start, -- link to enable DDS (System-Startup)
}
uci:foreach("ddns", "service", function (s)
-- Get section we are looking at
-- and enabled state
local section = s[".name"]
local enabled = tonumber(s["enabled"]) or 0
local datelast = "_empty_" -- formatted date of last update
local datenext = "_empty_" -- formatted date of next update
local datenextstat = nil
-- get force seconds
local force_seconds = DDNS.calc_seconds(
tonumber(s["force_interval"]) or 72 ,
s["force_unit"] or "hours" )
-- get/validate pid and last update
local pid = DDNS.get_pid(section)
local uptime = SYS.uptime()
local lasttime = DDNS.get_lastupd(section)
if lasttime > uptime then -- /var might not be linked to /tmp
lasttime = 0 -- and/or not cleared on reboot
end
-- no last update happen
if lasttime == 0 then
datelast = "_never_"
-- we read last update
else
-- calc last update
-- sys.epoch - sys uptime + lastupdate(uptime)
local epoch = os.time() - uptime + lasttime
-- use linux date to convert epoch
datelast = DDNS.epoch2date(epoch)
-- calc and fill next update
datenext = DDNS.epoch2date(epoch + force_seconds)
end
-- process running but update needs to happen
-- problems if force_seconds > uptime
force_seconds = (force_seconds > uptime) and uptime or force_seconds
if pid > 0 and ( lasttime + force_seconds - uptime ) <= 0 then
datenext = "_verify_"
datenextstat = translate("Verify")
-- run once
elseif force_seconds == 0 then
datenext = "_runonce_"
datenextstat = translate("Run once")
-- no process running and NOT enabled
elseif pid == 0 and enabled == 0 then
datenext = "_disabled_"
datenextstat = translate("Disabled")
-- no process running and enabled
elseif pid == 0 and enabled ~= 0 then
datenext = "_stopped_"
datenextstat = translate("Stopped")
end
-- get/set monitored interface and IP version
local iface = s["interface"] or "wan"
local use_ipv6 = tonumber(s["use_ipv6"]) or 0
local ipv = (use_ipv6 == 1) and "IPv6" or "IPv4"
iface = ipv .. " / " .. iface
-- try to get registered IP
local lookup_host = s["lookup_host"] or "_nolookup_"
local chk_sec = DDNS.calc_seconds(
tonumber(s["check_interval"]) or 10,
s["check_unit"] or "minutes" )
local reg_ip = DDNS.get_regip(section, chk_sec)
if reg_ip == "NOFILE" then
local dnsserver = s["dns_server"] or ""
local force_ipversion = tonumber(s["force_ipversion"] or 0)
local force_dnstcp = tonumber(s["force_dnstcp"] or 0)
local is_glue = tonumber(s["is_glue"] or 0)
local command = luci_helper .. [[ -]]
if (use_ipv6 == 1) then command = command .. [[6]] end
if (force_ipversion == 1) then command = command .. [[f]] end
if (force_dnstcp == 1) then command = command .. [[t]] end
if (is_glue == 1) then command = command .. [[g]] end
command = command .. [[l ]] .. lookup_host
command = command .. [[ -S ]] .. section
if (#dnsserver > 0) then command = command .. [[ -d ]] .. dnsserver end
command = command .. [[ -- get_registered_ip]]
reg_ip = SYS.exec(command)
end
-- fill transfer array
data[#data+1] = {
section = section,
enabled = enabled,
iface = iface,
lookup = lookup_host,
reg_ip = reg_ip,
pid = pid,
datelast = datelast,
datenext = datenext,
datenextstat = datenextstat
}
end)
uci:unload("ddns")
return data
end
-- called by XHR.get from detail_logview.htm
function logread(section)
-- read application settings
local uci = UCI.cursor()
local ldir = uci:get("ddns", "global", "ddns_logdir") or "/var/log/ddns"
local lfile = ldir .. "/" .. section .. ".log"
local ldata = NXFS.readfile(lfile)
if not ldata or #ldata == 0 then
ldata="_nodata_"
end
uci:unload("ddns")
HTTP.write(ldata)
end
-- called by XHR.get from overview_status.htm
function startstop(section, enabled)
local uci = UCI.cursor()
local pid = DDNS.get_pid(section)
local data = {} -- Array to transfer data to javascript
-- if process running we want to stop and return
if pid > 0 then
local tmp = NX.kill(pid, 15) -- terminate
NX.nanosleep(2) -- 2 second "show time"
-- status changed so return full status
data = _get_status()
HTTP.prepare_content("application/json")
HTTP.write_json(data)
return
end
-- read uncommitted changes
-- we don't save and commit data from other section or other options
-- only enabled will be done
local exec = true
local changed = uci:changes("ddns")
for k_config, v_section in pairs(changed) do
-- security check because uci.changes only gets our config
if k_config ~= "ddns" then
exec = false
break
end
for k_section, v_option in pairs(v_section) do
-- check if only section of button was changed
if k_section ~= section then
exec = false
break
end
for k_option, v_value in pairs(v_option) do
-- check if only enabled was changed
if k_option ~= "enabled" then
exec = false
break
end
end
end
end
-- we can not execute because other
-- uncommitted changes pending, so exit here
if not exec then
HTTP.write("_uncommitted_")
return
end
-- save enable state
uci:set("ddns", section, "enabled", ( (enabled == "true") and "1" or "0") )
uci:save("ddns")
uci:commit("ddns")
uci:unload("ddns")
-- start ddns-updater for section
local command = "%s -S %s -- start" %{ luci_helper, UTIL.shellquote(section) }
os.execute(command)
NX.nanosleep(3) -- 3 seconds "show time"
-- status changed so return full status
data = _get_status()
HTTP.prepare_content("application/json")
HTTP.write_json(data)
end
-- called by XHR.poll from overview_status.htm
function status()
local data = _get_status()
HTTP.prepare_content("application/json")
HTTP.write_json(data)
end

File diff suppressed because it is too large Load diff

View file

@ -1,121 +0,0 @@
-- Copyright 2014 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 DISP = require "luci.dispatcher"
local SYS = require "luci.sys"
local CTRL = require "luci.controller.ddns" -- this application's controller
local DDNS = require "luci.tools.ddns" -- ddns multiused functions
-- 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")
function m.commit_handler(self)
if self.changed then -- changes ?
local command = CTRL.luci_helper .. " -- reload"
os.execute(command) -- reload configuration
end
end
-- cbi-section definition -- ###################################################
local ns = m:section( NamedSection, "global", "ddns",
translate("Global Settings"),
translate("Configure here the details for all Dynamic DNS services including this LuCI application.")
.. [[<br /><strong>]]
.. translate("It is NOT recommended for casual users to change settings on this page.")
.. [[</strong><br />]]
.. [[<a href="https://openwrt.org/docs/guide-user/base-system/ddns#section_ddns" target="_blank">]]
.. translate("For detailed information about parameter settings look here.")
.. [[</a>]]
)
-- section might not exist
function ns.cfgvalue(self, section)
if not self.map:get(section) then
self.map:set(section, nil, self.sectiontype)
end
return self.map:get(section)
end
-- upd_privateip -- ###########################################################
local ali = ns:option(Flag, "upd_privateip")
ali.title = translate("Allow non-public IP's")
ali.description = translate("Non-public and by default blocked IP's") .. ":"
.. [[<br /><strong>IPv4: </strong>]]
.. "0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192.168/16"
.. [[<br /><strong>IPv6: </strong>]]
.. "::/32, f000::/4"
ali.default = "0"
-- ddns_dateformat -- #########################################################
local df = ns:option(Value, "ddns_dateformat")
df.title = translate("Date format")
df.description = [[<a href="http://www.cplusplus.com/reference/ctime/strftime/" target="_blank">]]
.. translate("For supported codes look here")
.. [[</a>]]
df.template = "ddns/global_value"
df.default = "%F %R"
df.date_string = ""
function df.cfgvalue(self, section)
local value = AbstractValue.cfgvalue(self, section) or self.default
local epoch = os.time()
self.date_string = DDNS.epoch2date(epoch, value)
return value
end
function df.parse(self, section, novld)
DDNS.value_parse(self, section, novld)
end
-- ddns_rundir -- #############################################################
local rd = ns:option(Value, "ddns_rundir")
rd.title = translate("Status directory")
rd.description = translate("Directory contains PID and other status information for each running section")
rd.default = "/var/run/ddns"
-- no need to validate. if empty default is used everything else created by dns-scripts
function rd.parse(self, section, novld)
DDNS.value_parse(self, section, novld)
end
-- ddns_logdir -- #############################################################
local ld = ns:option(Value, "ddns_logdir")
ld.title = translate("Log directory")
ld.description = translate("Directory contains Log files for each running section")
ld.default = "/var/log/ddns"
-- no need to validate. if empty default is used everything else created by dns-scripts
function ld.parse(self, section, novld)
DDNS.value_parse(self, section, novld)
end
-- ddns_loglines -- ###########################################################
local ll = ns:option(Value, "ddns_loglines")
ll.title = translate("Log length")
ll.description = translate("Number of last lines stored in log files")
ll.default = "250"
function ll.validate(self, value)
local n = tonumber(value)
if not n or math.floor(n) ~= n or n < 1 then
return nil, self.title .. ": " .. translate("minimum value '1'")
end
return value
end
function ll.parse(self, section, novld)
DDNS.value_parse(self, section, novld)
end
-- use_curl -- ################################################################
if (SYS.call([[ grep -i "\+ssl" /usr/bin/wget >/dev/null 2>&1 ]]) == 0)
and NXFS.access("/usr/bin/curl") then
local pc = ns:option(Flag, "use_curl")
pc.title = translate("Use cURL")
pc.description = translate("If both cURL and GNU Wget are installed, Wget is used by default.")
.. [[<br />]]
.. translate("To use cURL activate this option.")
pc.orientation = "horizontal"
pc.default = "0"
end
return m

View file

@ -1,166 +0,0 @@
-- Copyright 2014-2016 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
-- Licensed to the public under the Apache License 2.0.
local DISP = require "luci.dispatcher"
local SYS = require "luci.sys"
local CTRL = require "luci.controller.ddns" -- this application's controller
local DDNS = require "luci.tools.ddns" -- ddns multiused functions
-- html constants
font_red = [[<font color="red">]]
font_off = [[</font>]]
bold_on = [[<strong>]]
bold_off = [[</strong>]]
-- cbi-map definition -- #######################################################
m = Map("ddns")
m.title = CTRL.app_title_back()
m.description = CTRL.app_description()
m.redirect = DISP.build_url("admin", "services", "ddns")
-- SimpleSection definition -- #################################################
-- show Hints to optimize installation and script usage
s = m:section( SimpleSection,
translate("Hints"),
translate("Below a list of configuration tips for your system to run Dynamic DNS updates without limitations") )
-- ddns-scripts needs to be updated for full functionality
if not CTRL.service_ok() then
local so = s:option(DummyValue, "_update_needed")
so.titleref = DISP.build_url("admin", "system", "opkg")
so.rawhtml = true
so.title = font_red .. bold_on ..
translate("Software update required") .. bold_off .. font_off
so.value = translate("The currently installed 'ddns-scripts' package did not support all available settings.") ..
"<br />" ..
translate("Please update to the current version!")
end
-- DDNS Service disabled
if not SYS.init.enabled("ddns") then
local se = s:option(DummyValue, "_not_enabled")
se.titleref = DISP.build_url("admin", "system", "startup")
se.rawhtml = true
se.title = bold_on ..
translate("DDNS Autostart disabled") .. bold_off
se.value = translate("Currently DDNS updates are not started at boot or on interface events." .. "<br />" ..
"This is the default if you run DDNS scripts by yourself (i.e. via cron with force_interval set to '0')" )
end
-- No IPv6 support
if not DDNS.env_info("has_ipv6") then
local v6 = s:option(DummyValue, "_no_ipv6")
v6.titleref = 'http://www.openwrt.org" target="_blank'
v6.rawhtml = true
v6.title = bold_on ..
translate("IPv6 not supported") .. bold_off
v6.value = translate("IPv6 is currently not (fully) supported by this system" .. "<br />" ..
"Please follow the instructions on OpenWrt's homepage to enable IPv6 support" .. "<br />" ..
"or update your system to the latest OpenWrt Release")
end
-- No HTTPS support
if not DDNS.env_info("has_ssl") then
local sl = s:option(DummyValue, "_no_https")
sl.titleref = DISP.build_url("admin", "system", "opkg")
sl.rawhtml = true
sl.title = bold_on ..
translate("HTTPS not supported") .. bold_off
sl.value = translate("Neither GNU Wget with SSL nor cURL installed to support secure updates via HTTPS protocol.") ..
"<br />- " ..
translate("You should install 'wget' or 'curl' or 'uclient-fetch' with 'libustream-*ssl' package.") ..
"<br />- " ..
translate("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")
end
-- No bind_network
if not DDNS.env_info("has_bindnet") then
local bn = s:option(DummyValue, "_no_bind_network")
bn.titleref = DISP.build_url("admin", "system", "opkg")
bn.rawhtml = true
bn.title = bold_on ..
translate("Binding to a specific network not supported") .. bold_off
bn.value = translate("Neither GNU Wget with SSL nor cURL installed to select a network to use for communication.") ..
"<br />- " ..
translate("You should install 'wget' or 'curl' package.") ..
"<br />- " ..
translate("GNU Wget will use the IP of given network, cURL will use the physical interface.") ..
"<br />- " ..
translate("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")
end
-- currently only cURL possibly without proxy support
if not DDNS.env_info("has_proxy") then
local px = s:option(DummyValue, "_no_proxy")
px.titleref = DISP.build_url("admin", "system", "opkg")
px.rawhtml = true
px.title = bold_on ..
translate("cURL without Proxy Support") .. bold_off
px.value = translate("cURL is installed, but libcurl was compiled without proxy support.") ..
"<br />- " ..
translate("You should install 'wget' or 'uclient-fetch' package or replace libcurl.") ..
"<br />- " ..
translate("In some versions cURL/libcurl in OpenWrt is compiled without proxy support.")
end
-- "Force IP Version not supported"
if not DDNS.env_info("has_forceip") then
local fi = s:option(DummyValue, "_no_force_ip")
fi.titleref = DISP.build_url("admin", "system", "opkg")
fi.rawhtml = true
fi.title = bold_on ..
translate("Force IP Version not supported") .. bold_off
local value = translate("BusyBox's nslookup and Wget do not support to specify " ..
"the IP version to use for communication with DDNS Provider!")
if not (DDNS.env_info("has_wgetssl") or DDNS.env_info("has_curl") or DDNS.env_info("has_fetch")) then
value = value .. "<br />- " ..
translate("You should install 'wget' or 'curl' or 'uclient-fetch' package.")
end
if not DDNS.env_info("has_bindhost") then
value = value .. "<br />- " ..
translate("You should install 'bind-host' or 'knot-host' or 'drill' package for DNS requests.")
end
fi.value = value
end
-- "DNS requests via TCP not supported"
if not DDNS.env_info("has_bindhost") then
local dt = s:option(DummyValue, "_no_dnstcp")
dt.titleref = DISP.build_url("admin", "system", "opkg")
dt.rawhtml = true
dt.title = bold_on ..
translate("DNS requests via TCP not supported") .. bold_off
dt.value = translate("BusyBox's nslookup and hostip do not support to specify to use TCP " ..
"instead of default UDP when requesting DNS server!") ..
"<br />- " ..
translate("You should install 'bind-host' or 'knot-host' or 'drill' package for DNS requests.")
end
-- nslookup compiled with musl produce problems when using
if not DDNS.env_info("has_dnsserver") then
local ds = s:option(DummyValue, "_no_dnsserver")
ds.titleref = DISP.build_url("admin", "system", "opkg")
ds.rawhtml = true
ds.title = bold_on ..
translate("Using specific DNS Server not supported") .. bold_off
ds.value = translate("BusyBox's nslookup in the current compiled version " ..
"does not handle given DNS Servers correctly!") ..
"<br />- " ..
translate("You should install 'bind-host' or 'knot-host' or 'drill' or 'hostip' package, " ..
"if you need to specify a DNS server to detect your registered IP.")
end
-- certificates installed
if DDNS.env_info("has_ssl") and not DDNS.env_info("has_cacerts") then
local ca = s:option(DummyValue, "_no_certs")
ca.titleref = DISP.build_url("admin", "system", "opkg")
ca.rawhtml = true
ca.title = bold_on ..
translate("No certificates found") .. bold_off
ca.value = translate("If using secure communication you should verify server certificates!") ..
"<br />- " ..
translate("Install 'ca-certificates' package or needed certificates " ..
"by hand into /etc/ssl/certs default directory")
end
return m

View file

@ -1,249 +0,0 @@
-- Copyright 2014-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
-- Licensed to the public under the Apache License 2.0.
local DISP = require "luci.dispatcher"
local HTTP = require "luci.http"
local SYS = require "luci.sys"
local CTRL = require "luci.controller.ddns" -- this application's controller
local DDNS = require "luci.tools.ddns" -- ddns multiused functions
local show_hints = not (DDNS.env_info("has_ipv6") -- IPv6 support
and DDNS.env_info("has_ssl") -- HTTPS support
and DDNS.env_info("has_proxy") -- Proxy support
and DDNS.env_info("has_bindhost") -- DNS TCP support
and DDNS.env_info("has_forceip") -- Force IP version
and DDNS.env_info("has_dnsserver") -- DNS server support
and DDNS.env_info("has_bindnet") -- Bind to network/interface
and DDNS.env_info("has_cacerts") -- certificates installed at /etc/ssl/certs
)
local not_enabled = not SYS.init.enabled("ddns")
local need_update = not CTRL.service_ok()
-- html constants
font_red = [[<font color="red">]]
font_off = [[</font>]]
bold_on = [[<strong>]]
bold_off = [[</strong>]]
-- cbi-map definition -- #######################################################
m = Map("ddns")
m.title = CTRL.app_title_main()
m.description = CTRL.app_description()
m.on_after_commit = function(self)
if self.changed then -- changes ?
local command = CTRL.luci_helper
if SYS.init.enabled("ddns") then -- ddns service enabled, restart all
command = command .. " -- restart"
os.execute(command)
else -- ddns service disabled, send SIGHUP to running
command = command .. " -- reload"
os.execute(command)
end
end
end
-- SimpleSection definition -- ##################################################
-- with all the JavaScripts we need for "a good Show"
a = m:section( SimpleSection )
a.template = "ddns/overview_status"
-- SimpleSection definition -- #################################################
-- show Hints to optimize installation and script usage
if show_hints or need_update or not_enabled then
s = m:section( SimpleSection, translate("Hints") )
-- ddns-scripts needs to be updated for full functionality
if need_update then
local dv = s:option(DummyValue, "_update_needed")
dv.titleref = DISP.build_url("admin", "system", "packages")
dv.rawhtml = true
dv.title = font_red .. bold_on ..
translate("Software update required") .. bold_off .. font_off
dv.value = translate("The currently installed 'ddns-scripts' package did not support all available settings.") ..
"<br />" ..
translate("Please update to the current version!")
end
-- DDNS Service disabled
if not_enabled then
local dv = s:option(DummyValue, "_not_enabled")
dv.titleref = DISP.build_url("admin", "system", "startup")
dv.rawhtml = true
dv.title = bold_on ..
translate("DDNS Autostart disabled") .. bold_off
dv.value = translate("Currently DDNS updates are not started at boot or on interface events." .. "<br />" ..
"You can start/stop each configuration here. It will run until next reboot.")
end
-- Show more hints on a separate page
if show_hints then
local dv = s:option(DummyValue, "_separate")
dv.titleref = DISP.build_url("admin", "services", "ddns", "hints")
dv.rawhtml = true
dv.title = bold_on ..
translate("Show more") .. bold_off
dv.value = translate("Follow this link" .. "<br />" ..
"You will find more hints to optimize your system to run DDNS scripts with all options")
end
end
-- TableSection definition -- ##################################################
ts = m:section( TypedSection, "service",
translate("Overview"),
translate("Below is a list of configured DDNS configurations and their current state.")
.. "<br />"
.. translate("If you want to send updates for IPv4 and IPv6 you need to define two separate Configurations "
.. "i.e. 'myddns_ipv4' and 'myddns_ipv6'")
.. "<br />"
.. [[<a href="]] .. DISP.build_url("admin", "services", "ddns", "global") .. [[">]]
.. translate("To change global settings click here") .. [[</a>]] )
ts.sectionhead = translate("Configuration")
ts.template = "cbi/tblsection"
ts.addremove = true
ts.extedit = DISP.build_url("admin", "services", "ddns", "detail", "%s")
function ts.create(self, name)
AbstractSection.create(self, name)
HTTP.redirect( self.extedit:format(name) )
end
-- Lookup_Host and registered IP -- #################################################
dom = ts:option(DummyValue, "_lookupIP",
translate("Lookup Hostname") .. "<br />" .. translate("Registered IP") )
dom.template = "ddns/overview_doubleline"
function dom.set_one(self, section)
local lookup = self.map:get(section, "lookup_host") or ""
if lookup ~= "" then
return lookup
else
return [[<em>]] .. translate("config error") .. [[</em>]]
end
end
function dom.set_two(self, section)
local chk_sec = DDNS.calc_seconds(
tonumber(self.map:get(section, "check_interval")) or 10,
self.map:get(section, "check_unit") or "minutes" )
local ip = DDNS.get_regip(section, chk_sec)
if ip == "NOFILE" then
local lookup_host = self.map:get(section, "lookup_host") or ""
if lookup_host == "" then return "" end
local dnsserver = self.map:get(section, "dnsserver") or ""
local use_ipv6 = tonumber(self.map:get(section, "use_ipv6") or 0)
local force_ipversion = tonumber(self.map:get(section, "force_ipversion") or 0)
local force_dnstcp = tonumber(self.map:get(section, "force_dnstcp") or 0)
local is_glue = tonumber(self.map:get(section, "is_glue") or 0)
local command = CTRL.luci_helper .. [[ -]]
if (use_ipv6 == 1) then command = command .. [[6]] end
if (force_ipversion == 1) then command = command .. [[f]] end
if (force_dnstcp == 1) then command = command .. [[t]] end
if (is_glue == 1) then command = command .. [[g]] end
command = command .. [[l ]] .. lookup_host
command = command .. [[ -S ]] .. section
if (#dnsserver > 0) then command = command .. [[ -d ]] .. dnsserver end
command = command .. [[ -- get_registered_ip]]
ip = SYS.exec(command)
end
if ip == "" then ip = translate("no data") end
return ip
end
-- enabled
ena = ts:option( Flag, "enabled",
translate("Enabled"))
ena.template = "ddns/overview_enabled"
ena.rmempty = false
-- show PID and next update
upd = ts:option( DummyValue, "_update",
translate("Last Update") .. "<br />" .. translate("Next Update"))
upd.template = "ddns/overview_doubleline"
function upd.set_one(self, section) -- fill Last Update
-- get/validate last update
local uptime = SYS.uptime()
local lasttime = DDNS.get_lastupd(section)
if lasttime > uptime then -- /var might not be linked to /tmp and cleared on reboot
lasttime = 0
end
-- no last update happen
if lasttime == 0 then
return translate("never")
-- we read last update
else
-- calc last update
-- os.epoch - sys.uptime + lastupdate(uptime)
local epoch = os.time() - uptime + lasttime
-- use linux date to convert epoch
return DDNS.epoch2date(epoch)
end
end
function upd.set_two(self, section) -- fill Next Update
-- get enabled state
local enabled = tonumber(self.map:get(section, "enabled") or 0)
local datenext = translate("unknown error") -- formatted date of next update
-- get force seconds
local force_interval = tonumber(self.map:get(section, "force_interval") or 72)
local force_unit = self.map:get(section, "force_unit") or "hours"
local force_seconds = DDNS.calc_seconds(force_interval, force_unit)
-- get last update and get/validate PID
local uptime = SYS.uptime()
local lasttime = DDNS.get_lastupd(section)
if lasttime > uptime then -- /var might not be linked to /tmp and cleared on reboot
lasttime = 0
end
local pid = DDNS.get_pid(section)
-- calc next update
if lasttime > 0 then
local epoch = os.time() - uptime + lasttime + force_seconds
-- use linux date to convert epoch
datelast = DDNS.epoch2date(epoch)
end
-- process running but update needs to happen
if pid > 0 and ( lasttime + force_seconds - uptime ) < 0 then
datenext = translate("Verify")
-- run once
elseif force_seconds == 0 then
datenext = translate("Run once")
-- no process running and NOT enabled
elseif pid == 0 and enabled == 0 then
datenext = translate("Disabled")
-- no process running and NOT
elseif pid == 0 and enabled ~= 0 then
datenext = translate("Stopped")
end
return datenext
end
-- start/stop button
btn = ts:option( Button, "_startstop",
translate("Process ID") .. "<br />" .. translate("Start / Stop") )
btn.template = "ddns/overview_startstop"
function btn.cfgvalue(self, section)
local pid = DDNS.get_pid(section)
if pid > 0 then
btn.inputtitle = "PID: " .. pid
btn.inputstyle = "reset"
btn.disabled = false
elseif (self.map:get(section, "enabled") or "0") ~= "0" then
btn.inputtitle = translate("Start")
btn.inputstyle = "apply"
btn.disabled = false
else
btn.inputtitle = "----------"
btn.inputstyle = "button"
btn.disabled = true
end
return true
end
return m

View file

@ -1,429 +0,0 @@
-- Copyright 2014-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
-- Licensed to the public under the Apache License 2.0.
module("luci.tools.ddns", package.seeall)
local NX = require "nixio"
local NXFS = require "nixio.fs"
local UCI = require "luci.model.uci"
local SYS = require "luci.sys"
function env_info(type)
if ( type == "has_ssl" ) or ( type == "has_proxy" ) or ( type == "has_forceip" )
or ( type == "has_bindnet" ) or ( type == "has_fetch" )
or ( type == "has_wgetssl" ) or ( type == "has_curl" )
or ( type == "has_curlssl" ) or ( type == "has_curlpxy" )
or ( type == "has_fetchssl" ) or ( type == "has_bbwget" ) then
local function has_wgetssl()
return (SYS.call( [[which wget-ssl >/dev/null 2>&1]] ) == 0) -- and true or nil
end
local function has_curlssl()
return (SYS.call( [[$(which curl) -V 2>&1 | grep -qF "https"]] ) == 0)
end
local function has_fetch()
return (SYS.call( [[which uclient-fetch >/dev/null 2>&1]] ) == 0)
end
local function has_fetchssl()
return NXFS.access("/lib/libustream-ssl.so")
end
local function has_curl()
return (SYS.call( [[which curl >/dev/null 2>&1]] ) == 0)
end
local function has_curlpxy()
return (SYS.call( [[grep -i "all_proxy" /usr/lib/libcurl.so* >/dev/null 2>&1]] ) == 0)
end
local function has_bbwget()
return (SYS.call( [[$(which wget) -V 2>&1 | grep -iqF "busybox"]] ) == 0)
end
if type == "has_wgetssl" then
return has_wgetssl()
elseif type == "has_curl" then
return has_curl()
elseif type == "has_curlssl" then
return has_curlssl()
elseif type == "has_curlpxy" then
return has_curlpxy()
elseif type == "has_fetch" then
return has_fetch()
elseif type == "has_fetchssl" then
return has_fetchssl()
elseif type == "has_bbwget" then
return has_bbwget()
elseif type == "has_ssl" then
if has_wgetssl() then return true end
if has_curlssl() then return true end
if (has_fetch() and has_fetchssl()) then return true end
return false
elseif type == "has_proxy" then
if has_wgetssl() then return true end
if has_curlpxy() then return true end
if has_fetch() then return true end
if has_bbwget() then return true end
return false
elseif type == "has_forceip" then
if has_wgetssl() then return true end
if has_curl() then return true end
if has_fetch() then return true end -- only really needed for transfer
return false
elseif type == "has_bindnet" then
if has_curl() then return true end
if has_wgetssl() then return true end
return false
end
elseif ( type == "has_dnsserver" ) or ( type == "has_bindhost" ) or ( type == "has_hostip" ) or ( type == "has_nslookup" ) then
local function has_bindhost()
if (SYS.call( [[which host >/dev/null 2>&1]] ) == 0) then return true end
if (SYS.call( [[which khost >/dev/null 2>&1]] ) == 0) then return true end
if (SYS.call( [[which drill >/dev/null 2>&1]] ) == 0) then return true end
return false
end
local function has_hostip()
return (SYS.call( [[which hostip >/dev/null 2>&1]] ) == 0)
end
local function has_nslookup()
return (SYS.call( [[which nslookup >/dev/null 2>&1]] ) == 0)
end
if type == "has_bindhost" then
return has_bindhost()
elseif type == "has_hostip" then
return has_hostip()
elseif type == "has_nslookup" then
return has_nslookup()
elseif type == "has_dnsserver" then
if has_bindhost() then return true end
if has_hostip() then return true end
if has_nslookup() then return true end
return false
end
elseif type == "has_ipv6" then
return (NXFS.access("/proc/net/ipv6_route") and NXFS.access("/usr/sbin/ip6tables"))
elseif type == "has_cacerts" then
--old _check_certs() local function
local _, v = NXFS.glob("/etc/ssl/certs/*.crt")
if ( v == 0 ) then _, v = NXFS.glob("/etc/ssl/certs/*.pem") end
return (v > 0)
else
return
end
end
-- function to calculate seconds from given interval and unit
function calc_seconds(interval, unit)
if not tonumber(interval) then
return nil
elseif unit == "days" then
return (tonumber(interval) * 86400) -- 60 sec * 60 min * 24 h
elseif unit == "hours" then
return (tonumber(interval) * 3600) -- 60 sec * 60 min
elseif unit == "minutes" then
return (tonumber(interval) * 60) -- 60 sec
elseif unit == "seconds" then
return tonumber(interval)
else
return nil
end
end
-- convert epoch date to given format
function epoch2date(epoch, format)
if not format or #format < 2 then
local uci = UCI.cursor()
format = uci:get("ddns", "global", "ddns_dateformat") or "%F %R"
uci:unload("ddns")
end
format = format:gsub("%%n", "<br />") -- replace newline
format = format:gsub("%%t", " ") -- replace tab
return os.date(format, epoch)
end
-- read lastupdate from [section].update file
function get_lastupd(section)
local uci = UCI.cursor()
local rdir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
local etime = tonumber(NXFS.readfile("%s/%s.update" % { rdir, section } ) or 0 )
uci:unload("ddns")
return etime
end
-- read registered IP from [section].ip file
function get_regip(section, chk_sec)
local uci = UCI.cursor()
local rdir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
local ip = "NOFILE"
if NXFS.access("%s/%s.ip" % { rdir, section }) then
local ftime = NXFS.stat("%s/%s.ip" % { rdir, section }, "ctime") or 0
local otime = os.time()
-- give ddns-scripts time (9 sec) to update file
if otime < (ftime + chk_sec + 9) then
ip = NXFS.readfile("%s/%s.ip" % { rdir, section })
end
end
uci:unload("ddns")
return ip
end
-- read PID from run file and verify if still running
function get_pid(section)
local uci = UCI.cursor()
local rdir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
local pid = tonumber(NXFS.readfile("%s/%s.pid" % { rdir, section } ) or 0 )
if pid > 0 and not NX.kill(pid, 0) then
pid = 0
end
uci:unload("ddns")
return pid
end
-- replacement of build-in read of UCI option
-- modified AbstractValue.cfgvalue(self, section) from cbi.lua
-- needed to read from other option then current value definition
function read_value(self, section, option)
local value
if self.tag_error[section] then
value = self:formvalue(section)
else
value = self.map:get(section, option)
end
if not value then
return nil
elseif not self.cast or self.cast == type(value) then
return value
elseif self.cast == "string" then
if type(value) == "table" then
return value[1]
end
elseif self.cast == "table" then
return { value }
end
end
-- replacement of build-in parse of "Value"
-- modified AbstractValue.parse(self, section, novld) from cbi.lua
-- validate is called if rmempty/optional true or false
-- before write check if forcewrite, value eq default, and more
function value_parse(self, section, novld)
local fvalue = self:formvalue(section)
local fexist = ( fvalue and (#fvalue > 0) ) -- not "nil" and "not empty"
local cvalue = self:cfgvalue(section)
local rm_opt = ( self.rmempty or self.optional )
local eq_cfg -- flag: equal cfgvalue
-- If favlue and cvalue are both tables and have the same content
-- make them identical
if type(fvalue) == "table" and type(cvalue) == "table" then
eq_cfg = (#fvalue == #cvalue)
if eq_cfg then
for i=1, #fvalue do
if cvalue[i] ~= fvalue[i] then
eq_cfg = false
end
end
end
if eq_cfg then
fvalue = cvalue
end
end
-- removed parameter "section" from function call because used/accepted nowhere
-- also removed call to function "transfer"
local vvalue, errtxt = self:validate(fvalue)
-- error handling; validate return "nil"
if not vvalue then
if novld then -- and "novld" set
return -- then exit without raising an error
end
if fexist then -- and there is a formvalue
self:add_error(section, "invalid", errtxt or self.title .. ": invalid")
return -- so data are invalid
elseif not rm_opt then -- and empty formvalue but NOT (rmempty or optional) set
self:add_error(section, "missing", errtxt or self.title .. ": missing")
return -- so data is missing
elseif errtxt then
self:add_error(section, "invalid", errtxt)
return
end
-- error ("\n option: " .. self.option ..
-- "\n fvalue: " .. tostring(fvalue) ..
-- "\n fexist: " .. tostring(fexist) ..
-- "\n cvalue: " .. tostring(cvalue) ..
-- "\n vvalue: " .. tostring(vvalue) ..
-- "\n vexist: " .. tostring(vexist) ..
-- "\n rm_opt: " .. tostring(rm_opt) ..
-- "\n eq_cfg: " .. tostring(eq_cfg) ..
-- "\n eq_def: " .. tostring(eq_def) ..
-- "\n novld : " .. tostring(novld) ..
-- "\n errtxt: " .. tostring(errtxt) )
end
-- lets continue with value returned from validate
eq_cfg = ( vvalue == cvalue ) -- update equal_config flag
local vexist = ( vvalue and (#vvalue > 0) ) and true or false -- not "nil" and "not empty"
local eq_def = ( vvalue == self.default ) -- equal_default flag
-- (rmempty or optional) and (no data or equal_default)
if rm_opt and (not vexist or eq_def) then
if self:remove(section) then -- remove data from UCI
self.section.changed = true -- and push events
end
return
end
-- not forcewrite and no changes, so nothing to write
if not self.forcewrite and eq_cfg then
return
end
-- we should have a valid value here
assert (vvalue, "\n option: " .. self.option ..
"\n fvalue: " .. tostring(fvalue) ..
"\n fexist: " .. tostring(fexist) ..
"\n cvalue: " .. tostring(cvalue) ..
"\n vvalue: " .. tostring(vvalue) ..
"\n vexist: " .. tostring(vexist) ..
"\n rm_opt: " .. tostring(rm_opt) ..
"\n eq_cfg: " .. tostring(eq_cfg) ..
"\n eq_def: " .. tostring(eq_def) ..
"\n errtxt: " .. tostring(errtxt) )
-- write data to UCI; raise event only on changes
if self:write(section, vvalue) and not eq_cfg then
self.section.changed = true
end
end
-----------------------------------------------------------------------------
-- copied from https://svn.nmap.org/nmap/nselib/url.lua
-- @author Diego Nehab
-- @author Eddie Bell <ejlbell@gmail.com>
--[[
URI parsing, composition and relative URL resolution
LuaSocket toolkit.
Author: Diego Nehab
RCS ID: $Id: url.lua,v 1.37 2005/11/22 08:33:29 diego Exp $
parse_query and build_query added For nmap (Eddie Bell <ejlbell@gmail.com>)
]]--
---
-- Parses a URL and returns a table with all its parts according to RFC 2396.
--
-- The following grammar describes the names given to the URL parts.
-- <code>
-- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
-- <authority> ::= <userinfo>@<host>:<port>
-- <userinfo> ::= <user>[:<password>]
-- <path> :: = {<segment>/}<segment>
-- </code>
--
-- The leading <code>/</code> in <code>/<path></code> is considered part of
-- <code><path></code>.
-- @param url URL of request.
-- @param default Table with default values for each field.
-- @return A table with the following fields, where RFC naming conventions have
-- been preserved:
-- <code>scheme</code>, <code>authority</code>, <code>userinfo</code>,
-- <code>user</code>, <code>password</code>, <code>host</code>,
-- <code>port</code>, <code>path</code>, <code>params</code>,
-- <code>query</code>, and <code>fragment</code>.
-----------------------------------------------------------------------------
function parse_url(url) --, default)
-- initialize default parameters
local parsed = {}
-- for i,v in base.pairs(default or parsed) do
-- parsed[i] = v
-- end
-- remove whitespace
-- url = string.gsub(url, "%s", "")
-- get fragment
url = string.gsub(url, "#(.*)$",
function(f)
parsed.fragment = f
return ""
end)
-- get scheme. Lower-case according to RFC 3986 section 3.1.
url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
function(s)
parsed.scheme = string.lower(s);
return ""
end)
-- get authority
url = string.gsub(url, "^//([^/]*)",
function(n)
parsed.authority = n
return ""
end)
-- get query stringing
url = string.gsub(url, "%?(.*)",
function(q)
parsed.query = q
return ""
end)
-- get params
url = string.gsub(url, "%;(.*)",
function(p)
parsed.params = p
return ""
end)
-- path is whatever was left
parsed.path = url
local authority = parsed.authority
if not authority then
return parsed
end
authority = string.gsub(authority,"^([^@]*)@",
function(u)
parsed.userinfo = u;
return ""
end)
authority = string.gsub(authority, ":([0-9]*)$",
function(p)
if p ~= "" then
parsed.port = p
end;
return ""
end)
if authority ~= "" then
parsed.host = authority
end
local userinfo = parsed.userinfo
if not userinfo then
return parsed
end
userinfo = string.gsub(userinfo, ":([^:]*)$",
function(p)
parsed.password = p;
return ""
end)
parsed.user = userinfo
return parsed
end

View file

@ -1 +0,0 @@
<%+ddns/system_status%>

View file

@ -1,56 +0,0 @@
<!-- ++ BEGIN ++ Dynamic DNS ++ detail_logview.htm ++ -->
<script type="text/javascript">//<![CDATA[
function onclick_logview(section, bottom) {
// get elements
var txt = document.getElementById("cbid.ddns." + section + "._logview.txt"); // TextArea
if ( !txt ) { return; } // security check
XHR.get('<%=url([[admin]], [[services]], [[ddns]], [[logview]])%>/' + section, null,
function(x) {
if (x.responseText == "_nodata_")
txt.value = "<%:File not found or empty%>";
else
txt.value = x.responseText;
if (bottom)
txt.scrollTop = txt.scrollHeight;
else
txt.scrollTop = 0; }
);
}
//]]></script>
<%+cbi/valueheader%>
<br />
<%
-- one button on top, one at the buttom
%>
<input class="cbi-button cbi-input-button" style="align: center; width: 100%" type="button" onclick="onclick_logview(this.name, false)"
<%=
attr("name", section) .. attr("id", cbid .. ".btn1") .. attr("value", self.inputtitle)
%> />
<br /><br />
<%
-- set a readable style taken from openwrt theme for textarea#syslog
-- in openwrt theme there are problems with a width of 100 so we check for theme and set to lower value
%>
<textarea style="width: <%if media == "/luci-static/openwrt.org" then%>98.7%<%else%>100%<%end%> ; min-height: 500px; border: 3px solid #cccccc; padding: 5px; font-family: monospace; resize: none;" wrap="off" readonly="readonly"
<%=
attr("name", cbid .. ".txt") .. attr("id", cbid .. ".txt") .. ifattr(self.rows, "rows")
%> >
<%-=pcdata(self:cfgvalue(section))-%>
</textarea>
<br /><br />
<%
-- one button on top, one at the buttom
%>
<input class="cbi-button cbi-input-button" style="align: center; width: 100%" type="button" onclick="onclick_logview(this.name, true)"
<%= attr("name", section) .. attr("id", cbid .. ".btn2") .. attr("value", self.inputtitle) %> />
<%+cbi/valuefooter%>
<!-- ++ END ++ Dynamic DNS ++ detail_logview.htm ++ -->

View file

@ -1,23 +0,0 @@
<!-- ++ BEGIN ++ Dynamic DNS ++ detail_lvalue.htm ++ -->
<!-- no value header to suppress next line -->
&#160;
<% if self.widget == "select" then %>
<select class="cbi-input-select" onchange="cbi_d_update(this.id)"<%= attr("id", cbid) .. attr("name", cbid) .. ifattr(self.size, "size") %>>
<% for i, key in pairs(self.keylist) do -%>
<option id="cbi-<%=self.config.."-"..section.."-"..self.option.."-"..key%>"<%= attr("value", key) .. ifattr(tostring(self:cfgvalue(section) or self.default) == key, "selected", "selected") %>><%=striptags(self.vallist[i])%></option>
<%- end %>
</select>
<% elseif self.widget == "radio" then
local c = 0
for i, key in pairs(self.keylist) do
c = c + 1
%>
<input class="cbi-input-radio" onclick="cbi_d_update(this.id)" onchange="cbi_d_update(this.id)" type="radio"<%= attr("id", cbid..c) .. attr("name", cbid) .. attr("value", key) .. ifattr((self:cfgvalue(section) or self.default) == key, "checked", "checked") %> />
<label<%= attr("for", cbid..c) %>></label>
<label<%= attr("for", cbid..c) %>><%=self.vallist[i]%></label>
<% if c == self.size then c = 0 %><% if self.orientation == "horizontal" then %>&#160;<% else %><br /><% end %>
<% end end %>
<% end %>
<%+cbi/valuefooter%>
<!-- ++ END ++ Dynamic DNS ++ detail_lvalue.htm ++ -->

View file

@ -1,9 +0,0 @@
<!-- ++ BEGIN ++ Dynamic DNS ++ detail_value.htm ++ -->
<%+cbi/valueheader%>
<input type="text" class="cbi-input-text" style="width: 10em;" onchange="cbi_d_update(this.id)"<%=
attr("name", cbid) .. attr("id", cbid) .. attr("value", self:cfgvalue(section) or self.default) ..
ifattr(self.size, "size") .. ifattr(self.placeholder, "placeholder")
%> />
<!-- no value footer to suppress next line -->
<!-- ++ END ++ Dynamic DNS ++ detail_value.htm ++ -->

View file

@ -1,33 +0,0 @@
<!-- ++ BEGIN ++ Dynamic DNS ++ global_value.htm ++ -->
<%+cbi/valueheader%>
<script type="text/javascript">//<![CDATA[
// event handler on changed date
function onkeyup_date(value) {
var obj = document.getElementById("cbid.ddns.global.ddns_dateformat.help");
if ( !obj ) { return; } // security check
if ( value == "" || value.length == 0 ) { value = "%F %R"; }
var now = new Date();
var txt = now.toLocaleFormat(value);
// handle newline(%n) and tab(%t) needs to be converted to HTML
txt = txt.replace(new RegExp('\r?\n','g'), '<br />');
txt = txt.replace(new RegExp('\t','g'), '&nbsp;&nbsp;&nbsp;&nbsp;');
obj.innerHTML = "<%:Current setting%>: <strong>" + txt + "<\/strong>";
}
//]]></script>
<input type="text" class="cbi-input-text" onchange="cbi_d_update(this.id)" onkeyup="onkeyup_date(this.value)"
<%=
attr("name", cbid) .. attr("id", cbid) .. attr("value", self:cfgvalue(section) or self.default) ..
ifattr(self.size, "size") .. ifattr(self.placeholder, "placeholder")
%>
/>
<br />
<div class="cbi-value-description">
<span class="cbi-value-helpicon"><img src="<%=resource%>/cbi/help.gif" alt="<%:help%>" /></span><%=self.description%>
<br /><%:Current setting%>: <strong><%=self.date_string%></strong>
</div> <!-- div class="cbi-value-description" -->
</div> <!-- div class="cbi-value-field" -->
</div> <!-- div class="cbi-value cbi-value-last" -->
<!-- ++ END ++ Dynamic DNS ++ global_value.htm ++ -->

View file

@ -1,10 +0,0 @@
<!-- ++ BEGIN ++ Dynamic DNS ++ overview_doubleline.htm ++ -->
<%+cbi/valueheader%>
<span id="<%=cbid%>.one"><%=self:set_one(section)%></span>
<br />
<span id="<%=cbid%>.two"><%=self:set_two(section)%></span>
<%+cbi/valuefooter%>
<!-- ++ END ++ Dynamic DNS ++ overview_doubleline.htm ++ -->

View file

@ -1,16 +0,0 @@
<!-- ++ BEGIN ++ Dynamic DNS ++ overview_enabled.htm ++ -->
<%+cbi/valueheader%>
<input type="hidden" value="1"<%=
attr("name", "cbi.cbe." .. self.config .. "." .. section .. "." .. self.option)
%> />
<!-- modified to call own function -->
<input class="cbi-input-checkbox" onclick="cbi_d_update(this.id)" onchange="onchange_enabled(this.id)" type="checkbox"<%=
attr("id", cbid) .. attr("name", cbid) .. attr("value", self.enabled or 1) ..
ifattr((self:cfgvalue(section) or self.default) == self.enabled, "checked", "checked")
%> />
<label<%= attr("for", cbid)%>></label>
<%+cbi/valuefooter%>
<!-- ++ END ++ Dynamic DNS ++ overview_enabled.htm ++ -->

View file

@ -1,17 +0,0 @@
<!-- ++ BEGIN ++ Dynamic DNS ++ overview_startstop.htm ++ -->
<%+cbi/valueheader%>
<% if self:cfgvalue(section) ~= false then
-- We need to garantie that function cfgvalue run first to set missing parameters
%>
<!-- style="font-size: 100%;" needed for openwrt theme to fix font size -->
<!-- type="button" onclick="..." enable standard onclick functionality -->
<input class="cbi-button cbi-input-<%=self.inputstyle or "button" %>" style="font-size: 100%;" type="button" onclick="onclick_startstop(this.id)"
<%=
attr("name", section) .. attr("id", cbid) .. attr("value", self.inputtitle) .. ifattr(self.disabled, "disabled")
%> />
<% end %>
<%+cbi/valuefooter%>
<!-- ++ END ++ Dynamic DNS ++ overview_startstop.htm ++ -->

View file

@ -1,180 +0,0 @@
<!-- ++ BEGIN ++ Dynamic DNS ++ overview_status.htm ++ -->
<script type="text/javascript">//<![CDATA[
// helper to extract section from objects id
// cbi.ddns.SECTION._xyz
function _id2section(id) {
var x = id.split(".");
return x[2];
}
// helper to move status data to the relevant
// screen objects
// called by XHR.poll and onclick_startstop
function _data2elements(data) {
// Service sections
for( var i = 1; i < data.length; i++ )
{
var section = data[i].section // Section to handle
var cbx = document.getElementById("cbid.ddns." + section + ".enabled"); // Enabled
var btn = document.getElementById("cbid.ddns." + section + "._startstop"); // Start/Stop button
var rip = document.getElementById("cbid.ddns." + section + "._lookupIP.two"); // Registered IP
var lup = document.getElementById("cbid.ddns." + section + "._update.one"); // Last Update
var nup = document.getElementById("cbid.ddns." + section + "._update.two"); // Next Update
if ( !(cbx && btn && rip && lup && nup) ) { return; } // security check
// process id
if (data[i].pid > 0) {
// stop always possible if process running
btn.value = "PID: " + data[i].pid;
btn.className = "cbi-button cbi-input-reset";
} else {
// default Start / enabled
btn.value = "<%:Start%>";
btn.className = "cbi-button cbi-input-apply";
}
btn.disabled = false; // button enabled
// last update
switch (data[i].datelast) {
case "_empty_":
lup.innerHTML = '<em><%:Unknown error%></em>' ;
break;
case "_never_":
lup.innerHTML = '<em><%:Never%></em>' ;
break;
default:
lup.innerHTML = data[i].datelast;
break;
}
// next update
switch (data[i].datenext) {
case "_empty_":
nup.innerHTML = '<em><%:Unknown error%></em>' ;
break;
case "_verify_":
nup.innerHTML = '<em><%:Verify%></em>';
break;
case "_runonce_":
case "_stopped_":
case "_disabled_":
if (cbx.checked && data[i].datenext == "_runonce_") {
nup.innerHTML = '<em><%:Run once%></em>';
} else if (cbx.checked) {
nup.innerHTML = '<em><%:Stopped%></em>';
} else {
nup.innerHTML = '<em><%:Disabled%></em>';
btn.value = '----------';
btn.className = "cbi-button cbi-input-button"; // no image
btn.disabled = true; // disabled
}
break;
default:
nup.innerHTML = data[i].datenext;
break;
}
// lookup
// (data[i].lookup ignored here
// registered IP
// rip.innerHTML = "Registered IP";
if (data[i].lookup == "_nolookup_")
rip.innerHTML = '';
else if (data[i].reg_ip == "_nodata_")
rip.innerHTML = '<em><%:No data%></em>';
else
rip.innerHTML = data[i].reg_ip;
// monitored interfacce
// data[i].iface ignored here
}
}
// event handler for enabled checkbox
function onchange_enabled(id) {
// run original function in cbi.js
// whatever is done there
cbi_d_update(id);
var section = _id2section(id);
var cbx = document.getElementById("cbid.ddns." + section + ".enabled");
var btn = document.getElementById("cbid.ddns." + section + "._startstop");
if ( !(cbx && btn) ) { return; } // security check
var pid_txt = btn.value;
var pid_found = ( pid_txt.search("PID") >= 0 ) ? true : false;
if (pid_found) {
// btn.value = "PID: 0000";
btn.className = "cbi-button cbi-button-reset";
btn.disabled = false;
} else if (cbx.checked) {
btn.value = "<%:Start%>";
btn.className = "cbi-button cbi-button-apply";
btn.disabled = false;
} else {
btn.value = '----------';
btn.className = "cbi-button cbi-input-button"; // no image
btn.disabled = true; // disabled
}
}
// event handler for start/stop button
function onclick_startstop(id) {
// extract section
var section = _id2section(id);
// get elements
var cbx = document.getElementById("cbid.ddns." + section + ".enabled"); // Enabled
var obj = document.getElementById("cbi-ddns-overview-status-legend"); // object defined below to make in-/visible
if ( !(obj && cbx) ) { return; } // security check
// make me visible
obj.parentNode.style.display = "block";
// do start/stop
var btnXHR = new XHR();
btnXHR.post('<%=url([[admin]], [[services]], [[ddns]], [[startstop]])%>/' + section + '/' + cbx.checked, { token: '<%=token%>' },
function(x, data) {
if (x.responseText == "_uncommitted_") {
// we need a trick to display Ampersand "&" in stead of "&#38;" or "&amp;"
// after translation
var txt="<%:Please [Save & Apply] your changes first%>";
alert( txt.replace(new RegExp("<%:&%>", "g"), "&") );
} else {
// should have data because status changed
// so update screen
if (data) { _data2elements(data); }
}
// make me invisible
obj.parentNode.style.display = "none";
}
);
}
// force to immediate show status on page load (not waiting for XHR.poll)
XHR.get('<%=url([[admin]], [[services]], [[ddns]], [[status]])%>', null,
function(x, data) {
if (data) { _data2elements(data); }
}
);
// define only ONE XHR.poll in a page because if one is running it blocks the other one
// optimum is to define on Map or Section Level from here you can reach all elements
// we need update every 15 seconds only
XHR.poll(-1, '<%=url([[admin]], [[services]], [[ddns]], [[status]])%>', null,
function(x, data) {
if (data) { _data2elements(data); }
}
);
//]]></script>
<fieldset class="cbi-section" style="display:none">
<legend id="cbi-ddns-overview-status-legend"><%:Applying changes%></legend>
<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" />
<span id="cbi-ddns-overview-status-text"><%:Waiting for changes to be applied...%></span>
</fieldset>
<!-- ++ END ++ Dynamic DNS ++ overview_status.htm ++ -->

View file

@ -1,60 +0,0 @@
<!-- ++ BEGIN ++ Dynamic DNS ++ system_status.htm ++ -->
<script type="text/javascript">//<![CDATA[
//start polling data every 30 second, this doesn't change so much
XHR.poll(30, '<%=url([[admin]], [[services]], [[ddns]], [[status]])%>', null,
function(x, data) {
if (data) {
var tbl = document.getElementById('ddns_status_table');
// security check
if ( !(tbl) ) { return; }
var rows = [];
// DDNS Service disabled
if (data[0].enabled == 0) {
var ddns_legend = document.getElementById('ddns_status_legend');
ddns_legend.style.display='none';
rows.push([
'<strong><font color="red"><%:DDNS Autostart disabled%></font>',
'<a class="cbi-button cbi-button-action important" type="button" href="' + data[0].url_up + '"><%:enable here%></a></strong>'
]);
} else {
for(var j = 1; j < data.length; j++ )
{
rows.push([
'<strong>' + data[j].section + '</strong>', //configuration
data[j].datenextstat ? '<em>'+data[j].datenextstat+'</em>' : '<em>'+data[j].datenext+'</em>',
data[j].lookup ? data[j].lookup : '<em><%:config error%></em>',
data[j].reg_ip ? data[j].reg_ip : '<em><%:No data%></em>',
data[j].iface // monitored interface
]);
}
}
cbi_update_table(tbl, rows, '<%:There is no service configured.%>');
}
}
);
//]]></script>
<fieldset class="cbi-section" id="ddns_status_section">
<legend><a href="<%=url([[admin]], [[services]], [[ddns]])%>"><%:Dynamic DNS%></a></legend>
<div class="cbi-section-node">
<div class="table" id="ddns_status_table">
<div class="tr table-titles" id="ddns_status_legend">
<div class="th"><%:Configuration%></div>
<div class="th"><%:Next Update%></div>
<div class="th"><%:Lookup Hostname%></div>
<div class="th"><%:Registered IP%></div>
<div class="th"><%:Network%></div>
</div>
<div class="tr">
<div class="td"><em><br /><%:Collecting data...%></em></div>
</div>
</div>
</div>
</fieldset>
<!-- ++ END ++ Dynamic DNS ++ system_status.htm ++ -->

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,321 @@
#!/usr/bin/env lua
local json = require "luci.jsonc"
local nixio = require "nixio"
local fs = require "nixio.fs"
local UCI = require "luci.model.uci"
local sys = require "luci.sys"
local util = require "luci.util"
local luci_helper = "/usr/lib/ddns/dynamic_dns_lucihelper.sh"
local srv_name = "ddns-scripts"
-- convert epoch date to given format
local function epoch2date(epoch, format)
if not format or #format < 2 then
local uci = UCI.cursor()
format = uci:get("ddns", "global", "ddns_dateformat") or "%F %R"
uci:unload("ddns")
end
format = format:gsub("%%n", "<br />") -- replace newline
format = format:gsub("%%t", " ") -- replace tab
return os.date(format, epoch)
end
-- function to calculate seconds from given interval and unit
local function calc_seconds(interval, unit)
if not tonumber(interval) then
return nil
elseif unit == "days" then
return (tonumber(interval) * 86400) -- 60 sec * 60 min * 24 h
elseif unit == "hours" then
return (tonumber(interval) * 3600) -- 60 sec * 60 min
elseif unit == "minutes" then
return (tonumber(interval) * 60) -- 60 sec
elseif unit == "seconds" then
return tonumber(interval)
else
return nil
end
end
local methods = {
get_services_log = {
args = { service_name = "service_name" },
call = function(args)
local result = "File not found or empty"
local uci = UCI.cursor()
local dirlog = uci:get('ddns', 'global', 'ddns_logdir') or "/var/log/ddns"
-- Fallback to default logdir with unsecure path
if dirlog:match('%.%.%/') then dirlog = "/var/log/ddns" end
if args and args.service_name and fs.access("%s/%s.log" % { dirlog, args.service_name }) then
result = fs.readfile("%s/%s.log" % { dirlog, args.service_name })
end
uci.unload()
return { result = result }
end
},
get_services_status = {
call = function()
local uci = UCI.cursor()
local rundir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
local date_format = uci:get("ddns", "global", "ddns_dateformat")
local res = {}
uci:foreach("ddns", "service", function (s)
local ip, last_update, next_update
local section = s[".name"]
if fs.access("%s/%s.ip" % { rundir, section }) then
ip = fs.readfile("%s/%s.ip" % { rundir, section })
else
local dnsserver = s["dns_server"] or ""
local force_ipversion = tonumber(s["force_ipversion"] or 0)
local force_dnstcp = tonumber(s["force_dnstcp"] or 0)
local is_glue = tonumber(s["is_glue"] or 0)
local command = { luci_helper , [[ -]] }
local lookup_host = s["lookup_host"] or "_nolookup_"
if (use_ipv6 == 1) then command[#command+1] = [[6]] end
if (force_ipversion == 1) then command[#command+1] = [[f]] end
if (force_dnstcp == 1) then command[#command+1] = [[t]] end
if (is_glue == 1) then command[#command+1] = [[g]] end
command[#command+1] = [[l ]]
command[#command+1] = lookup_host
command[#command+1] = [[ -S ]]
command[#command+1] = section
if (#dnsserver > 0) then command[#command+1] = [[ -d ]] .. dnsserver end
command[#command+1] = [[ -- get_registered_ip]]
line = util.exec(table.concat(command))
end
local last_update = tonumber(fs.readfile("%s/%s.update" % { rundir, section } ) or 0)
local next_update, converted_last_update
local pid = tonumber(fs.readfile("%s/%s.pid" % { rundir, section } ) or 0)
if pid > 0 and not nixio.kill(pid, 0) then
pid = 0
end
local uptime = sys.uptime()
local force_seconds = calc_seconds(
tonumber(s["force_interval"]) or 72,
s["force_unit"] or "hours" )
-- process running but update needs to happen
-- problems if force_seconds > uptime
force_seconds = (force_seconds > uptime) and uptime or force_seconds
if last_update > 0 then
local epoch = os.time() - uptime + last_update + force_seconds
-- use linux date to convert epoch
converted_last_update = epoch2date(epoch,date_format)
next_update = epoch2date(epoch + force_seconds)
end
if pid > 0 and ( last_update + force_seconds - uptime ) <= 0 then
next_update = "Verify"
-- run once
elseif force_seconds == 0 then
next_update = "Run once"
-- no process running and NOT enabled
elseif pid == 0 and s['enabled'] == '0' then
next_update = "Disabled"
-- no process running and enabled
elseif pid == 0 and s['enabled'] ~= '0' then
next_update = "Stopped"
end
res[section] = {
ip = ip and ip:gsub("\n","") or nil,
last_update = last_update ~= 0 and converted_last_update or nil,
next_update = next_update or nil,
pid = pid or nil,
}
end
)
uci:unload("ddns")
return res
end
},
get_ddns_state = {
call = function()
local ipkg = require "luci.model.ipkg"
local uci = UCI.cursor()
local dateformat = uci:get("ddns", "global", "ddns_dateformat") or "%F %R"
uci:unload("ddns")
local ver, srv_ver_cmd
local res = {}
if ipkg then
ver = ipkg.info(srv_name)[srv_name].Version
else
srv_ver_cmd = luci_helper .. " -V | awk {'print $2'} "
ver = util.exec(srv_ver_cmd)
end
res['_version'] = ver and #ver > 0 and ver or nil
res['_enabled'] = sys.init.enabled("ddns")
res['_curr_dateformat'] = os.date(dateformat)
return res
end
},
get_env = {
call = function()
local res = {}
local cache = {}
local function has_wget()
return (sys.call( [[which wget >/dev/null 2>&1]] ) == 0)
end
local function has_wgetssl()
if cache['has_wgetssl'] then return cache['has_wgetssl'] end
local res = (sys.call( [[which wget-ssl >/dev/null 2>&1]] ) == 0)
cache['has_wgetssl'] = res
return res
end
local function has_curlssl()
return (sys.call( [[$(which curl) -V 2>&1 | grep -qF "https"]] ) == 0)
end
local function has_fetch()
if cache['has_fetch'] then return cache['has_fetch'] end
local res = (sys.call( [[which uclient-fetch >/dev/null 2>&1]] ) == 0)
cache['has_fetch'] = res
return res
end
local function has_fetchssl()
return fs.access("/lib/libustream-ssl.so")
end
local function has_curl()
if cache['has_curl'] then return cache['has_curl'] end
local res = (sys.call( [[which curl >/dev/null 2>&1]] ) == 0)
cache['has_curl'] = res
return res
end
local function has_curlpxy()
return (sys.call( [[grep -i "all_proxy" /usr/lib/libcurl.so* >/dev/null 2>&1]] ) == 0)
end
local function has_bbwget()
return (sys.call( [[$(which wget) -V 2>&1 | grep -iqF "busybox"]] ) == 0)
end
res['has_wget'] = has_wget() or false
res['has_curl'] = has_curl() or false
res['has_ssl'] = has_wgetssl() or has_curlssl() or (has_fetch() and has_fetchssl()) or false
res['has_proxy'] = has_wgetssl() or has_curlpxy() or has_fetch() or has_bbwget or false
res['has_forceip'] = has_wgetssl() or has_curl() or has_fetch() or false
res['has_bindnet'] = has_curl() or has_wgetssl() or false
local function has_bindhost()
if (sys.call( [[which host >/dev/null 2>&1]] ) == 0) then return true end
if (sys.call( [[which khost >/dev/null 2>&1]] ) == 0) then return true end
if (sys.call( [[which drill >/dev/null 2>&1]] ) == 0) then return true end
return false
end
local function has_hostip()
return (sys.call( [[which hostip >/dev/null 2>&1]] ) == 0)
end
local function has_nslookup()
return (sys.call( [[which nslookup >/dev/null 2>&1]] ) == 0)
end
res['has_dnsserver'] = has_bindhost() or has_hostip() or has_nslookup() or false
local function check_certs()
local _, v = fs.glob("/etc/ssl/certs/*.crt")
if ( v == 0 ) then _, v = NXFS.glob("/etc/ssl/certs/*.pem") end
return (v > 0)
end
res['has_cacerts'] = check_certs() or false
res['has_ipv6'] = (fs.access("/proc/net/ipv6_route") and fs.access("/usr/sbin/ip6tables"))
return res
end
}
}
local function parseInput()
local parse = json.new()
local done, err
while true do
local chunk = io.read(4096)
if not chunk then
break
elseif not done and not err then
done, err = parse:parse(chunk)
end
end
if not done then
print(json.stringify({ error = err or "Incomplete input" }))
os.exit(1)
end
return parse:get()
end
local function validateArgs(func, uargs)
local method = methods[func]
if not method then
print(json.stringify({ error = "Method not found" }))
os.exit(1)
end
if type(uargs) ~= "table" then
print(json.stringify({ error = "Invalid arguments" }))
os.exit(1)
end
uargs.ubus_rpc_session = nil
local k, v
local margs = method.args or {}
for k, v in pairs(uargs) do
if margs[k] == nil or
(v ~= nil and type(v) ~= type(margs[k]))
then
print(json.stringify({ error = "Invalid arguments" }))
os.exit(1)
end
end
return method
end
if arg[1] == "list" then
local _, method, rv = nil, nil, {}
for _, method in pairs(methods) do rv[_] = method.args or {} end
print((json.stringify(rv):gsub(":%[%]", ":{}")))
elseif arg[1] == "call" then
local args = parseInput()
local method = validateArgs(arg[2], args)
local result, code = method.call(args)
print((json.stringify(result):gsub("^%[%]$", "{}")))
os.exit(code or 0)
end

View file

@ -0,0 +1,17 @@
{
"luci-app-ddns": {
"description": "Grant access to ddns procedures",
"read": {
"ubus": {
"luci.ddns": [ "get_services_status", "get_ddns_state", "get_env", "get_services_log" ],
"luci": [ "setInitAction" ],
},
"file": {
"/etc/ddns/services": [ "read" ],
"/etc/ddns/services_ipv6": [ "read" ],
"/usr/lib/ddns/dynamic_dns_lucihelper.sh": [ "exec" ]
},
"uci": [ "ddns" ]
}
}
}