-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.js
182 lines (149 loc) · 4.71 KB
/
server.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#!/usr/bin/env node
var https = require('https');
var http = require('http');
var crypto = require('crypto');
var msgpack = require('msgpack');
var domains = Object.create(null);
function getDomain(domain) {
// return any credentials for this domain itself
if (domains[domain]) return domains[domain];
var components = domain.split('.');
// We go to length - 1 because nobody's going to own the TLD
// There may be other public suffixes we'll disallow, but this one
// we can 100% not do
for (var i = 1; i < components.length - 1; i++) {
var wildDomain = '*.' + components.slice(i).join('.');
// return any wildcard credentials for a higher-level domain
if (domains[wildDomain]) return domains[wildDomain];
}
// If we tried the domain and any possible wildcards and got no success,
// we didn't find a record
return null;
}
function getCredentials(domain) {
var record = getDomain(domain);
return record && record.credentials;
}
function getTarget(domain) {
var record = getDomain(domain);
return record && record.target;
}
function getRedirect(domain) {
var record = getDomain(domain);
return record && record.redirect;
}
function processSelfUpdate(update) {
// If the update content is not null
if (update) {
// (Re)start the HTTPS server when we get a key and cert
if (update.key && update.cert) {
serverOpts.key = update.key;
serverOpts.cert = update.cert;
startHttpsServer();
}
// Self updates only test for HTTPS server credentials
// and ignore target / redirect records
// If the update content is null
} else {
// Stop the HTTPS server and clear the credentials
stopHttpsServer();
delete serverOpts.key;
delete serverOpts.cert;
}
}
function processDomainUpdate(domain, update) {
// If the update content is not null
if (update) {
// Create any new domain
if (!domains[domain]) domains[domain] = {};
// Update any target
if (update.target) {
domains[domain].target = message[domain].target;
delete domains[domain].redirect;
}
// If not target but redirect, set redirect instead
else if (update.redirect) {
domains[domain].redirect = message[domain].redirect;
delete domains[domain].target;
}
// Create credentials for any key + cert pairs
if (update.key && update.cert)
domains[domain].credentials = crypto.createCredentials({
key: update.key, cert: update.cert
});
// If the update content is null
} else {
// Delete the domain record
delete domains[domain];
}
}
function updateDomains(message) {
// there should really only be one domain per message but
var msgDomains = Object.keys(message);
// For the domain(s) in the message
for (var i = 0; i < msgDomains.length; i++) {
var domain = msgDomains[i];
// If this is a self-content message
if (domain == '@') {
// process it as such
processSelfUpdate(message[domain]);
// Otherwise
} else {
// Process content for the domain
processDomainUpdate(message[domain]);
}
}
}
process.stdin.resume();
var msgstream = new msgpack.Stream(process.stdin);
msgstream.addListener('msg', updateDomains);
var serverOpts = {
// handshakeTimeout by default is 120 seconds which sounds WAY too high
//handshakeTimeout: 20000,
// mitigate BEAST attacks by preferring non-vulnerable ciphers
honorCipherOrder: true,
SNICallback: getCredentials
};
var proxy = require('http-proxy').createProxyServer({
xfwd: true, secure: false
});
function respondError(err, req, res) {
res.statusCode = 500;
// TODO: handle error object
res.end();
}
function respondNotFound(req, res) {
res.statusCode = 404;
res.end();
}
function respondRedirect(req, res, url) {
res.statusCode = 301;
res.setHeader('Location', url);
res.end();
}
function forwardRequest(req, res) {
var record = req.headers.host && getDomain(req.headers.host);
if (record && record.target)
return proxy.web(req, res, {target:record.target});
else if (record && record.redirect)
return respondRedirect(req, res, record.redirect);
else return respondNotFound(req, res);
}
function redirectToHttps(req, res) {
var host = req.headers.host;
// If they're asking for a name we're proxying
if (host && getDomain(host)) {
return respondRedirect('https://' + host + req.url);
} else {
return respondNotFound(req, res);
}
}
http.createServer(redirectToHttps).listen(80);
var httpsServer = null;
function stopHttpsServer(cb) {
httpsServer.close(function(){httpsServer = null; cb && cb()});
}
function startHttpsServer(cb) {
if (httpsServer) stopHttpsServer(startHttpsServer.bind(null,cb));
httpsServer = https.createServer(serverOpts, forwardRequest).listen(443,cb);
}