forked from Ylianst/MeshCentral
-
Notifications
You must be signed in to change notification settings - Fork 0
/
redirserver.js
166 lines (147 loc) · 7.85 KB
/
redirserver.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* @description Meshcentral web server
* @author Ylian Saint-Hilaire
* @copyright Intel Corporation 2018-2022
* @license Apache-2.0
* @version v0.0.2
*/
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
"use strict";
// ExpressJS login sample
// https://github.com/expressjs/express/blob/master/examples/auth/index.js
// Construct a HTTP redirection web server object
module.exports.CreateRedirServer = function (parent, db, args, func) {
var obj = {};
obj.parent = parent;
obj.db = db;
obj.args = args;
obj.certificates = null;
obj.express = require('express');
obj.net = require('net');
obj.app = obj.express();
obj.tcpServer = null;
obj.port = null;
const leChallengePrefix = '/.well-known/acme-challenge/';
// Perform an HTTP to HTTPS redirection
function performRedirection(req, res) {
var host = req.headers.host;
if (typeof host == 'string') { host = host.split(':')[0]; }
if ((host == null) && (obj.certificates != null)) { host = obj.certificates.CommonName; if (obj.certificates.CommonName.indexOf('.') == -1) { host = req.headers.host; } }
var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
res.redirect('https://' + host + ':' + httpsPort + req.url);
}
// Setup CrowdSec bouncer middleware if needed
if (parent.crowdsecMiddleware != null) { obj.app.use(parent.crowdsecMiddleware); }
/*
// Return the current domain of the request
function getDomain(req) {
var x = req.url.split("/");
if (x.length < 2) { return parent.config.domains[""]; }
if (parent.config.domains[x[1].toLowerCase()]) { return parent.config.domains[x[1].toLowerCase()]; }
return parent.config.domains[""];
}
*/
// Renter the terms of service.
obj.app.get('/MeshServerRootCert.cer', function (req, res) {
// The redirection server starts before certificates are loaded, make sure to handle the case where no certificate is loaded now.
if (obj.certificates != null) {
res.set({ 'Cache-Control': 'no-store', 'Content-Type': 'application/octet-stream', 'Content-Disposition': 'attachment; filename*="' + encodeURIComponent(obj.certificates.RootName) + '.cer"' });
var rootcert = obj.certificates.root.cert;
var i = rootcert.indexOf('-----BEGIN CERTIFICATE-----\r\n');
if (i >= 0) { rootcert = rootcert.substring(i + 29); }
i = rootcert.indexOf('-----END CERTIFICATE-----');
if (i >= 0) { rootcert = rootcert.substring(i, 0); }
res.send(Buffer.from(rootcert, 'base64'));
} else {
res.sendStatus(404);
}
});
// Add HTTP security headers to all responses
obj.app.use(function (req, res, next) {
parent.debug('webrequest', req.url + ' (RedirServer)');
res.removeHeader('X-Powered-By');
if ((parent.letsencrypt != null) && (req.url.startsWith(leChallengePrefix))) {
// Let's Encrypt Support
parent.letsencrypt.challenge(req.url.slice(leChallengePrefix.length), getCleanHostname(req), function (response) { if (response == null) { res.sendStatus(404); } else { res.send(response); } });
} else {
// Everything else
var selfurl = (' wss://' + req.headers.host);
res.set({
'strict-transport-security': 'max-age=60000; includeSubDomains',
'Referrer-Policy': 'no-referrer',
'x-frame-options': 'SAMEORIGIN',
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'Content-Security-Policy': "default-src 'none'; style-src 'self' 'unsafe-inline';"
});
return next();
}
});
// Once the main web server is started, call this to hookup additional handlers
obj.hookMainWebServer = function (certs) {
obj.certificates = certs;
for (var i in parent.config.domains) {
if (parent.config.domains[i].dns != null) { continue; }
var url = parent.config.domains[i].url;
obj.app.post(url + 'amtevents.ashx', obj.parent.webserver.handleAmtEventRequest);
obj.app.get(url + 'meshsettings', obj.parent.webserver.handleMeshSettingsRequest);
obj.app.get(url + 'meshagents', obj.parent.webserver.handleMeshAgentRequest);
// Server redirects
if (parent.config.domains[i].redirects) {
for (var j in parent.config.domains[i].redirects) {
if (j[0] != '_') { obj.app.get(url + j, obj.parent.webserver.handleDomainRedirect); }
}
}
}
}
// Setup all HTTP redirection handlers
//obj.app.set("etag", false);
for (var i in parent.config.domains) {
if (parent.config.domains[i].dns != null) { continue; }
var url = parent.config.domains[i].url;
obj.app.get(url, performRedirection); // Root redirection
// Setup any .well-known folders
var p = obj.parent.path.join(obj.parent.datapath, '.well-known' + ((parent.config.domains[i].id == '') ? '' : ('-' + parent.config.domains[i].id)));
if (obj.parent.fs.existsSync(p)) { obj.app.use(url + '.well-known', obj.express.static(p)); }
// Setup all of the redirections to HTTPS
const redirections = ['player.htm', 'terms', 'logout', 'MeshServerRootCert.cer', 'mescript.ashx', 'checkmail', 'agentinvite', 'messenger', 'meshosxagent', 'devicepowerevents.ashx', 'downloadfile.ashx', 'userfiles/*', 'webrelay.ashx', 'health.ashx', 'logo.png', 'welcome.jpg', 'invite'];
for (i in redirections) { obj.app.get(url + redirections[i], performRedirection); }
}
// Find a free port starting with the specified one and going up.
function CheckListenPort(port, addr, func) {
var s = obj.net.createServer(function (socket) { });
obj.tcpServer = s.listen(port, addr, function () { s.close(function () { if (func) { func(port, addr); } }); }).on("error", function (err) {
if (args.exactports) { console.error("ERROR: MeshCentral HTTP server port " + port + " not available."); process.exit(); }
else { if (port < 65535) { CheckListenPort(port + 1, addr, func); } else { if (func) { func(0); } } }
});
}
// Start the ExpressJS web server, if the port is busy try the next one.
function StartRedirServer(port, addr) {
if (port == 0 || port == 65535) { return; }
obj.tcpServer = obj.app.listen(port, addr, function () {
obj.port = port;
console.log("MeshCentral HTTP redirection server running on port " + port + ".");
obj.parent.authLog('http', 'Server listening on ' + ((addr != null)?addr:'0.0.0.0') + ' port ' + port + '.');
obj.parent.updateServerState('redirect-port', port);
func(obj.port);
}).on('error', function (err) {
if ((err.code == 'EACCES') && (port < 65535)) { StartRedirServer(port + 1, addr); } else { console.log(err); func(obj.port); }
});
}
// Get the remote hostname correctly
const servernameRe = /^[a-z0-9\.\-]+$/i;
function getHostname(req) { return req.hostname || req.headers['x-forwarded-host'] || (req.headers.host || ''); };
function getCleanHostname(req) {
var servername = getHostname(req).toLowerCase().replace(/:.*/, '');
try { req.hostname = servername; } catch (e) { } // read-only express property
if (req.headers['x-forwarded-host']) { req.headers['x-forwarded-host'] = servername; }
try { req.headers.host = servername; } catch (e) { }
return (servernameRe.test(servername) && -1 === servername.indexOf('..') && servername) || '';
};
CheckListenPort(args.redirport, args.redirportbind, StartRedirServer);
return obj;
};