204 lines
5.3 KiB
Plaintext
Executable File
204 lines
5.3 KiB
Plaintext
Executable File
#!/usr/bin/ucode
|
|
/*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*
|
|
* Copyright (C) 2023 ImmortalWrt.org
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import { access, error, lstat, mkstemp, popen, readfile, writefile } from 'fs';
|
|
|
|
/* Kanged from ucode/luci */
|
|
function shellquote(s) {
|
|
return `'${replace(s, "'", "'\\''")}'`;
|
|
}
|
|
|
|
const HP_DIR = '/etc/homeproxy';
|
|
const RUN_DIR = '/var/run/homeproxy';
|
|
|
|
const methods = {
|
|
acllist_read: {
|
|
args: { type: 'type' },
|
|
call: function(req) {
|
|
if (index(['direct_list', 'proxy_list'], req.args?.type) === -1)
|
|
return { content: null, error: 'illegal type' };
|
|
|
|
const filecontent = readfile(`${HP_DIR}/resources/${req.args?.type}.txt`);
|
|
return { content: filecontent };
|
|
}
|
|
},
|
|
acllist_write: {
|
|
args: { type: 'type', content: 'content' },
|
|
call: function(req) {
|
|
if (index(['direct_list', 'proxy_list'], req.args?.type) === -1)
|
|
return { result: false, error: 'illegal type' };
|
|
|
|
const file = `${HP_DIR}/resources/${req.args?.type}.txt`;
|
|
let content = req.args?.content;
|
|
|
|
/* Sanitize content */
|
|
if (content) {
|
|
content = trim(content);
|
|
content = replace(content, /\r\n?/g, '\n');
|
|
if (!match(content, /\n$/))
|
|
content += '\n';
|
|
}
|
|
|
|
system(`mkdir -p ${HP_DIR}/resources`);
|
|
writefile(file, content);
|
|
|
|
return { result: true };
|
|
}
|
|
},
|
|
|
|
certificate_write: {
|
|
args: { filename: 'filename' },
|
|
call: function(req) {
|
|
const writeCertificate = function(filename, priv) {
|
|
const tmpcert = '/tmp/homeproxy_certificate.tmp';
|
|
const filestat = lstat(tmpcert);
|
|
|
|
if (!filestat || filestat.type !== 'file' || filestat.size <= 0) {
|
|
system(`rm -f ${tmpcert}`);
|
|
return { result: false, error: 'empty certificate file' };
|
|
}
|
|
|
|
let filecontent = readfile(tmpcert);
|
|
if (is_binary(filecontent)) {
|
|
system(`rm -f ${tmpcert}`);
|
|
return { result: false, error: 'illegal file type: binary' };
|
|
}
|
|
|
|
/* Kanged from luci-proto-openconnect */
|
|
const beg = priv ? /^-----BEGIN (RSA|EC) PRIVATE KEY-----$/ : /^-----BEGIN CERTIFICATE-----$/,
|
|
end = priv ? /^-----END (RSA|EC) PRIVATE KEY-----$/ : /^-----END CERTIFICATE-----$/,
|
|
lines = split(trim(filecontent), /[\r\n]/);
|
|
let start = false, i;
|
|
|
|
for (i = 0; i < length(lines); i++) {
|
|
if (match(lines[i], beg))
|
|
start = true;
|
|
else if (start && !b64dec(lines[i]) && length(lines[i]) !== 64)
|
|
break;
|
|
}
|
|
|
|
if (!start || i < length(lines) - 1 || !match(lines[i], end)) {
|
|
system(`rm -f ${tmpcert}`);
|
|
return { result: false, error: 'this does not look like a correct PEM file' };
|
|
}
|
|
|
|
/* Sanitize certificate */
|
|
filecontent = trim(filecontent);
|
|
filecontent = replace(filecontent, /\r\n?/g, '\n');
|
|
if (!match(filecontent, /\n$/))
|
|
filecontent += '\n';
|
|
|
|
system(`mkdir -p ${HP_DIR}/certs`);
|
|
writefile(`${HP_DIR}/certs/${filename}.pem`, filecontent);
|
|
system(`rm -f ${tmpcert}`);
|
|
|
|
return { result: true };
|
|
};
|
|
|
|
const filename = req.args?.filename;
|
|
switch (filename) {
|
|
case 'client_ca':
|
|
case 'server_publickey':
|
|
return writeCertificate(filename, false);
|
|
break;
|
|
case 'server_privatekey':
|
|
return writeCertificate(filename, true);
|
|
break;
|
|
default:
|
|
return { result: false, error: 'illegal cerificate filename' };
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
connection_check: {
|
|
args: { site: 'site' },
|
|
call: function(req) {
|
|
let url;
|
|
switch(req.args?.site) {
|
|
case 'baidu':
|
|
url = 'https://www.baidu.com';
|
|
break;
|
|
case 'google':
|
|
url = 'https://www.google.com';
|
|
break;
|
|
default:
|
|
return { result: false, error: 'illegal site' };
|
|
break;
|
|
}
|
|
|
|
return { result: (system(`/usr/bin/wget --spider -qT3 ${url} 2>"/dev/null"`, 3100) === 0) };
|
|
}
|
|
},
|
|
|
|
log_clean: {
|
|
args: { type: 'type' },
|
|
call: function(req) {
|
|
if (!(req.args?.type in ['homeproxy', 'sing-box-c', 'sing-box-s']))
|
|
return { result: false, error: 'illegal type' };
|
|
|
|
const filestat = lstat(`${RUN_DIR}/${req.args?.type}.log`);
|
|
if (filestat)
|
|
writefile(`${RUN_DIR}/${req.args?.type}.log`, '');
|
|
return { result: true };
|
|
}
|
|
},
|
|
|
|
singbox_get_features: {
|
|
call: function() {
|
|
let features = {};
|
|
|
|
const fd = popen('/usr/bin/sing-box version');
|
|
if (fd) {
|
|
for (let line = fd.read('line'); length(line); line = fd.read('line')) {
|
|
if (match(trim(line), /Environment: (go[0-9\.]+)/))
|
|
features['has_mptcp'] = (index(line, 'go1.20') === -1) ? true : false;
|
|
|
|
let tags = match(trim(line), /Tags: (.*)/);
|
|
if (!tags)
|
|
continue;
|
|
|
|
for (let i in split(tags[1], ','))
|
|
features[i] = true;
|
|
}
|
|
|
|
fd.close();
|
|
}
|
|
|
|
features.hp_has_chinadns_ng = access('/usr/bin/chinadns-ng');
|
|
features.hp_has_ip_full = access('/usr/libexec/ip-full');
|
|
features.hp_has_tproxy = access('/etc/modules.d/nft-tproxy');
|
|
features.hp_has_tun = access('/etc/modules.d/30-tun');
|
|
|
|
return features;
|
|
}
|
|
},
|
|
|
|
resources_get_version: {
|
|
args: { type: 'type' },
|
|
call: function(req) {
|
|
const version = trim(readfile(`${HP_DIR}/resources/${req.args?.type}.ver`));
|
|
return { version: version, error: error() };
|
|
}
|
|
},
|
|
resources_update: {
|
|
args: { type: 'type' },
|
|
call: function(req) {
|
|
if (req.args?.type) {
|
|
const type = shellquote(req.args?.type);
|
|
const exit_code = system(`${HP_DIR}/scripts/update_resources.sh ${type}`);
|
|
return { status: exit_code };
|
|
} else
|
|
return { status: 255, error: 'illegal type' };
|
|
}
|
|
}
|
|
};
|
|
|
|
return { 'luci.homeproxy': methods };
|