| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var sni = require('sni'); | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  | var pipeWs = require('./pipe-ws.js'); | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  | module.exports.createTcpConnectionHandler = function (state) { | 
					
						
							|  |  |  |   var Devices = state.Devices; | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-31 06:12:37 +00:00
										 |  |  |   return function onTcpConnection(conn, serviceport) { | 
					
						
							| 
									
										
										
										
											2018-05-31 06:18:02 +00:00
										 |  |  |     serviceport = serviceport || conn.localPort; | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |     // this works when I put it here, but I don't know if it's tls yet here
 | 
					
						
							|  |  |  |     // httpsServer.emit('connection', socket);
 | 
					
						
							|  |  |  |     //tls3000.emit('connection', socket);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     //var tlsSocket = new tls.TLSSocket(socket, { secureContext: tls.createSecureContext(tlsOpts) });
 | 
					
						
							|  |  |  |     //tlsSocket.on('data', function (chunk) {
 | 
					
						
							|  |  |  |     //  console.log('dummy', chunk.byteLength);
 | 
					
						
							|  |  |  |     //});
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     //return;
 | 
					
						
							|  |  |  |     conn.once('data', function (firstChunk) { | 
					
						
							| 
									
										
										
										
											2018-06-19 23:43:28 +00:00
										 |  |  |       var service = 'tcp'; | 
					
						
							|  |  |  |       var servername; | 
					
						
							|  |  |  |       var str; | 
					
						
							|  |  |  |       var m; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-26 21:21:03 +00:00
										 |  |  |       conn.pause(); | 
					
						
							|  |  |  |       conn.unshift(firstChunk); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |       // BUG XXX: this assumes that the packet won't be chunked smaller
 | 
					
						
							|  |  |  |       // than the 'hello' or the point of the 'Host' header.
 | 
					
						
							|  |  |  |       // This is fairly reasonable, but there are edge cases where
 | 
					
						
							|  |  |  |       // it does not hold (such as manual debugging with telnet)
 | 
					
						
							|  |  |  |       // and so it should be fixed at some point in the future
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // defer after return (instead of being in many places)
 | 
					
						
							| 
									
										
										
										
											2018-05-26 21:21:03 +00:00
										 |  |  |       function deferData(fn) { | 
					
						
							|  |  |  |         if (fn) { | 
					
						
							| 
									
										
										
										
											2018-06-19 23:43:28 +00:00
										 |  |  |           state[fn](servername, conn); | 
					
						
							| 
									
										
										
										
											2018-05-26 21:21:03 +00:00
										 |  |  |         } | 
					
						
							|  |  |  |         process.nextTick(function () { | 
					
						
							|  |  |  |           conn.resume(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |       function tryTls() { | 
					
						
							| 
									
										
										
										
											2018-05-24 19:19:50 +00:00
										 |  |  |         var vhost; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-30 23:10:18 +00:00
										 |  |  |         if (!servername) { | 
					
						
							|  |  |  |           if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); } | 
					
						
							|  |  |  |           deferData('httpsInvalid'); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |         if (!state.servernames.length) { | 
					
						
							|  |  |  |           console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)"); | 
					
						
							| 
									
										
										
										
											2018-05-26 21:21:03 +00:00
										 |  |  |           deferData('httpsSetupServer'); | 
					
						
							| 
									
										
										
										
											2018-05-23 11:12:39 +00:00
										 |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |         if (-1 !== state.servernames.indexOf(servername)) { | 
					
						
							|  |  |  |           if (state.debug) { console.log("[Admin]", servername); } | 
					
						
							| 
									
										
										
										
											2018-05-26 21:21:03 +00:00
										 |  |  |           deferData('httpsTunnel'); | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |         if (state.config.nowww && /^www\./i.test(servername)) { | 
					
						
							| 
									
										
										
										
											2018-05-24 19:19:50 +00:00
										 |  |  |           console.log("TODO: use www bare redirect"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-14 09:59:19 +00:00
										 |  |  |         function run() { | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |           var nextDevice = Devices.next(state.deviceLists, servername); | 
					
						
							| 
									
										
										
										
											2018-05-24 19:19:50 +00:00
										 |  |  |           if (!nextDevice) { | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |             if (state.debug) { console.log("No devices match the given servername"); } | 
					
						
							| 
									
										
										
										
											2018-05-26 21:21:03 +00:00
										 |  |  |             deferData('httpsInvalid'); | 
					
						
							| 
									
										
										
										
											2018-05-24 19:19:50 +00:00
										 |  |  |             return; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-19 23:43:28 +00:00
										 |  |  |           if (state.debug) { console.log("pipeWs(servername, service, deviceLists['" + servername + "'], socket)"); } | 
					
						
							| 
									
										
										
										
											2018-05-26 21:21:03 +00:00
										 |  |  |           deferData(); | 
					
						
							| 
									
										
										
										
											2018-06-19 23:43:28 +00:00
										 |  |  |           pipeWs(servername, service, nextDevice, conn, serviceport); | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |         // TODO don't run an fs check if we already know this is working elsewhere
 | 
					
						
							|  |  |  |         //if (!state.validHosts) { state.validHosts = {}; }
 | 
					
						
							|  |  |  |         if (state.config.vhost) { | 
					
						
							| 
									
										
										
										
											2018-06-14 09:59:19 +00:00
										 |  |  |           vhost = state.config.vhost.replace(/:hostname/, (servername||'reallydoesntexist')); | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |           if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); } | 
					
						
							| 
									
										
										
										
											2018-06-19 23:43:28 +00:00
										 |  |  |           //state.httpsVhost(servername, conn);
 | 
					
						
							| 
									
										
										
										
											2018-05-24 19:19:50 +00:00
										 |  |  |           //return;
 | 
					
						
							|  |  |  |           require('fs').readdir(vhost, function (err, nodes) { | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |             if (state.debug && err) { console.log("VHOST error", err); } | 
					
						
							| 
									
										
										
										
											2018-06-19 23:43:28 +00:00
										 |  |  |             if (err || !nodes) { run(); return; } | 
					
						
							| 
									
										
										
										
											2018-06-14 09:59:19 +00:00
										 |  |  |             //if (nodes) { deferData('httpsVhost'); return; }
 | 
					
						
							|  |  |  |             deferData('httpsVhost'); | 
					
						
							| 
									
										
										
										
											2018-05-24 19:19:50 +00:00
										 |  |  |           }); | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-24 19:19:50 +00:00
										 |  |  |         run(); | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
 | 
					
						
							|  |  |  |       if (22 === firstChunk[0]) { | 
					
						
							|  |  |  |         // TLS
 | 
					
						
							|  |  |  |         service = 'https'; | 
					
						
							| 
									
										
										
										
											2018-06-14 09:59:19 +00:00
										 |  |  |         servername = (sni(firstChunk)||'').toLowerCase().trim(); | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |         if (state.debug) { console.log("[tcp] tls hello from '" + servername + "'"); } | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |         tryTls(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (firstChunk[0] > 32 && firstChunk[0] < 127) { | 
					
						
							|  |  |  |         str = firstChunk.toString(); | 
					
						
							|  |  |  |         m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im); | 
					
						
							|  |  |  |         servername = (m && m[1].toLowerCase() || '').split(':')[0]; | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |         if (state.debug) { console.log("[tcp] http hostname '" + servername + "'"); } | 
					
						
							| 
									
										
										
										
											2018-05-23 11:12:39 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |         if (/HTTP\//i.test(str)) { | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |           if (!state.servernames.length) { | 
					
						
							|  |  |  |             console.info("[tcp] No admin servername. Entering setup mode."); | 
					
						
							| 
									
										
										
										
											2018-05-26 21:21:03 +00:00
										 |  |  |             deferData(); | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |             state.httpSetupServer.emit('connection', conn); | 
					
						
							| 
									
										
										
										
											2018-05-23 11:12:39 +00:00
										 |  |  |             return; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |           service = 'http'; | 
					
						
							| 
									
										
										
										
											2018-05-23 11:12:39 +00:00
										 |  |  |           // TODO make https redirect configurable
 | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |           // /^\/\.well-known\/acme-challenge\//.test(str)
 | 
					
						
							|  |  |  |           if (/well-known/.test(str)) { | 
					
						
							|  |  |  |             // HTTP
 | 
					
						
							| 
									
										
										
										
											2018-06-01 06:41:32 +00:00
										 |  |  |             if (Devices.exist(state.deviceLists, servername)) { | 
					
						
							| 
									
										
										
										
											2018-05-26 21:21:03 +00:00
										 |  |  |               deferData(); | 
					
						
							| 
									
										
										
										
											2018-06-19 23:43:28 +00:00
										 |  |  |               pipeWs(servername, service, Devices.next(state.deviceLists, servername), conn, serviceport); | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |               return; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-05-26 21:21:03 +00:00
										 |  |  |             deferData('handleHttp'); | 
					
						
							|  |  |  |             return; | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2018-05-26 21:21:03 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |           // redirect to https
 | 
					
						
							|  |  |  |           deferData('handleInsecureHttp'); | 
					
						
							| 
									
										
										
										
											2018-04-25 17:41:20 +00:00
										 |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       console.error("Got unexpected connection", str); | 
					
						
							|  |  |  |       conn.write(JSON.stringify({ error: { | 
					
						
							|  |  |  |         message: "not sure what you were trying to do there..." | 
					
						
							|  |  |  |       , code: 'E_INVALID_PROTOCOL' } | 
					
						
							|  |  |  |       })); | 
					
						
							|  |  |  |       conn.end(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     conn.on('error', function (err) { | 
					
						
							|  |  |  |       console.error('[error] tcp socket raw TODO forward and close'); | 
					
						
							|  |  |  |       console.error(err); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | }; |