luci-app-ddns: convert to client side implementation

Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
This commit is contained in:
Ansuel Smith 2019-11-06 01:33:53 +01:00
parent 0731f7e5e4
commit 34fa5122f9
No known key found for this signature in database
GPG key ID: AC001D09ADBFEAD7
21 changed files with 1380 additions and 3222 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 ++ -->

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" ]
}
}
}