@lordbex/thelounge 4.4.3-blowfish
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.thelounge_home +1 -0
- package/LICENSE +22 -0
- package/README.md +95 -0
- package/client/index.html.tpl +69 -0
- package/dist/defaults/config.js +465 -0
- package/dist/package.json +174 -0
- package/dist/server/client.js +678 -0
- package/dist/server/clientManager.js +220 -0
- package/dist/server/command-line/index.js +85 -0
- package/dist/server/command-line/install.js +123 -0
- package/dist/server/command-line/outdated.js +30 -0
- package/dist/server/command-line/start.js +34 -0
- package/dist/server/command-line/storage.js +103 -0
- package/dist/server/command-line/uninstall.js +40 -0
- package/dist/server/command-line/upgrade.js +64 -0
- package/dist/server/command-line/users/add.js +67 -0
- package/dist/server/command-line/users/edit.js +39 -0
- package/dist/server/command-line/users/index.js +17 -0
- package/dist/server/command-line/users/list.js +53 -0
- package/dist/server/command-line/users/remove.js +37 -0
- package/dist/server/command-line/users/reset.js +64 -0
- package/dist/server/command-line/utils.js +177 -0
- package/dist/server/config.js +138 -0
- package/dist/server/helper.js +161 -0
- package/dist/server/identification.js +139 -0
- package/dist/server/index.js +3 -0
- package/dist/server/log.js +35 -0
- package/dist/server/models/chan.js +275 -0
- package/dist/server/models/msg.js +92 -0
- package/dist/server/models/network.js +546 -0
- package/dist/server/models/prefix.js +31 -0
- package/dist/server/models/user.js +42 -0
- package/dist/server/plugins/auth/ldap.js +188 -0
- package/dist/server/plugins/auth/local.js +41 -0
- package/dist/server/plugins/auth.js +70 -0
- package/dist/server/plugins/changelog.js +103 -0
- package/dist/server/plugins/clientCertificate.js +115 -0
- package/dist/server/plugins/dev-server.js +33 -0
- package/dist/server/plugins/inputs/action.js +54 -0
- package/dist/server/plugins/inputs/away.js +20 -0
- package/dist/server/plugins/inputs/ban.js +45 -0
- package/dist/server/plugins/inputs/blow.js +44 -0
- package/dist/server/plugins/inputs/connect.js +41 -0
- package/dist/server/plugins/inputs/ctcp.js +29 -0
- package/dist/server/plugins/inputs/disconnect.js +15 -0
- package/dist/server/plugins/inputs/ignore.js +74 -0
- package/dist/server/plugins/inputs/ignorelist.js +50 -0
- package/dist/server/plugins/inputs/index.js +105 -0
- package/dist/server/plugins/inputs/invite.js +31 -0
- package/dist/server/plugins/inputs/kick.js +26 -0
- package/dist/server/plugins/inputs/kill.js +13 -0
- package/dist/server/plugins/inputs/list.js +12 -0
- package/dist/server/plugins/inputs/mode.js +55 -0
- package/dist/server/plugins/inputs/msg.js +106 -0
- package/dist/server/plugins/inputs/mute.js +56 -0
- package/dist/server/plugins/inputs/nick.js +55 -0
- package/dist/server/plugins/inputs/notice.js +42 -0
- package/dist/server/plugins/inputs/part.js +46 -0
- package/dist/server/plugins/inputs/quit.js +27 -0
- package/dist/server/plugins/inputs/raw.js +13 -0
- package/dist/server/plugins/inputs/rejoin.js +25 -0
- package/dist/server/plugins/inputs/topic.js +24 -0
- package/dist/server/plugins/inputs/whois.js +19 -0
- package/dist/server/plugins/irc-events/away.js +59 -0
- package/dist/server/plugins/irc-events/cap.js +62 -0
- package/dist/server/plugins/irc-events/chghost.js +29 -0
- package/dist/server/plugins/irc-events/connection.js +152 -0
- package/dist/server/plugins/irc-events/ctcp.js +72 -0
- package/dist/server/plugins/irc-events/error.js +80 -0
- package/dist/server/plugins/irc-events/help.js +21 -0
- package/dist/server/plugins/irc-events/info.js +21 -0
- package/dist/server/plugins/irc-events/invite.js +27 -0
- package/dist/server/plugins/irc-events/join.js +53 -0
- package/dist/server/plugins/irc-events/kick.js +39 -0
- package/dist/server/plugins/irc-events/link.js +442 -0
- package/dist/server/plugins/irc-events/list.js +47 -0
- package/dist/server/plugins/irc-events/message.js +187 -0
- package/dist/server/plugins/irc-events/mode.js +124 -0
- package/dist/server/plugins/irc-events/modelist.js +67 -0
- package/dist/server/plugins/irc-events/motd.js +29 -0
- package/dist/server/plugins/irc-events/names.js +21 -0
- package/dist/server/plugins/irc-events/nick.js +45 -0
- package/dist/server/plugins/irc-events/part.js +35 -0
- package/dist/server/plugins/irc-events/quit.js +32 -0
- package/dist/server/plugins/irc-events/sasl.js +26 -0
- package/dist/server/plugins/irc-events/topic.js +42 -0
- package/dist/server/plugins/irc-events/unhandled.js +31 -0
- package/dist/server/plugins/irc-events/welcome.js +22 -0
- package/dist/server/plugins/irc-events/whois.js +57 -0
- package/dist/server/plugins/messageStorage/sqlite.js +454 -0
- package/dist/server/plugins/messageStorage/text.js +124 -0
- package/dist/server/plugins/packages/index.js +200 -0
- package/dist/server/plugins/packages/publicClient.js +66 -0
- package/dist/server/plugins/packages/themes.js +61 -0
- package/dist/server/plugins/storage.js +88 -0
- package/dist/server/plugins/sts.js +85 -0
- package/dist/server/plugins/uploader.js +267 -0
- package/dist/server/plugins/webpush.js +99 -0
- package/dist/server/server.js +857 -0
- package/dist/server/storageCleaner.js +131 -0
- package/dist/server/utils/fish.js +432 -0
- package/dist/shared/irc.js +19 -0
- package/dist/shared/linkify.js +81 -0
- package/dist/shared/types/chan.js +22 -0
- package/dist/shared/types/changelog.js +2 -0
- package/dist/shared/types/config.js +2 -0
- package/dist/shared/types/mention.js +2 -0
- package/dist/shared/types/msg.js +34 -0
- package/dist/shared/types/network.js +2 -0
- package/dist/shared/types/storage.js +2 -0
- package/dist/shared/types/user.js +2 -0
- package/dist/webpack.config.js +224 -0
- package/index.js +38 -0
- package/package.json +174 -0
- package/public/audio/pop.wav +0 -0
- package/public/css/style.css +12 -0
- package/public/css/style.css.map +1 -0
- package/public/favicon.ico +0 -0
- package/public/fonts/fa-solid-900.woff +0 -0
- package/public/fonts/fa-solid-900.woff2 +0 -0
- package/public/img/favicon-alerted.ico +0 -0
- package/public/img/icon-alerted-black-transparent-bg-72x72px.png +0 -0
- package/public/img/icon-alerted-grey-bg-192x192px.png +0 -0
- package/public/img/icon-black-transparent-bg.svg +1 -0
- package/public/img/logo-grey-bg-120x120px.png +0 -0
- package/public/img/logo-grey-bg-152x152px.png +0 -0
- package/public/img/logo-grey-bg-167x167px.png +0 -0
- package/public/img/logo-grey-bg-180x180px.png +0 -0
- package/public/img/logo-grey-bg-192x192px.png +0 -0
- package/public/img/logo-grey-bg-512x512px.png +0 -0
- package/public/img/logo-grey-bg.svg +1 -0
- package/public/img/logo-horizontal-transparent-bg-inverted.svg +1 -0
- package/public/img/logo-horizontal-transparent-bg.svg +1 -0
- package/public/img/logo-transparent-bg-inverted.svg +1 -0
- package/public/img/logo-transparent-bg.svg +1 -0
- package/public/img/logo-vertical-transparent-bg-inverted.svg +1 -0
- package/public/img/logo-vertical-transparent-bg.svg +1 -0
- package/public/js/bundle.js +2 -0
- package/public/js/bundle.js.map +1 -0
- package/public/js/bundle.vendor.js +3 -0
- package/public/js/bundle.vendor.js.LICENSE.txt +18 -0
- package/public/js/bundle.vendor.js.map +1 -0
- package/public/js/loading-error-handlers.js +1 -0
- package/public/robots.txt +2 -0
- package/public/service-worker.js +1 -0
- package/public/thelounge.webmanifest +53 -0
- package/public/themes/default.css +35 -0
- package/public/themes/morning.css +183 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const net_1 = __importDefault(require("net"));
|
|
11
|
+
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
12
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
13
|
+
const Helper = {
|
|
14
|
+
expandHome,
|
|
15
|
+
getVersion,
|
|
16
|
+
getVersionCacheBust,
|
|
17
|
+
getVersionNumber,
|
|
18
|
+
getGitCommit,
|
|
19
|
+
ip2hex,
|
|
20
|
+
parseHostmask,
|
|
21
|
+
compareHostmask,
|
|
22
|
+
compareWithWildcard,
|
|
23
|
+
catch_to_error,
|
|
24
|
+
password: {
|
|
25
|
+
hash: passwordHash,
|
|
26
|
+
compare: passwordCompare,
|
|
27
|
+
requiresUpdate: passwordRequiresUpdate,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
exports.default = Helper;
|
|
31
|
+
function getVersion() {
|
|
32
|
+
const gitCommit = getGitCommit();
|
|
33
|
+
const version = `v${package_json_1.default.version}`;
|
|
34
|
+
return gitCommit ? `source (${gitCommit} / ${version})` : version;
|
|
35
|
+
}
|
|
36
|
+
function getVersionNumber() {
|
|
37
|
+
return package_json_1.default.version;
|
|
38
|
+
}
|
|
39
|
+
let _fetchedGitCommit = false;
|
|
40
|
+
let _gitCommit = null;
|
|
41
|
+
function getGitCommit() {
|
|
42
|
+
if (_fetchedGitCommit) {
|
|
43
|
+
return _gitCommit;
|
|
44
|
+
}
|
|
45
|
+
_fetchedGitCommit = true;
|
|
46
|
+
// --git-dir ".git" makes git only check current directory for `.git`, and not travel upwards
|
|
47
|
+
// We set cwd to the location of `index.js` as soon as the process is started
|
|
48
|
+
try {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
50
|
+
_gitCommit = require("child_process")
|
|
51
|
+
.execSync('git --git-dir ".git" rev-parse --short HEAD', // Returns hash of current commit
|
|
52
|
+
{ stdio: ["ignore", "pipe", "ignore"] })
|
|
53
|
+
.toString()
|
|
54
|
+
.trim();
|
|
55
|
+
return _gitCommit;
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
// Not a git repository or git is not installed
|
|
59
|
+
_gitCommit = null;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function getVersionCacheBust() {
|
|
64
|
+
const hash = crypto_1.default.createHash("sha256").update(Helper.getVersion()).digest("hex");
|
|
65
|
+
return hash.substring(0, 10);
|
|
66
|
+
}
|
|
67
|
+
function ip2hex(address) {
|
|
68
|
+
// no ipv6 support
|
|
69
|
+
if (!net_1.default.isIPv4(address)) {
|
|
70
|
+
return "00000000";
|
|
71
|
+
}
|
|
72
|
+
return address
|
|
73
|
+
.split(".")
|
|
74
|
+
.map(function (octet) {
|
|
75
|
+
let hex = parseInt(octet, 10).toString(16);
|
|
76
|
+
if (hex.length === 1) {
|
|
77
|
+
hex = "0" + hex;
|
|
78
|
+
}
|
|
79
|
+
return hex;
|
|
80
|
+
})
|
|
81
|
+
.join("");
|
|
82
|
+
}
|
|
83
|
+
// Expand ~ into the current user home dir.
|
|
84
|
+
// This does *not* support `~other_user/tmp` => `/home/other_user/tmp`.
|
|
85
|
+
function expandHome(shortenedPath) {
|
|
86
|
+
if (!shortenedPath) {
|
|
87
|
+
return "";
|
|
88
|
+
}
|
|
89
|
+
const home = os_1.default.homedir().replace("$", "$$$$");
|
|
90
|
+
return path_1.default.resolve(shortenedPath.replace(/^~($|\/|\\)/, home + "$1"));
|
|
91
|
+
}
|
|
92
|
+
function passwordRequiresUpdate(password) {
|
|
93
|
+
return bcryptjs_1.default.getRounds(password) !== 11;
|
|
94
|
+
}
|
|
95
|
+
function passwordHash(password) {
|
|
96
|
+
return bcryptjs_1.default.hashSync(password, bcryptjs_1.default.genSaltSync(11));
|
|
97
|
+
}
|
|
98
|
+
function passwordCompare(password, expected) {
|
|
99
|
+
return bcryptjs_1.default.compare(password, expected);
|
|
100
|
+
}
|
|
101
|
+
function parseHostmask(hostmask) {
|
|
102
|
+
let nick = "";
|
|
103
|
+
let ident = "*";
|
|
104
|
+
let hostname = "*";
|
|
105
|
+
let parts = [];
|
|
106
|
+
// Parse hostname first, then parse the rest
|
|
107
|
+
parts = hostmask.split("@");
|
|
108
|
+
if (parts.length >= 2) {
|
|
109
|
+
hostname = parts[1] || "*";
|
|
110
|
+
hostmask = parts[0];
|
|
111
|
+
}
|
|
112
|
+
hostname = hostname.toLowerCase();
|
|
113
|
+
parts = hostmask.split("!");
|
|
114
|
+
if (parts.length >= 2) {
|
|
115
|
+
ident = parts[1] || "*";
|
|
116
|
+
hostmask = parts[0];
|
|
117
|
+
}
|
|
118
|
+
ident = ident.toLowerCase();
|
|
119
|
+
nick = hostmask.toLowerCase() || "*";
|
|
120
|
+
const result = {
|
|
121
|
+
nick: nick,
|
|
122
|
+
ident: ident,
|
|
123
|
+
hostname: hostname,
|
|
124
|
+
};
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
function compareHostmask(a, b) {
|
|
128
|
+
return (compareWithWildcard(a.nick, b.nick) &&
|
|
129
|
+
compareWithWildcard(a.ident, b.ident) &&
|
|
130
|
+
compareWithWildcard(a.hostname, b.hostname));
|
|
131
|
+
}
|
|
132
|
+
function compareWithWildcard(a, b) {
|
|
133
|
+
// we allow '*' and '?' wildcards in our comparison.
|
|
134
|
+
// this is mostly aligned with https://modern.ircdocs.horse/#wildcard-expressions
|
|
135
|
+
// but we do not support the escaping. The ABNF does not seem to be clear as to
|
|
136
|
+
// how to escape the escape char '\', which is valid in a nick,
|
|
137
|
+
// whereas the wildcards tend not to be (as per RFC1459).
|
|
138
|
+
// The "*" wildcard is ".*" in regex, "?" is "."
|
|
139
|
+
// so we tokenize and join with the proper char back together,
|
|
140
|
+
// escaping any other regex modifier
|
|
141
|
+
const wildmany_split = a.split("*").map((sub) => {
|
|
142
|
+
const wildone_split = sub.split("?").map((p) => lodash_1.default.escapeRegExp(p));
|
|
143
|
+
return wildone_split.join(".");
|
|
144
|
+
});
|
|
145
|
+
const user_regex = wildmany_split.join(".*");
|
|
146
|
+
const re = new RegExp(`^${user_regex}$`, "i"); // case insensitive
|
|
147
|
+
return re.test(b);
|
|
148
|
+
}
|
|
149
|
+
function catch_to_error(prefix, err) {
|
|
150
|
+
let msg;
|
|
151
|
+
if (err instanceof Error) {
|
|
152
|
+
msg = err.message;
|
|
153
|
+
}
|
|
154
|
+
else if (typeof err === "string") {
|
|
155
|
+
msg = err;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
msg = err.toString();
|
|
159
|
+
}
|
|
160
|
+
return new Error(`${prefix}: ${msg}`);
|
|
161
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const net_1 = __importDefault(require("net"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const helper_1 = __importDefault(require("./helper"));
|
|
10
|
+
const config_1 = __importDefault(require("./config"));
|
|
11
|
+
const log_1 = __importDefault(require("./log"));
|
|
12
|
+
class Identification {
|
|
13
|
+
connectionId;
|
|
14
|
+
connections;
|
|
15
|
+
oidentdFile;
|
|
16
|
+
constructor(startedCallback) {
|
|
17
|
+
this.connectionId = 0;
|
|
18
|
+
this.connections = new Map();
|
|
19
|
+
if (typeof config_1.default.values.oidentd === "string") {
|
|
20
|
+
this.oidentdFile = helper_1.default.expandHome(config_1.default.values.oidentd);
|
|
21
|
+
log_1.default.info(`Oidentd file: ${chalk_1.default.green(this.oidentdFile)}`);
|
|
22
|
+
this.refresh();
|
|
23
|
+
}
|
|
24
|
+
if (config_1.default.values.identd.enable) {
|
|
25
|
+
if (this.oidentdFile) {
|
|
26
|
+
log_1.default.warn("Using both identd and oidentd at the same time, this is most likely not intended.");
|
|
27
|
+
}
|
|
28
|
+
const server = net_1.default.createServer(this.serverConnection.bind(this));
|
|
29
|
+
server.on("error", (err) => {
|
|
30
|
+
startedCallback(this, err);
|
|
31
|
+
});
|
|
32
|
+
server.listen({
|
|
33
|
+
port: config_1.default.values.identd.port || 113,
|
|
34
|
+
host: config_1.default.values.bind,
|
|
35
|
+
}, () => {
|
|
36
|
+
const address = server.address();
|
|
37
|
+
if (typeof address === "string") {
|
|
38
|
+
log_1.default.info(`Identd server available on ${chalk_1.default.green(address)}`);
|
|
39
|
+
}
|
|
40
|
+
else if (address?.address) {
|
|
41
|
+
log_1.default.info(`Identd server available on ${chalk_1.default.green(address.address + ":" + address.port.toString())}`);
|
|
42
|
+
}
|
|
43
|
+
startedCallback(this);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
startedCallback(this);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
serverConnection(socket) {
|
|
51
|
+
socket.on("error", (err) => log_1.default.error(`Identd socket error: ${err}`));
|
|
52
|
+
socket.setTimeout(5000, () => {
|
|
53
|
+
log_1.default.warn(`identd: no data received, closing connection to ${socket.remoteAddress || "undefined"}`);
|
|
54
|
+
socket.destroy();
|
|
55
|
+
});
|
|
56
|
+
socket.once("data", (data) => {
|
|
57
|
+
this.respondToIdent(socket, data);
|
|
58
|
+
socket.end();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
respondToIdent(socket, buffer) {
|
|
62
|
+
if (!socket.remoteAddress) {
|
|
63
|
+
log_1.default.warn("identd: no remote address");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const data = buffer.toString().split(",");
|
|
67
|
+
const lport = parseInt(data[0], 10) || 0;
|
|
68
|
+
const fport = parseInt(data[1], 10) || 0;
|
|
69
|
+
if (lport < 1 || fport < 1 || lport > 65535 || fport > 65535) {
|
|
70
|
+
log_1.default.warn(`identd: bogus request from ${socket.remoteAddress}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
log_1.default.debug(`identd: remote ${socket.remoteAddress} query ${lport}, ${fport}`);
|
|
74
|
+
for (const connection of this.connections.values()) {
|
|
75
|
+
// we only want to respond if all the ip,port tuples match, to avoid user enumeration
|
|
76
|
+
if (connection.socket.remotePort === fport &&
|
|
77
|
+
connection.socket.localPort === lport &&
|
|
78
|
+
socket.remoteAddress === connection.socket.remoteAddress &&
|
|
79
|
+
socket.localAddress === connection.socket.localAddress) {
|
|
80
|
+
const reply = `${lport}, ${fport} : USERID : TheLounge : ${connection.user}\r\n`;
|
|
81
|
+
log_1.default.debug(`identd: reply is ${reply.trimEnd()}`);
|
|
82
|
+
socket.write(reply);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const reply = `${lport}, ${fport} : ERROR : NO-USER\r\n`;
|
|
87
|
+
log_1.default.debug(`identd: reply is ${reply.trimEnd()}`);
|
|
88
|
+
socket.write(reply);
|
|
89
|
+
}
|
|
90
|
+
addSocket(socket, user) {
|
|
91
|
+
const id = ++this.connectionId;
|
|
92
|
+
this.connections.set(id, { socket, user });
|
|
93
|
+
if (this.oidentdFile) {
|
|
94
|
+
this.refresh();
|
|
95
|
+
}
|
|
96
|
+
return id;
|
|
97
|
+
}
|
|
98
|
+
removeSocket(id) {
|
|
99
|
+
this.connections.delete(id);
|
|
100
|
+
if (this.oidentdFile) {
|
|
101
|
+
this.refresh();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
refresh() {
|
|
105
|
+
let file = "# Warning: file generated by The Lounge: changes will be overwritten!\n";
|
|
106
|
+
this.connections.forEach((connection, id) => {
|
|
107
|
+
if (!connection.socket.remotePort || !connection.socket.localPort) {
|
|
108
|
+
// Race condition: this can happen when more than one socket gets disconnected at
|
|
109
|
+
// once, since we `refresh()` for each one being added/removed. This results
|
|
110
|
+
// in there possibly being one or more disconnected sockets remaining when we get here.
|
|
111
|
+
log_1.default.warn(`oidentd: socket has no remote or local port (id=${id}). See https://github.com/thelounge/thelounge/pull/4695.`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (!connection.socket.remoteAddress) {
|
|
115
|
+
log_1.default.warn(`oidentd: socket has no remote address, will not respond to queries`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!connection.socket.localAddress) {
|
|
119
|
+
log_1.default.warn(`oidentd: socket has no local address, will not respond to queries`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// we only want to respond if all the ip,port tuples match, to avoid user enumeration
|
|
123
|
+
file +=
|
|
124
|
+
`to ${connection.socket.remoteAddress}` +
|
|
125
|
+
` fport ${connection.socket.remotePort}` +
|
|
126
|
+
` from ${connection.socket.localAddress}` +
|
|
127
|
+
` lport ${connection.socket.localPort}` +
|
|
128
|
+
` { reply "${connection.user}" }\n`;
|
|
129
|
+
});
|
|
130
|
+
if (this.oidentdFile) {
|
|
131
|
+
fs_1.default.writeFile(this.oidentdFile, file, { flag: "w+" }, function (err) {
|
|
132
|
+
if (err) {
|
|
133
|
+
log_1.default.error("Failed to update oidentd file!", err.message);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
exports.default = Identification;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
7
|
+
const read_1 = __importDefault(require("read"));
|
|
8
|
+
function timestamp() {
|
|
9
|
+
const datetime = new Date().toISOString().split(".")[0].replace("T", " ");
|
|
10
|
+
return chalk_1.default.dim(datetime);
|
|
11
|
+
}
|
|
12
|
+
const log = {
|
|
13
|
+
/* eslint-disable no-console */
|
|
14
|
+
error(...args) {
|
|
15
|
+
console.error(timestamp(), chalk_1.default.red("[ERROR]"), ...args);
|
|
16
|
+
},
|
|
17
|
+
warn(...args) {
|
|
18
|
+
console.error(timestamp(), chalk_1.default.yellow("[WARN]"), ...args);
|
|
19
|
+
},
|
|
20
|
+
info(...args) {
|
|
21
|
+
console.log(timestamp(), chalk_1.default.blue("[INFO]"), ...args);
|
|
22
|
+
},
|
|
23
|
+
debug(...args) {
|
|
24
|
+
console.log(timestamp(), chalk_1.default.green("[DEBUG]"), ...args);
|
|
25
|
+
},
|
|
26
|
+
raw(...args) {
|
|
27
|
+
console.log(...args);
|
|
28
|
+
},
|
|
29
|
+
/* eslint-enable no-console */
|
|
30
|
+
prompt(options, callback) {
|
|
31
|
+
options.prompt = [timestamp(), chalk_1.default.cyan("[PROMPT]"), options.text].join(" ");
|
|
32
|
+
(0, read_1.default)(options, callback);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
exports.default = log;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
7
|
+
const log_1 = __importDefault(require("../log"));
|
|
8
|
+
const config_1 = __importDefault(require("../config"));
|
|
9
|
+
const user_1 = __importDefault(require("./user"));
|
|
10
|
+
const storage_1 = __importDefault(require("../plugins/storage"));
|
|
11
|
+
const prefix_1 = __importDefault(require("./prefix"));
|
|
12
|
+
const msg_1 = require("../../shared/types/msg");
|
|
13
|
+
const chan_1 = require("../../shared/types/chan");
|
|
14
|
+
class Chan {
|
|
15
|
+
// TODO: don't force existence, figure out how to make TS infer it.
|
|
16
|
+
id;
|
|
17
|
+
messages;
|
|
18
|
+
name;
|
|
19
|
+
key;
|
|
20
|
+
topic;
|
|
21
|
+
firstUnread;
|
|
22
|
+
unread;
|
|
23
|
+
highlight;
|
|
24
|
+
users;
|
|
25
|
+
muted;
|
|
26
|
+
type;
|
|
27
|
+
state;
|
|
28
|
+
// mIRC FiSH Blowfish key, stored server-side only
|
|
29
|
+
blowfishKey;
|
|
30
|
+
userAway;
|
|
31
|
+
special;
|
|
32
|
+
data;
|
|
33
|
+
closed;
|
|
34
|
+
num_users;
|
|
35
|
+
constructor(attr) {
|
|
36
|
+
lodash_1.default.defaults(this, attr, {
|
|
37
|
+
id: 0,
|
|
38
|
+
messages: [],
|
|
39
|
+
name: "",
|
|
40
|
+
key: "",
|
|
41
|
+
topic: "",
|
|
42
|
+
type: chan_1.ChanType.CHANNEL,
|
|
43
|
+
state: chan_1.ChanState.PARTED,
|
|
44
|
+
firstUnread: 0,
|
|
45
|
+
unread: 0,
|
|
46
|
+
highlight: 0,
|
|
47
|
+
users: new Map(),
|
|
48
|
+
muted: false,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
destroy() {
|
|
52
|
+
this.dereferencePreviews(this.messages);
|
|
53
|
+
}
|
|
54
|
+
pushMessage(client, msg, increasesUnread = false) {
|
|
55
|
+
const chanId = this.id;
|
|
56
|
+
msg.id = client.idMsg++;
|
|
57
|
+
// If this channel is open in any of the clients, do not increase unread counter
|
|
58
|
+
const isOpen = lodash_1.default.find(client.attachedClients, { openChannel: chanId }) !== undefined;
|
|
59
|
+
if (msg.self) {
|
|
60
|
+
// reset counters/markers when receiving self-/echo-message
|
|
61
|
+
this.unread = 0;
|
|
62
|
+
this.firstUnread = msg.id;
|
|
63
|
+
this.highlight = 0;
|
|
64
|
+
}
|
|
65
|
+
else if (!isOpen) {
|
|
66
|
+
if (!this.firstUnread) {
|
|
67
|
+
this.firstUnread = msg.id;
|
|
68
|
+
}
|
|
69
|
+
if (increasesUnread || msg.highlight) {
|
|
70
|
+
this.unread++;
|
|
71
|
+
}
|
|
72
|
+
if (msg.highlight) {
|
|
73
|
+
this.highlight++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
client.emit("msg", { chan: chanId, msg, unread: this.unread, highlight: this.highlight });
|
|
77
|
+
// Never store messages in public mode as the session
|
|
78
|
+
// is completely destroyed when the page gets closed
|
|
79
|
+
if (config_1.default.values.public) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// showInActive is only processed on "msg", don't need it on page reload
|
|
83
|
+
if (msg.showInActive) {
|
|
84
|
+
delete msg.showInActive;
|
|
85
|
+
}
|
|
86
|
+
this.writeUserLog(client, msg);
|
|
87
|
+
if (config_1.default.values.maxHistory >= 0 && this.messages.length > config_1.default.values.maxHistory) {
|
|
88
|
+
const deleted = this.messages.splice(0, this.messages.length - config_1.default.values.maxHistory);
|
|
89
|
+
// If maxHistory is 0, image would be dereferenced before client had a chance to retrieve it,
|
|
90
|
+
// so for now, just don't implement dereferencing for this edge case.
|
|
91
|
+
if (config_1.default.values.maxHistory > 0) {
|
|
92
|
+
this.dereferencePreviews(deleted);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
dereferencePreviews(messages) {
|
|
97
|
+
if (!config_1.default.values.prefetch || !config_1.default.values.prefetchStorage) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
messages.forEach((message) => {
|
|
101
|
+
if (message.previews) {
|
|
102
|
+
message.previews.forEach((preview) => {
|
|
103
|
+
if (preview.thumb) {
|
|
104
|
+
storage_1.default.dereference(preview.thumb);
|
|
105
|
+
preview.thumb = "";
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
getSortedUsers(irc) {
|
|
112
|
+
const users = Array.from(this.users.values());
|
|
113
|
+
if (!irc || !irc.network || !irc.network.options || !irc.network.options.PREFIX) {
|
|
114
|
+
return users;
|
|
115
|
+
}
|
|
116
|
+
const userModeSortPriority = {};
|
|
117
|
+
irc.network.options.PREFIX.forEach((prefix, index) => {
|
|
118
|
+
userModeSortPriority[prefix.symbol] = index;
|
|
119
|
+
});
|
|
120
|
+
userModeSortPriority[""] = 99; // No mode is lowest
|
|
121
|
+
return users.sort(function (a, b) {
|
|
122
|
+
if (a.mode === b.mode) {
|
|
123
|
+
return a.nick.toLowerCase() < b.nick.toLowerCase() ? -1 : 1;
|
|
124
|
+
}
|
|
125
|
+
return userModeSortPriority[a.mode] - userModeSortPriority[b.mode];
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
findMessage(msgId) {
|
|
129
|
+
return this.messages.find((message) => message.id === msgId);
|
|
130
|
+
}
|
|
131
|
+
findUser(nick) {
|
|
132
|
+
return this.users.get(nick.toLowerCase());
|
|
133
|
+
}
|
|
134
|
+
getUser(nick) {
|
|
135
|
+
return this.findUser(nick) || new user_1.default({ nick }, new prefix_1.default([]));
|
|
136
|
+
}
|
|
137
|
+
setUser(user) {
|
|
138
|
+
this.users.set(user.nick.toLowerCase(), user);
|
|
139
|
+
}
|
|
140
|
+
removeUser(user) {
|
|
141
|
+
this.users.delete(user.nick.toLowerCase());
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get a clean clone of this channel that will be sent to the client.
|
|
145
|
+
* This function performs manual cloning of channel object for
|
|
146
|
+
* better control of performance and memory usage.
|
|
147
|
+
*
|
|
148
|
+
* @param {(int|bool)} lastActiveChannel - Last known active user channel id (needed to control how many messages are sent)
|
|
149
|
+
* If true, channel is assumed active.
|
|
150
|
+
* @param {int} lastMessage - Last message id seen by active client to avoid sending duplicates.
|
|
151
|
+
*/
|
|
152
|
+
getFilteredClone(lastActiveChannel, lastMessage) {
|
|
153
|
+
let msgs;
|
|
154
|
+
// If client is reconnecting, only send new messages that client has not seen yet
|
|
155
|
+
if (lastMessage && lastMessage > -1) {
|
|
156
|
+
// When reconnecting, always send up to 100 messages to prevent message gaps on the client
|
|
157
|
+
// See https://github.com/thelounge/thelounge/issues/1883
|
|
158
|
+
msgs = this.messages.filter((m) => m.id > lastMessage).slice(-100);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// If channel is active, send up to 100 last messages, for all others send just 1
|
|
162
|
+
// Client will automatically load more messages whenever needed based on last seen messages
|
|
163
|
+
const messagesToSend = lastActiveChannel === true || this.id === lastActiveChannel ? 100 : 1;
|
|
164
|
+
msgs = this.messages.slice(-messagesToSend);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
id: this.id,
|
|
168
|
+
messages: msgs,
|
|
169
|
+
totalMessages: this.messages.length,
|
|
170
|
+
name: this.name,
|
|
171
|
+
key: this.key,
|
|
172
|
+
topic: this.topic,
|
|
173
|
+
firstUnread: this.firstUnread,
|
|
174
|
+
unread: this.unread,
|
|
175
|
+
highlight: this.highlight,
|
|
176
|
+
muted: this.muted,
|
|
177
|
+
type: this.type,
|
|
178
|
+
state: this.state,
|
|
179
|
+
special: this.special,
|
|
180
|
+
data: this.data,
|
|
181
|
+
closed: this.closed,
|
|
182
|
+
num_users: this.num_users,
|
|
183
|
+
};
|
|
184
|
+
// TODO: funny array mutation below might need to be reproduced
|
|
185
|
+
// static optionalProperties = ["userAway", "special", "data", "closed", "num_users"];
|
|
186
|
+
// return Object.keys(this).reduce((newChannel, prop) => {
|
|
187
|
+
// if (Chan.optionalProperties.includes(prop)) {
|
|
188
|
+
// if (this[prop] !== undefined || (Array.isArray(this[prop]) && this[prop].length)) {
|
|
189
|
+
// newChannel[prop] = this[prop];
|
|
190
|
+
// }
|
|
191
|
+
// }
|
|
192
|
+
}
|
|
193
|
+
writeUserLog(client, msg) {
|
|
194
|
+
this.messages.push(msg);
|
|
195
|
+
// Are there any logs enabled
|
|
196
|
+
if (client.messageStorage.length === 0) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const targetChannel = this;
|
|
200
|
+
// Is this particular message or channel loggable
|
|
201
|
+
if (!msg.isLoggable() || !this.isLoggable()) {
|
|
202
|
+
// Because notices are nasty and can be shown in active channel on the client
|
|
203
|
+
// if there is no open query, we want to always log notices in the sender's name
|
|
204
|
+
if (msg.type === msg_1.MessageType.NOTICE && msg.showInActive) {
|
|
205
|
+
targetChannel.name = msg.from.nick || ""; // TODO: check if || works
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Find the parent network where this channel is in
|
|
212
|
+
const target = client.find(this.id);
|
|
213
|
+
if (!target) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
for (const messageStorage of client.messageStorage) {
|
|
217
|
+
messageStorage.index(target.network, targetChannel, msg).catch((e) => log_1.default.error(e));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
loadMessages(client, network) {
|
|
221
|
+
if (!this.isLoggable()) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (!network.irc) {
|
|
225
|
+
// Network created, but misconfigured
|
|
226
|
+
log_1.default.warn(`Failed to load messages for ${client.name}, network ${network.name} is not initialized.`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (!client.messageProvider) {
|
|
230
|
+
if (network.irc.network.cap.isEnabled("znc.in/playback")) {
|
|
231
|
+
// if we do have a message provider we might be able to only fetch partial history,
|
|
232
|
+
// so delay the cap in this case.
|
|
233
|
+
requestZncPlayback(this, network, 0);
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
client.messageProvider
|
|
238
|
+
.getMessages(network, this, () => client.idMsg++)
|
|
239
|
+
.then((messages) => {
|
|
240
|
+
if (messages.length === 0) {
|
|
241
|
+
if (network.irc.network.cap.isEnabled("znc.in/playback")) {
|
|
242
|
+
requestZncPlayback(this, network, 0);
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
this.messages.unshift(...messages);
|
|
247
|
+
if (!this.firstUnread) {
|
|
248
|
+
this.firstUnread = messages[messages.length - 1].id;
|
|
249
|
+
}
|
|
250
|
+
client.emit("more", {
|
|
251
|
+
chan: this.id,
|
|
252
|
+
messages: messages.slice(-100),
|
|
253
|
+
totalMessages: messages.length,
|
|
254
|
+
});
|
|
255
|
+
if (network.irc.network.cap.isEnabled("znc.in/playback")) {
|
|
256
|
+
const from = Math.floor(messages[messages.length - 1].time.getTime() / 1000);
|
|
257
|
+
requestZncPlayback(this, network, from);
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
.catch((err) => log_1.default.error(`Failed to load messages for ${client.name}: ${err.toString()}`));
|
|
261
|
+
}
|
|
262
|
+
isLoggable() {
|
|
263
|
+
return this.type === chan_1.ChanType.CHANNEL || this.type === chan_1.ChanType.QUERY;
|
|
264
|
+
}
|
|
265
|
+
setMuteStatus(muted) {
|
|
266
|
+
this.muted = !!muted;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function requestZncPlayback(channel, network, from) {
|
|
270
|
+
if (!network.irc) {
|
|
271
|
+
throw new Error(`requestZncPlayback: no irc field on network "${network.name}", this is a bug`);
|
|
272
|
+
}
|
|
273
|
+
network.irc.raw("ZNC", "*playback", "PLAY", channel.name, from.toString());
|
|
274
|
+
}
|
|
275
|
+
exports.default = Chan;
|