diff options
author | Paul Donald <newtwen+github@gmail.com> | 2024-11-21 18:34:45 +0100 |
---|---|---|
committer | Paul Donald <newtwen+github@gmail.com> | 2024-11-21 18:34:45 +0100 |
commit | 22fcc720ec5d2ad1426509136447172281d345a0 (patch) | |
tree | a76947bc0d038701f10f4174bd8f8f90846fe046 /applications/luci-app-ddns/root/usr/share/rpcd/ucode | |
parent | 53e36e3293bee2ea2ce0fbc1250074c44cfe8335 (diff) |
luci-app-ddns: Convert to JS
Signed-off-by: Paul Donald <newtwen+github@gmail.com>
Diffstat (limited to 'applications/luci-app-ddns/root/usr/share/rpcd/ucode')
-rw-r--r-- | applications/luci-app-ddns/root/usr/share/rpcd/ucode/ddns.uc | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/applications/luci-app-ddns/root/usr/share/rpcd/ucode/ddns.uc b/applications/luci-app-ddns/root/usr/share/rpcd/ucode/ddns.uc new file mode 100644 index 0000000000..3a8e5cf50a --- /dev/null +++ b/applications/luci-app-ddns/root/usr/share/rpcd/ucode/ddns.uc @@ -0,0 +1,330 @@ +#!/usr/bin/env ucode + +'use strict'; + +import { readfile, mkstemp, open, popen, stat, glob } from 'fs'; +import { init_list, init_index, init_enabled, init_action, conntrack_list, process_list } from 'luci.sys'; +import { isnan } from 'math'; +import { cursor } from 'uci'; + +const uci = cursor(); +const ddns_log_path = '/var/log/ddns'; +const ddns_package_path = '/usr/share/ddns'; +const ddns_run_path = '/var/run/ddns'; +const luci_helper = '/usr/lib/ddns/dynamic_dns_lucihelper.sh'; +const srv_name = 'ddns-scripts'; +const opkg_info_path = '/usr/lib/opkg/info'; + + + + +function get_dateformat() { + return uci.get('ddns', 'global', 'ddns_dateformat') || '%F %R'; +} + +function uptime() { + return split(readfile('/proc/uptime', 256), ' ')?.[0]; +} + +function killcmd(procid, signal) { + if (!signal) { + signal = 0; + } + // by default, we simply re-nice a process to check it is running + return system(`kill -${signal} ${procid}`); +} + +function trimnonewline(input) { + return replace(trim(input), /\n/g, ''); +} + +function get_date(seconds, format) { + return trimnonewline( popen(`date -d @${seconds} "+${format}" 2>/dev/null`, 'r')?.read?.('line') ); +} + +// convert epoch date to given format +function epoch2date(epoch, format) { + if (!format || length(format) < 2) { + format = get_dateformat(); + // uci.unload('ddns'); //don't do this in uci.foreach loops + } + format = replace(format, /%n/g, '<br />'); // Replace '%n' with '<br />' + format = replace(format, /%t/g, ' '); // Replace '%t' with four spaces + + return get_date(epoch, format); +} + +// function to calculate seconds from given interval and unit +function calc_seconds(interval, unit) { + let parsedInterval = int(interval); + if (isnan(parsedInterval)) { + return null; + } + + switch (unit) { + case 'days': + return parsedInterval * 86400; // 60 sec * 60 min * 24 h + case 'hours': + return parsedInterval * 3600; // 60 sec * 60 min + case 'minutes': + return parsedInterval * 60; // 60 sec + case 'seconds': + return parsedInterval; + default: + return null; + } +} + +const methods = { + get_services_log: { + args: { service_name: 'service_name' }, + call: function(request) { + let result = 'File not found or empty'; + + // Get the log directory. Fall back to '/var/log/ddns' if not found + let logdir = uci.get('ddns', 'global', 'ddns_logdir') || ddns_log_path; + + // Fall back to default logdir with insecure path + if (match(logdir, /\.\.\//)) { + logdir = ddns_log_path; + } + + // Check if service_name is provided and log file exists + if (request.args && request.args.service_name && stat(`${logdir}/${request.args.service_name}.log`)?.type == 'file' ) { + result = readfile(`${logdir}/${request.args.service_name}.log`); + } + + uci.unload(); + return { result: result }; + } + }, + + get_services_status: { + call: function() { + const rundir = uci.get('ddns', 'global', 'ddns_rundir') || ddns_run_path; + // const dateFormat = get_dateformat(); + let res = {}; + + uci.foreach('ddns', 'service', function(s) { + /* uci.foreach danger zone: if you inadvertently call uci.unload('ddns') + anywhere in this foreach loop, you will produce some spectacular undefined behaviour */ + let ip, lastUpdate, nextUpdate; + const section = s['.name']; + if (section == '.anonymous') + return; + + if (stat(`${rundir}/${section}.ip`)?.type == 'file') { + ip = readfile(`${rundir}/${section}.ip`); + } else { + const dnsServer = s['dns_server'] || ''; + const forceIpVersion = int(s['force_ipversion'] || 0); + const forceDnsTcp = int(s['force_dnstcp'] || 0); + const isGlue = int(s['is_glue'] || 0); + const useIpv6 = int(s['use_ipv6'] || 0); + const lookupHost = s['lookup_host'] || '_nolookup_'; + let command = [luci_helper]; + + if (useIpv6 == 1) push(command, '-6'); + if (forceIpVersion == 1) push(command, '-f'); + if (forceDnsTcp == 1) push(command, '-t'); + if (isGlue == 1) push(command, '-g'); + + push(command, '-l', lookupHost); + push(command, '-S', section); + if (length(dnsServer) > 0) push(command, '-d', dnsServer); + push(command, '-- get_registered_ip'); + + const result = system(`${join(' ', command)}`); + } + + lastUpdate = int(readfile(`${rundir}/${section}.update`) || 0); + + let pid = int(readfile(`${rundir}/${section}.pid`) || 0); + + // if killcmd succeeds (0) to re-nice the process, we do not assume the pid is dead + if (pid > 0 && killcmd(pid)) { + pid = 0; + } + + let _uptime = int(uptime()); + + const forceSeconds = calc_seconds( + int(s['force_interval']) || 72, + s['force_unit'] || 'hours' + ); + + const checkSeconds = calc_seconds( + int(s['check_interval']) || 10, + s['check_unit'] || 'minutes' + ); + + let convertedLastUpdate; + if (lastUpdate > 0) { + const epoch = time() - _uptime + lastUpdate; + convertedLastUpdate = epoch2date(epoch); + // convertedLastUpdate = get_date(epoch, dateFormat); + nextUpdate = epoch2date(epoch + forceSeconds + checkSeconds); + // nextUpdate = get_date(epoch + forceSeconds + checkSeconds, dateFormat); + } + + if (pid > 0 && (lastUpdate + forceSeconds + checkSeconds - _uptime) <= 0) { + nextUpdate = 'Verify'; + } else if (forceSeconds === 0) { + nextUpdate = 'Run once'; + } else if (pid == 0 && s['enabled'] == '0') { + nextUpdate = 'Disabled'; + } else if (pid == 0 && s['enabled'] != '0') { + nextUpdate = 'Stopped'; + } + + res[section] = { + ip: ip ? replace(ip, '\n', '') : null, + last_update: lastUpdate !== 0 ? convertedLastUpdate : null, + next_update: nextUpdate || null, + pid: pid || null, + }; + }); + + uci.unload('ddns'); + return res; + } + }, + + get_ddns_state: { + call: function() { + // const dateFormat = get_dateformat(); + + const services_mtime = stat(ddns_package_path + '/list')?.mtime; + // uci.unload('ddns'); + let res = {}; + let ver, control; + + if (stat(opkg_info_path + `/${srv_name}.control`)?.type == 'file') { + control = readfile(opkg_info_path + `/${srv_name}.control`); + } + + for (let line in split(control, '\n')) { + ver = match(line, /^Version: (.+)$/)?.[1]; + if ( ver && length(ver) > 0 ) + break; + } + + ver = ver || trimnonewline(popen(`${luci_helper} -V | awk {'print $2'}`, 'r')?.read?.('line')); + + res['_version'] = ver; + res['_enabled'] = init_enabled('ddns'); + // res['_curr_dateformat'] = get_date(time(), dateFormat); + res['_curr_dateformat'] = epoch2date(time()); + res['_services_list'] = (services_mtime && epoch2date(services_mtime)) || 'NO_LIST'; + + uci.unload('ddns'); + return res; + } + }, + + get_env: { + call: function () { + let res = {}; + let cache = {}; + + const hasCommand = (command) => { + if (system(`command -v ${command}`) == 0) + return true; + else + return false; + }; + + const hasWget = () => hasCommand('wget'); + + const hasWgetSsl = () => { + if (cache['has_wgetssl']) return cache['has_wgetssl']; + const result = hasWget() && system(`wget 2>&1 | grep -iqF 'https'`) == 0 ? true: false; + cache['has_wgetssl'] = result; + return result; + }; + + const hasCurl = () => { + if (cache['has_curl']) return cache['has_curl']; + const result = hasCommand('curl'); + cache['has_curl'] = result; + return result; + }; + + const hasCurlSsl = () => { + return system(`curl -V 2>&1 | grep -qF 'https'`) == 0 ? true: false; + }; + + const hasFetch = () => { + if (cache['has_fetch']) return cache['has_fetch']; + const result = hasCommand('uclient-fetch'); + cache['has_fetch'] = result; + return result; + }; + + const hasFetchSsl = () => { + return stat('/lib/libustream-ssl.so') == 0 ? true: false; + }; + + const hasCurlPxy = () => { + return system(`grep -i 'all_proxy' /usr/lib/libcurl.so*`) == 0 ? true: false; + }; + + const hasBbwget = () => { + return system(`wget -V 2>&1 | grep -iqF 'busybox'`) == 0 ? true: false; + }; + + + res['has_wget'] = hasWget(); + res['has_curl'] = hasCurl(); + + res['has_ssl'] = hasWgetSsl() || hasCurlSsl() || (hasFetch() && hasFetchSsl()); + res['has_proxy'] = hasWgetSsl() || hasCurlPxy() || hasFetch() || hasBbwget(); + res['has_forceip'] = hasWgetSsl() || hasCurl() || hasFetch(); + res['has_bindnet'] = hasCurl() || hasWgetSsl(); + + const hasBindHost = () => { + if (cache['has_bindhost']) return cache['has_bindhost']; + const commands = ['host', 'khost', 'drill']; + for (let command in commands) { + if (hasCommand(command)) { + cache['has_bindhost'] = true; + return true; + } + } + + cache['has_bindhost'] = false; + return false; + }; + + res['has_bindhost'] = cache['has_bindhost'] || hasBindHost(); + + const hasHostIp = () => { + return hasCommand('hostip'); + }; + + const hasNslookup = () => { + return hasCommand('nslookup'); + }; + + res['has_dnsserver'] = cache['has_bindhost'] || hasNslookup() || hasHostIp() || hasBindHost(); + + const checkCerts = () => { + let present = false; + for (let cert in glob('/etc/ssl/certs/*.crt', '/etc/ssl/certs/*.pem')) { + if (cert != null) + present = true; + } + return present; + }; + + res['has_cacerts'] = checkCerts(); + + res['has_ipv6'] = (stat('/proc/net/ipv6_route')?.type == 'file' && + (stat('/usr/sbin/ip6tables')?.type == 'file' || stat('/usr/sbin/nft')?.type == 'file')); + + return res; + } + } +}; + +return { 'luci.ddns': methods }; |