| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-31 15:39:24 -06:00
										 |  |  | function httpsTunnel(servername, conn) { | 
					
						
							|  |  |  |   console.error('tunnel server received encrypted connection to', servername); | 
					
						
							|  |  |  |   conn.end(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | function handleHttp(servername, conn) { | 
					
						
							|  |  |  |   console.error('tunnel server received un-encrypted connection to', servername); | 
					
						
							|  |  |  |   conn.end([ | 
					
						
							|  |  |  |     'HTTP/1.1 404 Not Found' | 
					
						
							|  |  |  |   , 'Date: ' + (new Date()).toUTCString() | 
					
						
							|  |  |  |   , 'Connection: close' | 
					
						
							|  |  |  |   , 'Content-Type: text/html' | 
					
						
							|  |  |  |   , 'Content-Length: 9' | 
					
						
							|  |  |  |   , '' | 
					
						
							|  |  |  |   , 'Not Found' | 
					
						
							|  |  |  |   ].join('\r\n')); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | function rejectNonWebsocket(req, res) { | 
					
						
							|  |  |  |   // status code 426 = Upgrade Required
 | 
					
						
							|  |  |  |   res.statusCode = 426; | 
					
						
							|  |  |  |   res.setHeader('Content-Type', 'application/json'); | 
					
						
							|  |  |  |   res.send({error: { message: 'Only websockets accepted for tunnel server' }}); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-31 15:39:24 -06:00
										 |  |  | var defaultConfig = { | 
					
						
							|  |  |  |   servernames: [] | 
					
						
							|  |  |  | , secret: null | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | var tunnelFuncs = { | 
					
						
							|  |  |  |   // These functions should not be called because connections to the admin domains
 | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  |   // should already be decrypted, and connections to non-client domains should never
 | 
					
						
							|  |  |  |   // be given to us in the first place.
 | 
					
						
							| 
									
										
										
										
											2017-10-31 15:39:24 -06:00
										 |  |  |   httpsTunnel:  httpsTunnel | 
					
						
							|  |  |  | , httpsInvalid: httpsTunnel | 
					
						
							|  |  |  |   // These function should not be called because ACME challenges should be handled
 | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  |   // before admin domain connections are given to us, and the only non-encrypted
 | 
					
						
							|  |  |  |   // client connections that should be given to us are ACME challenges.
 | 
					
						
							| 
									
										
										
										
											2017-10-31 15:39:24 -06:00
										 |  |  | , handleHttp:         handleHttp | 
					
						
							|  |  |  | , handleInsecureHttp: handleHttp | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-31 15:39:24 -06:00
										 |  |  | module.exports.create = function (deps, config) { | 
					
						
							|  |  |  |   var equal = require('deep-equal'); | 
					
						
							|  |  |  |   var enableDestroy = require('server-destroy'); | 
					
						
							|  |  |  |   var currentOpts = Object.assign({}, defaultConfig); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   var httpServer, wsServer, stunneld; | 
					
						
							|  |  |  |   function start() { | 
					
						
							|  |  |  |     if (httpServer || wsServer || stunneld) { | 
					
						
							|  |  |  |       throw new Error('trying to start already started tunnel server'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     httpServer = require('http').createServer(rejectNonWebsocket); | 
					
						
							|  |  |  |     enableDestroy(httpServer); | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-31 15:39:24 -06:00
										 |  |  |     wsServer = new (require('ws').Server)({ server: httpServer }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var tunnelOpts = Object.assign({}, tunnelFuncs, currentOpts); | 
					
						
							|  |  |  |     stunneld = require('stunneld').create(tunnelOpts); | 
					
						
							|  |  |  |     wsServer.on('connection', stunneld.ws); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function stop() { | 
					
						
							|  |  |  |     if (!httpServer || !wsServer || !stunneld) { | 
					
						
							|  |  |  |       throw new Error('trying to stop unstarted tunnel server (or it got into semi-initialized state'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     wsServer.close(); | 
					
						
							|  |  |  |     wsServer = null; | 
					
						
							|  |  |  |     httpServer.destroy(); | 
					
						
							|  |  |  |     httpServer = null; | 
					
						
							|  |  |  |     // Nothing to close here, just need to set it to null to allow it to be garbage-collected.
 | 
					
						
							|  |  |  |     stunneld = null; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function updateConf() { | 
					
						
							|  |  |  |     var newOpts = Object.assign({}, defaultConfig, config.tunnelServer); | 
					
						
							|  |  |  |     if (!Array.isArray(newOpts.servernames)) { | 
					
						
							|  |  |  |       newOpts.servernames = []; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     var trimmedOpts = { | 
					
						
							|  |  |  |       servernames: newOpts.servernames.slice().sort() | 
					
						
							|  |  |  |     , secret:      newOpts.secret | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (equal(trimmedOpts, currentOpts)) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     currentOpts = trimmedOpts; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Stop what's currently running, then if we are still supposed to be running then we
 | 
					
						
							|  |  |  |     // can start it again with the updated options. It might be possible to make use of
 | 
					
						
							|  |  |  |     // the existing http and ws servers when the config changes, but I'm not sure what
 | 
					
						
							|  |  |  |     // state the actions needed to close all existing connections would put them in.
 | 
					
						
							|  |  |  |     if (httpServer || wsServer || stunneld) { | 
					
						
							|  |  |  |       stop(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (currentOpts.servernames.length && currentOpts.secret) { | 
					
						
							|  |  |  |       start(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   process.nextTick(updateConf); | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     isAdminDomain: function (domain) { | 
					
						
							| 
									
										
										
										
											2017-10-31 15:39:24 -06:00
										 |  |  |       return currentOpts.servernames.indexOf(domain) !== -1; | 
					
						
							| 
									
										
										
										
											2017-06-23 17:22:45 -06:00
										 |  |  |     } | 
					
						
							|  |  |  |   , handleAdminConn: function (conn) { | 
					
						
							| 
									
										
										
										
											2017-10-31 15:39:24 -06:00
										 |  |  |       if (!httpServer) { | 
					
						
							|  |  |  |         console.error(new Error('handleAdminConn called with no active tunnel server')); | 
					
						
							|  |  |  |         conn.end(); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         return httpServer.emit('connection', conn); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   , isClientDomain: function (domain) { | 
					
						
							|  |  |  |       if (!stunneld) { return false; } | 
					
						
							|  |  |  |       return stunneld.isClientDomain(domain); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   , handleClientConn: function (conn) { | 
					
						
							|  |  |  |       if (!stunneld) { | 
					
						
							|  |  |  |         console.error(new Error('handleClientConn called with no active tunnel server')); | 
					
						
							|  |  |  |         conn.end(); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         return stunneld.tcp(conn); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2017-06-23 17:22:45 -06:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-31 15:39:24 -06:00
										 |  |  |   , updateConf | 
					
						
							| 
									
										
										
										
											2017-06-14 10:58:56 -06:00
										 |  |  |   }; | 
					
						
							|  |  |  | }; |