* v0.9.55

- CGI Support (eg PHP, Perl, etc)
- Slight PC Admin updates
- Numerous bug fixes
- Security updates

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
zefie
2025-01-03 12:49:50 -05:00
committed by GitHub
parent 9314705def
commit 907cec23c2
28 changed files with 1637 additions and 576 deletions

View File

@@ -0,0 +1,32 @@
const prototypes = {
String: {
reverse: function () {
return this.split("").reverse().join("");
},
toHexString: function () {
var result = '';
for (var i = 0; i < this.length; i++) {
result += this.charCodeAt(i).toString(16);
}
return result;
}
},
Array: {
replace: function(sub, newSub) {
splits = this.split(sub, 2);
return Array.concat(splits[0], newSub, splits[1])
},
moveKey: function (from, to) {
this.splice(to, 0, this.splice(from, 1)[0]);
return this;
}
}
};
for (const [type, methods] of Object.entries(prototypes)) {
for (const [name, method] of Object.entries(methods)) {
if (!global[type].prototype[name]) {
global[type].prototype[name] = method;
}
}
}

View File

@@ -6,9 +6,18 @@ class WTVAdmin {
wtvr = null;
wtvshared = null;
wtvclient = null;
pcservices = false;
WTVClientSessionData = require("./WTVClientSessionData.js");
service_name = "wtv-admin";
SUCCESS = 0
FAIL = 1
INVALID_OP = 2
REASON_NOSELF = 3
REASON_EXISTS = 4
REASON_NONEXIST = 5
constructor(minisrv_config, wtvclient, service_name) {
this.minisrv_config = minisrv_config;
var { WTVShared } = require("./WTVShared.js");
@@ -16,10 +25,69 @@ class WTVAdmin {
this.wtvclient = wtvclient;
this.wtvshared = new WTVShared(minisrv_config);
this.wtvr = new WTVRegister(minisrv_config);
this.clientAddress = wtvclient.getClientAddress();
if (this.wtvclient.remoteAddress) {
// is a socket
this.clientAddress = this.wtvclient.remoteAddress;
this.pcservices = true;
} else {
// is wtvclient class
this.clientAddress = this.wtvclient.getClientAddress();
}
this.service_name = service_name;
}
banSSID(ssid, admin_ssid = null) {
if (ssid == admin_ssid) {
return this.REASON_NOSELF;
} else {
var fake_config = this.wtvshared.getUserConfig();
if (!fake_config.config) fake_config.config = {};
if (!fake_config.config.ssid_block_list) fake_config.config.ssid_block_list = [];
var entry_exists = false;
var self = this;
Object.keys(fake_config.config.ssid_block_list).forEach(function (k) {
if (fake_config.config.ssid_block_list[k] == ssid) {
return self.REASON_EXISTS;
}
});
if (!entry_exists) {
fake_config.config.ssid_block_list.push(ssid);
this.wtvshared.writeToUserConfig(fake_config);
return this.SUCCESS;
}
}
}
unbanSSID(ssid) {
var config_changed = false;
var fake_config = wtvshared.getUserConfig();
if (!fake_config.config) fake_config.config = {};
if (!fake_config.config.ssid_block_list) fake_config.config.ssid_block_list = [];
if (typeof request_headers.query.ssid === 'string') {
Object.keys(fake_config.config.ssid_block_list).forEach(function (k) {
if (fake_config.config.ssid_block_list[k].toLowerCase() == request_headers.query.ssid.toLowerCase()) {
fake_config.config.ssid_block_list.splice(k, 1);
config_changed = true
}
});
} else {
Object.keys(fake_config.config.ssid_block_list).forEach(function (k) {
Object.keys(request_headers.query.ssid).forEach(function (j) {
if (fake_config.config.ssid_block_list[k].toLowerCase() == request_headers.query.ssid[j].toLowerCase()) {
fake_config.config.ssid_block_list.splice(k, 1);
config_changed = true
}
});
});
}
if (config_changed) {
wtvshared.writeToUserConfig(fake_config);
minisrv_config = reloadConfig();
return this.SUCCESS
} else {
return this.REASON_NONEXIST;
}
}
ip2long(ip) {
var components;
@@ -53,47 +121,87 @@ class WTVAdmin {
rejectConnection(reason_is_ssid) {
var rejectReason;
if (reason_is_ssid) {
rejectReason = this.wtvclient.ssid + " is not in the whitelist.";
console.log(" * Request from SSID", this.wtvshared.filterSSID(this.wtvclient.ssid), "(" + this.clientAddress + ") for wtv-admin, but that SSID is not in the admin whitelist.");
if (this.pcservices) {
rejectReason = this.clientAddress + " is not in the whitelist for PC Services Admin.";
console.log(" * Request from IP (" + this.clientAddress + ") for PC Services Admin, but that IP is not authorized.");
} else {
rejectReason = this.clientAddress + " is not in the whitelist for SSID " + this.wtvclient.ssid + ".";
console.log(" * Request from SSID", this.wtvshared.filterSSID(this.wtvclient.ssid), "(" + this.clientAddress + ") for wtv-admin, but that IP is not authorized for that SSID.");
if (reason_is_ssid) {
rejectReason = this.wtvclient.ssid + " is not in the whitelist.";
console.log(" * Request from SSID", this.wtvshared.filterSSID(this.wtvclient.ssid), "(" + this.clientAddress + ") for wtv-admin, but that SSID is not in the admin whitelist.");
} else {
rejectReason = this.clientAddress + " is not in the whitelist for SSID " + this.wtvclient.ssid + ".";
console.log(" * Request from SSID", this.wtvshared.filterSSID(this.wtvclient.ssid), "(" + this.clientAddress + ") for wtv-admin, but that IP is not authorized for that SSID.");
}
}
return rejectReason;
}
checkPassword(password) {
if (this.minisrv_config.services[this.service_name].password) {
return (password == this.minisrv_config.services[this.service_name].password);
if (this.pcservices) {
if (this.minisrv_config.config.pc_admin.password) {
return (password == this.minisrv_config.config.pc_admin.password);
} else {
// no password set
return true;
}
} else {
// no password set
return true;
if (this.minisrv_config.services[this.service_name].password) {
return (password == this.minisrv_config.services[this.service_name].password);
} else {
// no password set
return true;
}
}
}
}
listRegisteredSSIDs() {
var search_dir = this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts");
var self = this;
var out = [];
this.fs.readdirSync(search_dir).forEach(file => {
if (self.fs.lstatSync(search_dir + self.path.sep + file).isDirectory()) {
var user = self.getAccountInfoBySSID(file);
out.push([file, user]);
}
});
return out;
}
isAuthorized(justchecking = false) {
var allowed_ssid = false;
var allowed_ip = false;
if (this.minisrv_config.services[this.service_name].authorized_ssids) {
var self = this;
Object.keys(self.minisrv_config.services[this.service_name].authorized_ssids).forEach(function (k) {
if (typeof self.minisrv_config.services[self.service_name].authorized_ssids[k] == "string") {
var ssid = self.minisrv_config.services[self.service_name].authorized_ssids[k]
if (ssid == self.wtvclient.ssid) allowed_ssid = true;
allowed_ip = true; // no ip block defined
} else {
var ssid = k;
if (ssid == self.wtvclient.ssid) {
allowed_ssid = true;
Object.keys(self.minisrv_config.services[self.service_name].authorized_ssids[k]).forEach(function (j) {
if (self.isInSubnet(self.clientAddress, self.minisrv_config.services[self.service_name].authorized_ssids[k][j])) {
allowed_ip = true;
}
});
var use_ssid = (this.wtvclient.ssid && !this.pcservices) ? true : false
if (use_ssid) {
if (this.minisrv_config.services[this.service_name].authorized_ssids) {
var self = this;
Object.keys(self.minisrv_config.services[this.service_name].authorized_ssids).forEach(function (k) {
if (typeof self.minisrv_config.services[self.service_name].authorized_ssids[k] == "string") {
var ssid = self.minisrv_config.services[self.service_name].authorized_ssids[k]
if (ssid == self.wtvclient.ssid) allowed_ssid = true;
allowed_ip = true; // no ip block defined
} else {
var ssid = k;
if (ssid == self.wtvclient.ssid) {
allowed_ssid = true;
Object.keys(self.minisrv_config.services[self.service_name].authorized_ssids[k]).forEach(function (j) {
if (self.isInSubnet(self.clientAddress, self.minisrv_config.services[self.service_name].authorized_ssids[k][j])) {
allowed_ip = true;
}
});
}
}
});
}
} else {
if (this.pcservices) {
if (this.minisrv_config.config.pc_admin.ip_whitelist) {
var self = this;
Object.keys(this.minisrv_config.config.pc_admin.ip_whitelist).forEach(function (k) {
allowed_ip = self.isInSubnet(self.clientAddress, self.minisrv_config.config.pc_admin.ip_whitelist[k]);
});
}
});
}
allowed_ssid = true;
}
if (justchecking) {
return (allowed_ssid && allowed_ip) ? true : false;
@@ -103,7 +211,7 @@ class WTVAdmin {
}
getAccountInfo(username, directory = null) {
var search_dir = this.minisrv_config.config.SessionStore + this.path.sep + "accounts";
var search_dir = this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts");
var account_data = null;
var self = this;
if (directory) search_dir = directory;
@@ -118,7 +226,7 @@ class WTVAdmin {
var temp_session_data = JSON.parse(temp_session_data_file);
if (temp_session_data.subscriber_username.toLowerCase() == username.toLowerCase()) {
account_data = [temp_session_data, (search_dir + self.path.sep + file).replace(this.minisrv_config.config.SessionStore + this.path.sep + "accounts", "").split(this.path.sep)[1]];
account_data = [temp_session_data, (search_dir + self.path.sep + file).replace(this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts"), "").split(this.path.sep)[1]];
}
} catch (e) {
console.error(" # Error parsing Session Data JSON", search_dir + self.path.sep + file, e);
@@ -145,7 +253,16 @@ class WTVAdmin {
if (userSession.isRegistered(false)) {
account_info.ssid = ssid;
account_info.account_users = userSession.listPrimaryAccountUsers();
account_info.username = account_info.account_users['subscriber'].subscriber_username;
if (account_info.account_users) {
if (account_info.account_users['subscriber']) {
account_info.username = account_info.account_users['subscriber'].subscriber_username;
} else {
account_info.username = account_info.account_users[0];
}
} else {
account_info.username = account_info.account_users[0];
}
account_info.user_id = 0;
return account_info;
}

View File

@@ -127,6 +127,19 @@ class WTVClientSessionData {
return true;
}
isAddressInAddressBook(addr) {
const addresses = this.getSessionData("address_book");
if (addresses) {
for (let i = 0; i < addresses.length; i++) {
console.log(addr.toLowerCase(), addresses[i].address.toLowerCase())
if (addr.toLowerCase() == addresses[i].address.toLowerCase()) {
return true;
}
}
}
return false;
}
findFreeUserSlot() {
if (this.user_id != 0) return false; // subscriber only command
var master_directory = this.getUserStoreDirectory(true);
@@ -158,10 +171,11 @@ class WTVClientSessionData {
var master_directory = this.getUserStoreDirectory(true);
var account_data = [];
var self = this;
this.debug(this.ssid)
this.fs.readdirSync(master_directory).forEach(f => {
if (self.fs.lstatSync(master_directory + self.path.sep + f).isDirectory()) {
if (f.substr(0, 4) == "user") {
var user_file = master_directory + self.path.sep + f + self.path.sep + f + ".json";
var user_file = this.path.resolve(master_directory + self.path.sep + f + self.path.sep + f + ".json");
if (self.fs.existsSync(user_file)) {
if (f == "user0") {
account_data['subscriber'] = JSON.parse(this.fs.readFileSync(user_file));
@@ -193,7 +207,7 @@ class WTVClientSessionData {
}
getAccountStoreDirectory() {
return this.path.resolve(this.wtvshared.getAbsolutePath() + this.path.sep + this.minisrv_config.config.SessionStore + this.path.sep + "accounts");
return this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts");
}
/**
@@ -205,7 +219,7 @@ class WTVClientSessionData {
if (user_id == null) user_id = this.user_id;
var userstore = this.getAccountStoreDirectory() + this.path.sep + this.ssid + this.path.sep;
if (!subscriber) userstore += "user" + user_id + this.path.sep;
return userstore;
return this.wtvshared.getAbsolutePath(userstore);
}
removeUser(user_id) {
@@ -264,7 +278,7 @@ class WTVClientSessionData {
if (!store_dir) return false; // unregistered
// FileStore
store_dir += "FileStore" + this.path.sep;
var store_dir_path = this.wtvshared.makeSafePath(store_dir, path.replace('/', this.path.sep));
var store_dir_path = this.wtvshared.getAbsolutePath(this.wtvshared.makeSafePath(store_dir, path.replace('/', this.path.sep)));
if (this.fs.existsSync(store_dir_path)) return this.fs.readFileSync(store_dir_path);
else return false;
}
@@ -611,8 +625,8 @@ class WTVClientSessionData {
}
getSessionData(key = null) {
if (typeof (this.data_store) === 'session_store') return null;
else if (key === null) return this.data_store;
if (typeof (this.session_store) === 'session_store') return null;
else if (key === null) return this.session_store;
else if (this.session_store[key]) return this.session_store[key];
else return null;
}

View File

@@ -376,7 +376,7 @@ class WTVMail {
checkUserExists(username, directory = null) {
// returns the user's ssid, and user_id and userid in an array if true, false if not
var search_dir = this.minisrv_config.config.SessionStore;
var search_dir = this.path.resolve(this.wtvshared.getAbsolutePath() + this.path.sep + this.minisrv_config.config.SessionStore);
var return_val = false;
var self = this;
if (directory) search_dir = directory;
@@ -388,9 +388,11 @@ class WTVMail {
try {
var temp_session_data_file = self.fs.readFileSync(search_dir + self.path.sep + file, 'Utf8');
var temp_session_data = JSON.parse(temp_session_data_file);
if (temp_session_data.subscriber_username.toLowerCase() == username.toLowerCase()) {
return_val = search_dir.replace(this.minisrv_config.config.SessionStore + self.path.sep + "accounts" + self.path.sep, '').replace("user", '').split(self.path.sep);
return_val.push(temp_session_data.subscriber_name);
if (temp_session_data.subscriber_username) {
if (temp_session_data.subscriber_username.toLowerCase() == username.toLowerCase()) {
return_val = search_dir.replace(this.minisrv_config.config.SessionStore + self.path.sep + "accounts" + self.path.sep, '').replace("user", '').split(self.path.sep);
return_val.push(temp_session_data.subscriber_name);
}
}
} catch (e) {
console.error(" # Error parsing Session Data JSON", file, e);

View File

@@ -228,13 +228,13 @@ class WTVNewsServer {
post_data.headers['Injection-Date'] = this.strftime("%a, %-d %b %Y %H:%M:%S %z", Date.parse(Date.now()))
// Reorder headers per examples in RFC3977 sect 6.2.1.3, not sure if needed
post_data.headers = this.wtvshared.moveObjectElement('Path', null, post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectElement('From', 'Path', post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectElement('Newsgroups', 'From', post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectElement('Subject', 'Newsgroups', post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectElement('Date', 'Subject', post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectElement('Organization', 'Date', post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectElement('Message-ID', 'Organization', post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectKey('Path', null, post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectKey('From', 'Path', post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectKey('Newsgroups', 'From', post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectKey('Subject', 'Newsgroups', post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectKey('Date', 'Subject', post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectKey('Organization', 'Date', post_data.headers, true);
post_data.headers = this.wtvshared.moveObjectKey('Message-ID', 'Organization', post_data.headers, true);
// end reordering of headers
if (this.articleExists(group, post_data.articleNumber)) return false // should not occur, but just in case

View File

@@ -95,9 +95,9 @@ class WTVRegister {
}
data += `<display nooptions>
<table width=520 align=center cellspacing=0 cellpadding=0>
<table width=480 align=center cellspacing=0 cellpadding=0>
<tr>
<td height=272>
<td height=242>
<font size=+1>
${main_content}
<p>

View File

@@ -349,7 +349,7 @@ class WTVShared {
utf8Decode(utf8String) {
if (typeof utf8String !== 'string') {
throw new TypeError('parameter <20>utf8String<6E> is not a string');
throw new TypeError('parameter <20>utf8String<6E> is not a string');
}
const textDecoder = new TextDecoder('utf-8');
const bytes = new Uint8Array(utf8String.split('').map(c => c.charCodeAt(0)));
@@ -480,57 +480,6 @@ class WTVShared {
else return this.parseSSID(ssid).manufacturer || null;
}
/**
* Moves an object to the desired location in the object (reorder)
* @param {string} currentKey Name of the object Key to move
* @param {string} afterKey Name of the object key to place currentKey after
* @param {object} obj The object to work on
* @param {boolean} caseInsensitive
* @returns {object} The modified object
*/
moveObjectElement(currentKey, afterKey, obj, caseInsensitive = false) {
let keys = Object.keys(obj);
let values = Object.values(obj);
if (caseInsensitive) {
const lowerCurrentKey = currentKey.toLowerCase();
const foundKey = keys.find(k => k.toLowerCase() === lowerCurrentKey);
currentKey = foundKey || currentKey;
}
const afterKeyIndex = typeof afterKey === 'string' ?
keys.findIndex(k => caseInsensitive ? k.toLowerCase() === afterKey.toLowerCase() : k === afterKey)
: -1;
if (afterKeyIndex === -1) {
// If afterKey is not found or not defined, don't move the element
return obj;
}
const currentIndex = keys.indexOf(currentKey);
if (currentIndex === -1) {
// If currentKey is not found, don't move the element
return obj;
}
// Remove the current element from keys and values
const [currentKeyValue] = values.splice(currentIndex, 1);
keys.splice(currentIndex, 1);
// Insert the current element after the afterKey position
keys.splice(afterKeyIndex + 1, 0, currentKey);
values.splice(afterKeyIndex + 1, 0, currentKeyValue);
// Reconstruct the object with the new order
const result = {};
keys.forEach((key, index) => {
result[key] = values[index];
});
return result;
}
readMiniSrvConfig(user_config = true, notices = true, reload_notice = false) {
const log = (msg) => {
if (notices || reload_notice) console.log(msg);
@@ -608,7 +557,7 @@ class WTVShared {
var new_user_config = {};
Object.assign(new_user_config, minisrv_user_config, config);
if (this.minisrv_config.config.debug_flags.debug) console.log(" * Writing new user configuration...");
this.fs.writeFileSync(this.getAbsolutePath("user_config.json", this.parentDirectory), JSON.stringify(new_user_config, null, "\t"));
this.fs.writeFileSync(this.getAbsolutePath("user_config.json", this.appdir), JSON.stringify(new_user_config, null, "\t"));
return true;
}
catch (e) {
@@ -827,6 +776,7 @@ class WTVShared {
}
});
}
delete newobj.raw_headers;
return newobj;
}
@@ -1160,6 +1110,67 @@ class WTVShared {
getTemplate(service_name, path, path_only = false) {
return this.getServiceDep(service_name + this.path.sep + path, path_only, true);
}
/**
* Finds a key in an object
* @param {string} key The key to find
* @param {object} obj The object to search
* @param {boolean} case_insensitive Search case insensitive
* @returns
*/
findObjectKeyIndex(key, obj, case_insensitive = false) {
const keys = Object.keys(obj);
if (case_insensitive) {
key = key.toLowerCase();
return keys.findIndex(k => k.toLowerCase() === key);
}
return keys.indexOf(key);
}
/**
* Moves an object to the desired location in the object (reorder)
* @param {string|int} currentKey Name of the object Key to move or the index to move
* @param {string|int} destKey Name of the object key to place currentKey after or the index to place it at
* @param {object} obj The object to work on
* @param {boolean} case_insensitive Search case insensitive
* @returns {object} The modified object
*/
moveObjectKey(currentKey, destKey, obj, case_insensitive = false) {
let keys = Object.keys(obj);
let values = Object.values(obj);
const currentIndex = typeof currentKey === 'string' ? this.findObjectKeyIndex(currentKey, obj, case_insensitive) : parseInt(currentKey);
if (currentIndex === -1) return obj;
var destIndex = typeof destKey === 'string' ? this.findObjectKeyIndex(destKey, obj, case_insensitive) : parseInt(destKey);
// Bump by one if the destKey is a string (put after the key)
if (typeof destKey === 'string' && destIndex !== -1) destIndex++;
// If destKey is not found or not defined, don't move the element
if (isNaN(destIndex)) return obj;
// Remove the current element from keys and values
const [currentKeyValue] = values.splice(currentIndex, 1);
const [currentKeyName] = keys.splice(currentIndex, 1);
// Insert the current element after the destKey position
keys.splice(destIndex, 0, currentKeyName);
values.splice(destIndex, 0, currentKeyValue);
// Reconstruct the object with the new order
const result = {};
keys.forEach((key, index) => {
result[key] = values[index];
});
return result;
}
getCaseInsensitiveKey(key, obj) {
const foundKey = Object.keys(obj).find(k => k.toLowerCase() === key.toLowerCase());
return foundKey || null;
}
}
class clientShowAlert {