1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | var _ = require("lodash")
|
8 | , intercept = require("./intercept")
|
9 | , Session = require("./session")
|
10 | , Firewall = require("./firewall")
|
11 | , VantageUtil = require("./util")
|
12 | , stripAnsi = require("strip-ansi")
|
13 | ; require("native-promise-only")
|
14 | ;
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | function VantageServer(parent) {
|
25 | if (!(this instanceof VantageServer)) { return new VantageServer(); }
|
26 | this._hooked = false;
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | this.sessions = [];
|
34 |
|
35 | this.parent = parent;
|
36 | return this;
|
37 | }
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | var vantageServer = VantageServer.prototype;
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | exports = module.exports = VantageServer;
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 | vantageServer.init = function(app, options, cb) {
|
61 | var self = this;
|
62 |
|
63 |
|
64 |
|
65 | cb = (_.isFunction(options)) ? options
|
66 | : (cb || function(){});
|
67 |
|
68 |
|
69 |
|
70 |
|
71 | options = (options && _.isFunction(options)) ? {}
|
72 | : (options && !_.isObject(options) && !isNaN(options)) ? ({ port: options })
|
73 | : (!options) ? {}
|
74 | : options;
|
75 |
|
76 |
|
77 |
|
78 | options.port =
|
79 | (app && !_.isObject(app) && !isNaN(app)) ? app : options.port;
|
80 |
|
81 |
|
82 |
|
83 | app =
|
84 | (app && !_.isObject(app) && !isNaN(app)) ? function(){} : app;
|
85 |
|
86 | var appIs =
|
87 | (_.isFunction(app)) ? "callback" :
|
88 | (_.isObject(app) && _.isFunction(app.callback)) ? "koa" :
|
89 | (_.isObject(app) && _.isFunction(app.connection)) ? "hapi" :
|
90 | "";
|
91 |
|
92 | options = _.defaults(options, {
|
93 | port: 80,
|
94 | ssl: false,
|
95 | logActivity: false
|
96 | });
|
97 |
|
98 |
|
99 | var appCallback =
|
100 | (appIs === "callback") ? app :
|
101 | (appIs === "koa") ? app.callback() :
|
102 | (appIs === "hapi") ? true :
|
103 | void 0;
|
104 |
|
105 | if (!appCallback) {
|
106 | throw new Error("Unsupported HTTP Server passed into Vantage.");
|
107 | }
|
108 |
|
109 | if (appIs === "hapi") {
|
110 | this.server = app;
|
111 | this.server.connection({ port: options.port });
|
112 | this.io = require("socket.io")(this.server.listener);
|
113 | } else {
|
114 | var type = (options.ssl) ? "https" : "http";
|
115 | if (type === "http") {
|
116 | this.server = require(type).createServer(appCallback);
|
117 | } else {
|
118 | this.server = require(type).createServer(options, appCallback);
|
119 | }
|
120 | this.io = require("socket.io")(this.server);
|
121 | this.server.listen(options.port);
|
122 | }
|
123 |
|
124 | this._port = options.port;
|
125 | this._logActivity = options.logActivity;
|
126 |
|
127 | this.io.set("authorization", function(handshakeData, accept){
|
128 |
|
129 | var address = handshakeData.connection._peername;
|
130 | var valid = self.firewall.valid(address);
|
131 | var query = handshakeData._query;
|
132 | var id = query.id || void 0;
|
133 | var ssnId = query.sessionId || void 0;
|
134 |
|
135 |
|
136 |
|
137 | if (id && (id === self.parent.session.id)) {
|
138 | return accept("You can't connect to yourself.", false);
|
139 | }
|
140 | if (_.pluck(self.sessions, "id").indexOf(ssnId) > -1) {
|
141 | return accept("You have already connected to this instance.", false);
|
142 | }
|
143 |
|
144 | if (!valid) {
|
145 | return accept("IP Not Allowed: " + address, false);
|
146 | } else {
|
147 | return accept(void 0, true);
|
148 | }
|
149 |
|
150 | });
|
151 |
|
152 | this.firewall = new Firewall();
|
153 | this.parent.firewall = this.firewall;
|
154 |
|
155 | this.listen(cb);
|
156 |
|
157 | this.hook(function(txt) {
|
158 | for (var i = 0; i < self.sessions.length; ++i) {
|
159 | if (self.sessions[i].pipe) {
|
160 |
|
161 |
|
162 |
|
163 | if (!(txt && txt.length > 1 && stripAnsi(txt).length === 0)) {
|
164 | self.parent._send("vantage-stdout-downstream", "downstream", { value: txt, sessionId: self.sessions[i].id });
|
165 | }
|
166 | }
|
167 | }
|
168 | return txt;
|
169 | });
|
170 |
|
171 | return this;
|
172 | };
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 | vantageServer.listen = function(cb) {
|
183 | var self = this;
|
184 | cb = cb || function(){};
|
185 |
|
186 | this.io.on("connection", function(socket) {
|
187 |
|
188 | var query = socket.handshake.query;
|
189 | var ssn = new Session({
|
190 | local: false,
|
191 | parent: self.parent,
|
192 | id: query.sessionId,
|
193 | authenticating: true,
|
194 | authenticated: false,
|
195 | host: socket.handshake.headers.host,
|
196 | address: socket.handshake.address
|
197 | });
|
198 |
|
199 | ssn.server = socket;
|
200 | self.sessions.push(ssn);
|
201 |
|
202 |
|
203 |
|
204 | function on(str, opts, cbk) {
|
205 | cbk = (_.isFunction(opts)) ? opts : cbk;
|
206 | cbk = cbk || function() {};
|
207 | opts = opts || {};
|
208 | ssn.server.on(str, function() {
|
209 | if (!ssn.server || (!ssn.authenticating && !ssn.authenticated)) {
|
210 |
|
211 | self.parent._send("vantage-close-downstream", "downstream", { sessionId: ssn.id });
|
212 | return;
|
213 | }
|
214 | cbk.apply(self, arguments);
|
215 | });
|
216 | }
|
217 |
|
218 | on("vantage-keypress-upstream", function(data) {
|
219 | self.parent._proxy("vantage-keypress-upstream", "upstream", data).then(function(){
|
220 | if ((["up", "down", "tab"].indexOf(data.key) > -1)) {
|
221 | var sessn = self.parent.getSessionById(data.sessionId);
|
222 | sessn.getKeypressResult(data.key, data.value, function(err, response) {
|
223 | self.parent._send("vantage-keypress-downstream", "downstream", { value: (err || response), sessionId: sessn.id });
|
224 | });
|
225 | } else {
|
226 | self.parent._histCtr = 0;
|
227 | }
|
228 | });
|
229 | });
|
230 |
|
231 | on("vantage-command-upstream", function(data) {
|
232 | self.parent._proxy("vantage-command-upstream", "upstream", data).then(function() {
|
233 | if (data.command) {
|
234 | var response = {
|
235 | command: data.command,
|
236 | completed: true,
|
237 | error: void 0,
|
238 | data: arguments,
|
239 | sessionId: data.sessionId
|
240 | };
|
241 | var execute = function() {
|
242 | return new Promise(function(resolve, reject){
|
243 | var cmd = {
|
244 | command: data.command,
|
245 | args: data.args,
|
246 | resolve: resolve,
|
247 | reject: reject,
|
248 | session: self.parent.getSessionById(data.sessionId),
|
249 | callback: function() {
|
250 | var args = VantageUtil.fixArgsForApply(arguments);
|
251 | response.data = args;
|
252 | if (args[0] !== undefined) {
|
253 | response.error = args[0] || args[1];
|
254 | }
|
255 | self.parent.emit("server_command_executed", response);
|
256 | self.parent._send("vantage-command-downstream", "downstream", response);
|
257 | }
|
258 | };
|
259 | self.parent._exec(cmd);
|
260 | });
|
261 | };
|
262 | self.parent.emit("server_command_received", {
|
263 | command: data.command
|
264 | });
|
265 |
|
266 | try {
|
267 | execute().then(function() {
|
268 | response.data = arguments;
|
269 | self.parent.emit("server_command_executed", response);
|
270 | self.parent._send("vantage-command-downstream", "downstream", response);
|
271 | }).catch(function(error){
|
272 | response.error = error;
|
273 | response.data = void 0;
|
274 | self.parent.emit("server_command_error", response);
|
275 | self.parent._send("vantage-command-downstream", "downstream", response);
|
276 | });
|
277 | } catch(e) {
|
278 | console.log("Error executing remote command: ", e);
|
279 | }
|
280 | }
|
281 | });
|
282 | });
|
283 |
|
284 | on("vantage-heartbeat-upstream", function(data) {
|
285 | self.parent._proxy("vantage-heartbeat-upstream", "upstream", data).then(function() {
|
286 | self.parent._send("vantage-heartbeat-downstream", "downstream", {
|
287 | delimiter: self.parent._delimiter,
|
288 | sessionId: ssn.id
|
289 | });
|
290 | });
|
291 | });
|
292 |
|
293 | on("vantage-close-upstream", function(data) {
|
294 | self.parent._proxy("vantage-close-upstream", "upstream", data).then(function() {
|
295 | self.parent._send("vantage-close-downstream", "downstream", { sessionId: ssn.id });
|
296 | });
|
297 | });
|
298 |
|
299 | on("vantage-prompt-upstream", function(data) {
|
300 | self.parent._proxy("vantage-prompt-upstream", "upstream", data).then(function() {
|
301 | self.parent.emit("vantage-prompt-upstream", data);
|
302 | });
|
303 | });
|
304 |
|
305 | on("vantage-auth-upstream", function(data) {
|
306 | self.parent._proxy("vantage-auth-upstream", "upstream", data).then(function() {
|
307 | self.parent._authenticate(data, function(err, authenticated) {
|
308 | self.parent._send("vantage-auth-downstream", "downstream", { sessionId: ssn.id, error: err, authenticated: authenticated });
|
309 | });
|
310 | });
|
311 | });
|
312 |
|
313 | ssn.server.on("disconnect", function(data) {
|
314 |
|
315 | self.parent.emit("server_disconnect", data);
|
316 |
|
317 | var nw = [];
|
318 | for (var i = 0; i < self.sessions.length; ++i) {
|
319 | if (self.sessions[i].id === ssn.id) {
|
320 |
|
321 | if (self.sessions[i].client !== undefined) {
|
322 | self.sessions[i].client.close();
|
323 | }
|
324 | delete self.sessions[i];
|
325 | } else {
|
326 | nw.push(self.sessions[i]);
|
327 | }
|
328 | }
|
329 | self.sessions = nw;
|
330 | });
|
331 |
|
332 | if (self.parent._banner) {
|
333 | self.parent._send("vantage-banner-downstream", "downstream", { banner: self.parent._banner, sessionId: ssn.id });
|
334 | }
|
335 |
|
336 | self.parent._send("vantage-heartbeat-downstream", "downstream", { delimiter: self.parent._delimiter, sessionId: ssn.id });
|
337 |
|
338 | self.parent.emit("server_connection", socket);
|
339 | cb(socket);
|
340 | });
|
341 |
|
342 | return this;
|
343 | };
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 | vantageServer.unhook = function() {
|
353 | if (this._hooked && this._unhook !== undefined && this.sessions.length < 1) {
|
354 | this._unhook();
|
355 | this._hooked = false;
|
356 | }
|
357 | return this;
|
358 | };
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 | vantageServer.hook = function(fn) {
|
369 | if (this._hooked && this._unhook !== undefined) {
|
370 | this.unhook();
|
371 | }
|
372 | this._unhook = intercept(fn);
|
373 | this._hooked = true;
|
374 | return this;
|
375 | };
|