Merge branch 'v1.0' of git.daplie.com:Daplie/Goldilocks.js into v1.0
This commit is contained in:
		
						commit
						5c7f2321cc
					
				@ -69,26 +69,34 @@ function readConfigAndRun(args) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  var recase = require('recase').create({});
 | 
					  var recase = require('recase').create({});
 | 
				
			||||||
  config = recase.camelCopy(config);
 | 
					  config = recase.camelCopy(config);
 | 
				
			||||||
 | 
					  config.debug = config.debug || args.debug;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!config.dns) {
 | 
					  if (!config.dns) {
 | 
				
			||||||
    config.dns = { modules: { name: 'proxy', port: 3053 } };
 | 
					    config.dns = { modules: [{ name: 'proxy', port: 3053 }] };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  // Use Object.assign to add any properties needed but not defined in the mdns config.
 | 
				
			||||||
 | 
					  // It will first copy the defaults into an empty object, then copy any real config over that.
 | 
				
			||||||
 | 
					  var mdnsDefaults = { port: 5353, broadcast: '224.0.0.251', ttl: 300 };
 | 
				
			||||||
 | 
					  config.mdns = Object.assign({}, mdnsDefaults, config.mdns || {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!config.tcp) {
 | 
					  if (!config.tcp) {
 | 
				
			||||||
    config.tcp = {};
 | 
					    config.tcp = {};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (!config.http) {
 | 
					  if (!config.http) {
 | 
				
			||||||
    config.http = { modules: { name: 'proxy', port: 3000 } };
 | 
					    config.http = { modules: [{ name: 'proxy', domains: ['*'], port: 3000 }] };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (!config.tls) {
 | 
					  if (!config.tls) {
 | 
				
			||||||
    console.log("TODO: tls: { modules: { name: 'acme', email: 'foo@bar.com', domains: [ '*' ] } }");
 | 
					    config.tls = {};
 | 
				
			||||||
    config.tls = {
 | 
					  }
 | 
				
			||||||
      agreeTos: args.agreeTos || args.agree || args['agree-tos']
 | 
					  if (!config.tls.acme && (args.email || args.agreeTos)) {
 | 
				
			||||||
    , servernames: (args.servernames||'').split(',').filter(Boolean).map(function (str) { return str.toLowerCase(); })
 | 
					    config.tls.acme = {};
 | 
				
			||||||
    };
 | 
					  }
 | 
				
			||||||
 | 
					  if (typeof args.agreeTos === 'string') {
 | 
				
			||||||
 | 
					    config.tls.acme.approvedDomains = args.agreeTos.split(',');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (args.email) {
 | 
					  if (args.email) {
 | 
				
			||||||
    config.email = args.email;
 | 
					    config.email = args.email;
 | 
				
			||||||
    config.tls.email = args.email;
 | 
					    config.tls.acme.email = args.email;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // maybe this should not go in config... but be ephemeral in some way?
 | 
					  // maybe this should not go in config... but be ephemeral in some way?
 | 
				
			||||||
@ -190,20 +198,20 @@ function readConfigAndRun(args) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function readEnv(args) {
 | 
					function readEnv(args) {
 | 
				
			||||||
  // TODO
 | 
					  // TODO
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    if (process.env.GOLDILOCKS_HOME) {
 | 
				
			||||||
 | 
					      process.chdir(process.env.GOLDILOCKS_HOME);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } catch (err) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var env = {
 | 
					  var env = {
 | 
				
			||||||
    tunnel: process.env.GOLDILOCKS_TUNNEL_TOKEN || process.env.GOLDILOCKS_TUNNEL && true
 | 
					    tunnel: process.env.GOLDILOCKS_TUNNEL_TOKEN || process.env.GOLDILOCKS_TUNNEL && true
 | 
				
			||||||
  , email: process.env.GOLDILOCKS_EMAIL
 | 
					  , email: process.env.GOLDILOCKS_EMAIL
 | 
				
			||||||
  , cwd: process.env.GOLDILOCKS_HOME
 | 
					  , cwd: process.env.GOLDILOCKS_HOME || process.cwd()
 | 
				
			||||||
  , debug: process.env.GOLDILOCKS_DEBUG && true
 | 
					  , debug: process.env.GOLDILOCKS_DEBUG && true
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  args.cwd = args.cwd || env.cwd;
 | 
					 | 
				
			||||||
  Object.keys(env).forEach(function (key) {
 | 
					 | 
				
			||||||
    if ('undefined' === typeof args[key]) {
 | 
					 | 
				
			||||||
      args[key] = env[key];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  readConfigAndRun(args);
 | 
					  readConfigAndRun(Object.assign({}, env, args));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var program = require('commander');
 | 
					var program = require('commander');
 | 
				
			||||||
@ -217,5 +225,4 @@ program
 | 
				
			|||||||
  .option('--debug', "Enable debug output")
 | 
					  .option('--debug', "Enable debug output")
 | 
				
			||||||
  .parse(process.argv);
 | 
					  .parse(process.argv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
program.cwd = process.cwd();
 | 
					 | 
				
			||||||
readEnv(program);
 | 
					readEnv(program);
 | 
				
			||||||
 | 
				
			|||||||
@ -10,17 +10,55 @@ tcp:
 | 
				
			|||||||
      address: '127.0.0.1:8022'
 | 
					      address: '127.0.0.1:8022'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tls:
 | 
					tls:
 | 
				
			||||||
 | 
					  acme:
 | 
				
			||||||
 | 
					    email: 'joe.shmoe@example.com'
 | 
				
			||||||
 | 
					    server: 'https://acme-staging.api.letsencrypt.org/directory'
 | 
				
			||||||
 | 
					    challenge_type: 'http-01'
 | 
				
			||||||
 | 
					    approved_domains:
 | 
				
			||||||
 | 
					      - localhost.baz.daplie.me
 | 
				
			||||||
 | 
					      - localhost.beta.daplie.me
 | 
				
			||||||
 | 
					  domains:
 | 
				
			||||||
 | 
					    - names:
 | 
				
			||||||
 | 
					        - localhost.gamma.daplie.me
 | 
				
			||||||
 | 
					      modules:
 | 
				
			||||||
 | 
					        - name: proxy
 | 
				
			||||||
 | 
					          address: '127.0.0.1:6443'
 | 
				
			||||||
 | 
					    - names:
 | 
				
			||||||
 | 
					        - beta.localhost.daplie.me
 | 
				
			||||||
 | 
					        - baz.localhost.daplie.me
 | 
				
			||||||
 | 
					      modules:
 | 
				
			||||||
 | 
					        - name: acme
 | 
				
			||||||
 | 
					          email: 'owner@example.com'
 | 
				
			||||||
 | 
					          challenge_type: 'tls-sni-01'
 | 
				
			||||||
 | 
					          # default server is 'https://acme-v01.api.letsencrypt.org/directory'
 | 
				
			||||||
  modules:
 | 
					  modules:
 | 
				
			||||||
    - name: proxy
 | 
					    - name: proxy
 | 
				
			||||||
      domains:
 | 
					      domains:
 | 
				
			||||||
        - localhost.bar.daplie.me
 | 
					        - localhost.bar.daplie.me
 | 
				
			||||||
        - localhost.foo.daplie.me
 | 
					        - localhost.foo.daplie.me
 | 
				
			||||||
      address: '127.0.0.1:5443'
 | 
					      address: '127.0.0.1:5443'
 | 
				
			||||||
 | 
					    - name: acme
 | 
				
			||||||
 | 
					      email: 'guest@example.com'
 | 
				
			||||||
 | 
					      challenge_type: 'http-01'
 | 
				
			||||||
 | 
					      domains:
 | 
				
			||||||
 | 
					        - foo.localhost.daplie.me
 | 
				
			||||||
 | 
					        - gamma.localhost.daplie.me
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
http:
 | 
					http:
 | 
				
			||||||
  trust_proxy: true
 | 
					  trust_proxy: true
 | 
				
			||||||
  allow_insecure: false
 | 
					  allow_insecure: false
 | 
				
			||||||
  primary_domain: localhost.foo.daplie.me
 | 
					  primary_domain: localhost.foo.daplie.me
 | 
				
			||||||
 | 
					  domains:
 | 
				
			||||||
 | 
					    - names:
 | 
				
			||||||
 | 
					        - localhost.baz.daplie.me
 | 
				
			||||||
 | 
					      modules:
 | 
				
			||||||
 | 
					        - name: redirect
 | 
				
			||||||
 | 
					          from: /nowhere/in/particular
 | 
				
			||||||
 | 
					          to: /just/an/example
 | 
				
			||||||
 | 
					        - name: proxy
 | 
				
			||||||
 | 
					          port: 3001
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  modules:
 | 
					  modules:
 | 
				
			||||||
    - name: redirect
 | 
					    - name: redirect
 | 
				
			||||||
      domains:
 | 
					      domains:
 | 
				
			||||||
@ -31,8 +69,15 @@ http:
 | 
				
			|||||||
    - name: proxy
 | 
					    - name: proxy
 | 
				
			||||||
      domains:
 | 
					      domains:
 | 
				
			||||||
        - localhost.daplie.me
 | 
					        - localhost.daplie.me
 | 
				
			||||||
      address: '127.0.0.1:4000'
 | 
					      host: locahost
 | 
				
			||||||
 | 
					      port: 4000
 | 
				
			||||||
    - name: static
 | 
					    - name: static
 | 
				
			||||||
      domains:
 | 
					      domains:
 | 
				
			||||||
        - '*.localhost.daplie.me'
 | 
					        - '*.localhost.daplie.me'
 | 
				
			||||||
      root: '/srv/www/:hostname'
 | 
					      root: '/srv/www/:hostname'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mdns:
 | 
				
			||||||
 | 
					  disabled: false
 | 
				
			||||||
 | 
					  port: 5353
 | 
				
			||||||
 | 
					  broadcast: '224.0.0.251'
 | 
				
			||||||
 | 
					  ttl: 300
 | 
				
			||||||
 | 
				
			|||||||
@ -74,46 +74,36 @@ module.exports.create = function (deps, config) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function dnsListener(msg) {
 | 
					  function dnsListener(msg) {
 | 
				
			||||||
    var dgram = require('dgram');
 | 
					    if (!Array.isArray(config.dns.modules)) {
 | 
				
			||||||
    var socket = dgram.createSocket('udp4');
 | 
					      return;
 | 
				
			||||||
    socket.send(msg, config.dns.proxy.port, config.dns.proxy.address || '127.0.0.1');
 | 
					    }
 | 
				
			||||||
 | 
					    var socket = require('dgram').createSocket('udp4');
 | 
				
			||||||
 | 
					    config.dns.modules.forEach(function (mod) {
 | 
				
			||||||
 | 
					      if (mod.name !== 'proxy') {
 | 
				
			||||||
 | 
					        console.warn('found bad DNS module', mod);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      var dest = require('./domain-utils').separatePort(mod.address || '');
 | 
				
			||||||
 | 
					      dest.port = dest.port || mod.port;
 | 
				
			||||||
 | 
					      dest.host = dest.host || mod.host || 'localhost';
 | 
				
			||||||
 | 
					      socket.send(msg, dest.port, dest.host);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function createTcpForwarder(mod) {
 | 
					  function createTcpForwarder(mod) {
 | 
				
			||||||
    var destination = mod.address.split(':');
 | 
					    var dest = require('./domain-utils').separatePort(mod.address || '');
 | 
				
			||||||
    var connected = false;
 | 
					    dest.port = dest.port || mod.port;
 | 
				
			||||||
 | 
					    dest.host = dest.host || mod.host || 'localhost';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return function (conn) {
 | 
					    return function (conn) {
 | 
				
			||||||
      var newConn = deps.net.createConnection({
 | 
					      var newConnOpts = {};
 | 
				
			||||||
        port: destination[1]
 | 
					      ['remote', 'local'].forEach(function (end) {
 | 
				
			||||||
      , host: destination[0] || '127.0.0.1'
 | 
					        ['Family', 'Address', 'Port'].forEach(function (name) {
 | 
				
			||||||
 | 
					          newConnOpts[end+name] = conn[end+name];
 | 
				
			||||||
      , remoteFamily:  conn.remoteFamily
 | 
					        });
 | 
				
			||||||
      , remoteAddress: conn.remoteAddress
 | 
					 | 
				
			||||||
      , remotePort:    conn.remotePort
 | 
					 | 
				
			||||||
      }, function () {
 | 
					 | 
				
			||||||
        connected = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        newConn.pipe(conn);
 | 
					 | 
				
			||||||
        conn.pipe(newConn);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Not sure how to effectively report this to the user or client, but we need to listen
 | 
					 | 
				
			||||||
      // for the event to prevent it from crashing us.
 | 
					 | 
				
			||||||
      newConn.on('error', function (err) {
 | 
					 | 
				
			||||||
        if (connected) {
 | 
					 | 
				
			||||||
          console.error('TCP forward remote error', err);
 | 
					 | 
				
			||||||
          conn.end();
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          console.log('TCP forward connection error', err);
 | 
					 | 
				
			||||||
          require('./proxy-err-resp').sendBadGateway(conn, err, config.debug);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      conn.on('error', function (err) {
 | 
					 | 
				
			||||||
        console.error('TCP forward client error', err);
 | 
					 | 
				
			||||||
        newConn.end();
 | 
					 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      deps.proxy(conn, Object.assign({}, dest, newConnOpts));
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -249,5 +239,9 @@ module.exports.create = function (deps, config) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!config.mdns.disabled) {
 | 
				
			||||||
 | 
					    require('./mdns').start(deps, config);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return PromiseA.all(listenPromises);
 | 
					  return PromiseA.all(listenPromises);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										144
									
								
								lib/mdns.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								lib/mdns.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,144 @@
 | 
				
			|||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var PromiseA = require('bluebird');
 | 
				
			||||||
 | 
					var fs = PromiseA.promisifyAll(require('fs'));
 | 
				
			||||||
 | 
					var idFilename = require('path').join(__dirname, '..', 'var', 'mdns-id');
 | 
				
			||||||
 | 
					var queryName = '_cloud._tcp.local';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var randomId = {
 | 
				
			||||||
 | 
					  get: function () {
 | 
				
			||||||
 | 
					    return fs.readFileAsync(idFilename)
 | 
				
			||||||
 | 
					      .catch(function (err) {
 | 
				
			||||||
 | 
					        if (err.code !== 'ENOENT') {
 | 
				
			||||||
 | 
					          return PromiseA.reject(err);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var id = require('crypto').randomBytes(5).toString('hex');
 | 
				
			||||||
 | 
					        return randomId.set(id);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					, set: function (value) {
 | 
				
			||||||
 | 
					    return fs.writeFileAsync(idFilename, value)
 | 
				
			||||||
 | 
					      .then(function () {
 | 
				
			||||||
 | 
					        return value;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function createResponse(name, packet, ttl) {
 | 
				
			||||||
 | 
					  var rpacket = {
 | 
				
			||||||
 | 
					    header: {
 | 
				
			||||||
 | 
					      id: packet.header.id
 | 
				
			||||||
 | 
					    , qr: 1
 | 
				
			||||||
 | 
					    , opcode: 0
 | 
				
			||||||
 | 
					    , aa: 1
 | 
				
			||||||
 | 
					    , tc: 0
 | 
				
			||||||
 | 
					    , rd: 0
 | 
				
			||||||
 | 
					    , ra: 0
 | 
				
			||||||
 | 
					    , res1:  0
 | 
				
			||||||
 | 
					    , res2:  0
 | 
				
			||||||
 | 
					    , res3:  0
 | 
				
			||||||
 | 
					    , rcode: 0
 | 
				
			||||||
 | 
					  , }
 | 
				
			||||||
 | 
					  , question: packet.question
 | 
				
			||||||
 | 
					  , answer: []
 | 
				
			||||||
 | 
					  , authority: []
 | 
				
			||||||
 | 
					  , additional: []
 | 
				
			||||||
 | 
					  , edns_options: []
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rpacket.answer.push({
 | 
				
			||||||
 | 
					    name: queryName
 | 
				
			||||||
 | 
					  , typeName: 'PTR'
 | 
				
			||||||
 | 
					  , ttl: ttl
 | 
				
			||||||
 | 
					  , className: 'IN'
 | 
				
			||||||
 | 
					  , data: name + '.' + queryName
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var ifaces = require('./local-ip').find();
 | 
				
			||||||
 | 
					  Object.keys(ifaces).forEach(function (iname) {
 | 
				
			||||||
 | 
					    var iface = ifaces[iname];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    iface.ipv4.forEach(function (addr) {
 | 
				
			||||||
 | 
					      rpacket.additional.push({
 | 
				
			||||||
 | 
					        name: name + '.local'
 | 
				
			||||||
 | 
					      , typeName: 'A'
 | 
				
			||||||
 | 
					      , ttl: ttl
 | 
				
			||||||
 | 
					      , className: 'IN'
 | 
				
			||||||
 | 
					      , address: addr.address
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    iface.ipv6.forEach(function (addr) {
 | 
				
			||||||
 | 
					      rpacket.additional.push({
 | 
				
			||||||
 | 
					        name: name + '.local'
 | 
				
			||||||
 | 
					      , typeName: 'AAAA'
 | 
				
			||||||
 | 
					      , ttl: ttl
 | 
				
			||||||
 | 
					      , className: 'IN'
 | 
				
			||||||
 | 
					      , address: addr.address
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rpacket.additional.push({
 | 
				
			||||||
 | 
					    name: name + '.' + queryName
 | 
				
			||||||
 | 
					  , typeName: 'SRV'
 | 
				
			||||||
 | 
					  , ttl: ttl
 | 
				
			||||||
 | 
					  , className: 'IN'
 | 
				
			||||||
 | 
					  , priority: 1
 | 
				
			||||||
 | 
					  , weight: 0
 | 
				
			||||||
 | 
					  , port: 443
 | 
				
			||||||
 | 
					  , target: name + ".local"
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  rpacket.additional.push({
 | 
				
			||||||
 | 
					    name: name + '._device-info._tcp.local'
 | 
				
			||||||
 | 
					  , typeName: 'TXT'
 | 
				
			||||||
 | 
					  , ttl: ttl
 | 
				
			||||||
 | 
					  , className: 'IN'
 | 
				
			||||||
 | 
					  , data: ["model=CloudHome1,1", "dappsvers=1"]
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return require('dns-suite').DNSPacket.write(rpacket);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.start = function (deps, config) {
 | 
				
			||||||
 | 
					  var socket = require('dgram').createSocket({ type: 'udp4', reuseAddr: true });
 | 
				
			||||||
 | 
					  var dns = require('dns-suite');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  socket.on('message', function (message, rinfo) {
 | 
				
			||||||
 | 
					    // console.log('Received %d bytes from %s:%d', message.length, rinfo.address, rinfo.port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var packet;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      packet = dns.DNSPacket.parse(message);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    catch (er) {
 | 
				
			||||||
 | 
					      // `dns-suite` actually errors on a lot of the packets floating around in our network,
 | 
				
			||||||
 | 
					      // so don't bother logging any errors. (We still use `dns-suite` because unlike `dns-js`
 | 
				
			||||||
 | 
					      // it can successfully craft the one packet we want to send.)
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Only respond to queries.
 | 
				
			||||||
 | 
					    if (packet.header.qr !== 0) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // Only respond if they were asking for cloud devices.
 | 
				
			||||||
 | 
					    if (packet.question.length !== 1 || packet.question[0].name !== queryName) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    randomId.get().then(function (name) {
 | 
				
			||||||
 | 
					      var resp = createResponse(name, packet, config.mdns.ttl);
 | 
				
			||||||
 | 
					      socket.send(resp, config.mdns.port, config.mdns.broadcast);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  socket.bind(config.mdns.port, function () {
 | 
				
			||||||
 | 
					    var addr = this.address();
 | 
				
			||||||
 | 
					    console.log('bound on UDP %s:%d for mDNS', addr.address, addr.port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    socket.setBroadcast(true);
 | 
				
			||||||
 | 
					    socket.addMembership(config.mdns.broadcast);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -59,6 +59,6 @@ module.exports.create = function (deps, conf) {
 | 
				
			|||||||
    ]
 | 
					    ]
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /* device, addresses, cwd, http */
 | 
					  var app = require('../app.js')(deps, conf, opts);
 | 
				
			||||||
  return require('../app.js')(deps, conf, opts);
 | 
					  return require('http').createServer(app);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,10 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports.create = function (deps, conf, greenlockMiddleware) {
 | 
					module.exports.create = function (deps, conf, greenlockMiddleware) {
 | 
				
			||||||
  var express = require('express');
 | 
					  var PromiseA = require('bluebird');
 | 
				
			||||||
  var app = express();
 | 
					  var statAsync = PromiseA.promisify(require('fs').stat);
 | 
				
			||||||
  var adminApp = require('./admin').create(deps, conf);
 | 
					 | 
				
			||||||
  var domainMatches = require('../domain-utils').match;
 | 
					  var domainMatches = require('../domain-utils').match;
 | 
				
			||||||
  var separatePort = require('../domain-utils').separatePort;
 | 
					  var separatePort = require('../domain-utils').separatePort;
 | 
				
			||||||
  var proxyRoutes = [];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var adminDomains = [
 | 
					  var adminDomains = [
 | 
				
			||||||
    /\blocalhost\.admin\./
 | 
					    /\blocalhost\.admin\./
 | 
				
			||||||
@ -15,132 +13,233 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
 | 
				
			|||||||
  , /\balpha\.localhost\./
 | 
					  , /\balpha\.localhost\./
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function moduleMatchesHost(req, mod) {
 | 
					  function parseHeaders(conn, opts) {
 | 
				
			||||||
    var host = separatePort(req.headers.host).host;
 | 
					    // There should already be a `firstChunk` on the opts, but because we might sometimes
 | 
				
			||||||
 | 
					    // need more than that to get all the headers it's easier to always read the data off
 | 
				
			||||||
 | 
					    // the connection and put it back later if we need to.
 | 
				
			||||||
 | 
					    opts.firstChunk = Buffer.alloc(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return mod.domains.some(function (pattern) {
 | 
					    // First we make sure we have all of the headers.
 | 
				
			||||||
 | 
					    return new PromiseA(function (resolve, reject) {
 | 
				
			||||||
 | 
					      if (opts.firstChunk.includes('\r\n\r\n')) {
 | 
				
			||||||
 | 
					        resolve(opts.firstChunk.toString());
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var errored = false;
 | 
				
			||||||
 | 
					      function handleErr(err) {
 | 
				
			||||||
 | 
					        errored = true;
 | 
				
			||||||
 | 
					        reject(err);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      conn.once('error', handleErr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      function handleChunk(chunk) {
 | 
				
			||||||
 | 
					        if (!errored) {
 | 
				
			||||||
 | 
					          opts.firstChunk = Buffer.concat([opts.firstChunk, chunk]);
 | 
				
			||||||
 | 
					          if (opts.firstChunk.includes('\r\n\r\n')) {
 | 
				
			||||||
 | 
					            resolve(opts.firstChunk.toString());
 | 
				
			||||||
 | 
					            conn.removeListener('error', handleErr);
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            conn.once('data', handleChunk);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      conn.once('data', handleChunk);
 | 
				
			||||||
 | 
					    }).then(function (firstStr) {
 | 
				
			||||||
 | 
					      var headerSection = firstStr.split('\r\n\r\n')[0];
 | 
				
			||||||
 | 
					      var lines = headerSection.split('\r\n');
 | 
				
			||||||
 | 
					      var result = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lines.slice(1).forEach(function (line) {
 | 
				
			||||||
 | 
					        var match = /(.*)\s*:\s*(.*)/.exec(line);
 | 
				
			||||||
 | 
					        if (match) {
 | 
				
			||||||
 | 
					          result[match[1].toLowerCase()] = match[2];
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          console.error('HTTP header line does not match pattern', line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var match = /^([a-zA-Z]+)\s+(\S+)\s+HTTP/.exec(lines[0]);
 | 
				
			||||||
 | 
					      if (!match) {
 | 
				
			||||||
 | 
					        throw new Error('first line of "HTTP" does not match pattern: '+lines[0]);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      result.method = match[1].toUpperCase();
 | 
				
			||||||
 | 
					      result.url = match[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return result;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function hostMatchesDomains(req, domains) {
 | 
				
			||||||
 | 
					    var host = separatePort((req.headers || req).host).host;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return domains.some(function (pattern) {
 | 
				
			||||||
      return domainMatches(pattern, host);
 | 
					      return domainMatches(pattern, host);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function verifyHost(fullHost) {
 | 
					  function determinePrimaryHost() {
 | 
				
			||||||
    var host = separatePort(fullHost).host;
 | 
					    var result;
 | 
				
			||||||
 | 
					    if (Array.isArray(conf.http.domains)) {
 | 
				
			||||||
    if (host === 'localhost') {
 | 
					      conf.http.domains.some(function (dom) {
 | 
				
			||||||
      return fullHost.replace(host, 'localhost.daplie.me');
 | 
					        return dom.names.some(function (domain) {
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Test for IPv4 and IPv6 addresses. These patterns will match some invalid addresses,
 | 
					 | 
				
			||||||
    // but since those still won't be valid domains that won't really be a problem.
 | 
					 | 
				
			||||||
    if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host) || /^\[[0-9a-fA-F:]+\]$/.test(host)) {
 | 
					 | 
				
			||||||
      if (!conf.http.primaryDomain) {
 | 
					 | 
				
			||||||
        (conf.http.modules || []).some(function (mod) {
 | 
					 | 
				
			||||||
          return mod.domains.some(function (domain) {
 | 
					 | 
				
			||||||
          if (domain[0] !== '*') {
 | 
					          if (domain[0] !== '*') {
 | 
				
			||||||
              conf.http.primaryDomain = domain;
 | 
					            result = domain;
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
      return fullHost.replace(host, conf.http.primaryDomain || host);
 | 
					    if (result) {
 | 
				
			||||||
 | 
					      return result;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return fullHost;
 | 
					    if (Array.isArray(conf.http.modules)) {
 | 
				
			||||||
 | 
					      conf.http.modules.some(function (mod) {
 | 
				
			||||||
 | 
					        return mod.domains.some(function (domain) {
 | 
				
			||||||
 | 
					          if (domain[0] !== '*') {
 | 
				
			||||||
 | 
					            result = domain;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // We handle both HTTPS and HTTP traffic on the same ports, and we want to redirect
 | 
					  // We handle both HTTPS and HTTP traffic on the same ports, and we want to redirect
 | 
				
			||||||
  // any unencrypted requests to the same port they came from unless it came in on
 | 
					  // any unencrypted requests to the same port they came from unless it came in on
 | 
				
			||||||
  // the default HTTP port, in which case there wont be a port specified in the host.
 | 
					  // the default HTTP port, in which case there wont be a port specified in the host.
 | 
				
			||||||
  var redirecters = {};
 | 
					  var redirecters = {};
 | 
				
			||||||
  function redirectHttps(req, res, next) {
 | 
					  var ipv4Re = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
 | 
				
			||||||
    if (conf.http.allowInsecure) {
 | 
					  var ipv6Re = /^\[[0-9a-fA-F:]+\]$/;
 | 
				
			||||||
      next();
 | 
					  function redirectHttps(req, res) {
 | 
				
			||||||
      return;
 | 
					    var host = separatePort(req.headers.host);
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var port = separatePort(req.headers.host).port;
 | 
					    if (!redirecters[host.port]) {
 | 
				
			||||||
    if (!redirecters[port]) {
 | 
					      redirecters[host.port] = require('redirect-https')({ port: host.port });
 | 
				
			||||||
      redirecters[port] = require('redirect-https')({
 | 
					 | 
				
			||||||
        port: port
 | 
					 | 
				
			||||||
      , trustProxy: conf.http.trustProxy
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // localhost and IP addresses cannot have real SSL certs (and don't contain any useful
 | 
					    // localhost and IP addresses cannot have real SSL certs (and don't contain any useful
 | 
				
			||||||
    // info for redirection either), so we direct some hosts to either localhost.daplie.me
 | 
					    // info for redirection either), so we direct some hosts to either localhost.daplie.me
 | 
				
			||||||
    // or the "primary domain" ie the first manually specified domain.
 | 
					    // or the "primary domain" ie the first manually specified domain.
 | 
				
			||||||
    req.headers.host = verifyHost(req.headers.host);
 | 
					    if (host.host === 'localhost') {
 | 
				
			||||||
 | 
					      req.headers.host = 'localhost.daplie.me' + (host.port ? ':'+host.port : '');
 | 
				
			||||||
    redirecters[port](req, res, next);
 | 
					    }
 | 
				
			||||||
 | 
					    // Test for IPv4 and IPv6 addresses. These patterns will match some invalid addresses,
 | 
				
			||||||
 | 
					    // but since those still won't be valid domains that won't really be a problem.
 | 
				
			||||||
 | 
					    if (ipv4Re.test(host.host) || ipv6Re.test(host.host)) {
 | 
				
			||||||
 | 
					      var dest;
 | 
				
			||||||
 | 
					      if (conf.http.primaryDomain) {
 | 
				
			||||||
 | 
					        dest = conf.http.primaryDomain;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        dest = determinePrimaryHost();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (dest) {
 | 
				
			||||||
 | 
					        req.headers.host = dest + (host.port ? ':'+host.port : '');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function handleAdmin(req, res, next) {
 | 
					    redirecters[host.port](req, res);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function emitConnection(server, conn, opts) {
 | 
				
			||||||
 | 
					    server.emit('connection', conn);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We need to put back whatever data we read off to determine the connection was HTTP
 | 
				
			||||||
 | 
					    // and to parse the headers. Must be done after data handlers added but before any new
 | 
				
			||||||
 | 
					    // data comes in.
 | 
				
			||||||
 | 
					    process.nextTick(function () {
 | 
				
			||||||
 | 
					      conn.unshift(opts.firstChunk);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Convenience return for all the check* functions.
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var acmeServer;
 | 
				
			||||||
 | 
					  function checkACME(conn, opts, headers) {
 | 
				
			||||||
 | 
					    if (headers.url.indexOf('/.well-known/acme-challenge/') !== 0) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!acmeServer) {
 | 
				
			||||||
 | 
					      acmeServer = require('http').createServer(greenlockMiddleware);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return emitConnection(acmeServer, conn, opts);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var httpsRedirectServer;
 | 
				
			||||||
 | 
					  function checkHttps(conn, opts, headers) {
 | 
				
			||||||
 | 
					    if (conf.http.allowInsecure || conn.encrypted) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (conf.http.trustProxy && 'https' === headers['x-forwarded-proto']) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!httpsRedirectServer) {
 | 
				
			||||||
 | 
					      httpsRedirectServer = require('http').createServer(redirectHttps);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return emitConnection(httpsRedirectServer, conn, opts);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var adminServer;
 | 
				
			||||||
 | 
					  function checkAdmin(conn, opts, headers) {
 | 
				
			||||||
    var admin = adminDomains.some(function (re) {
 | 
					    var admin = adminDomains.some(function (re) {
 | 
				
			||||||
      return re.test(req.headers.host);
 | 
					      return re.test(headers.host);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (admin) {
 | 
					    if (admin) {
 | 
				
			||||||
      adminApp(req, res);
 | 
					      if (!adminServer) {
 | 
				
			||||||
    } else {
 | 
					        adminServer = require('./admin').create(deps, conf);
 | 
				
			||||||
      next();
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      return emitConnection(adminServer, conn, opts);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function respond404(req, res) {
 | 
					  function checkProxy(mod, conn, opts, headers) {
 | 
				
			||||||
    res.writeHead(404);
 | 
					    var index = opts.firstChunk.indexOf('\r\n\r\n');
 | 
				
			||||||
    res.end('Not Found');
 | 
					    var body = opts.firstChunk.slice(index);
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function createProxyRoute(mod) {
 | 
					    var head = opts.firstChunk.slice(0, index).toString();
 | 
				
			||||||
    // This is the easiest way to override the createConnections function the proxy
 | 
					    var headLines = head.split('\r\n');
 | 
				
			||||||
    // module uses, but take note the since we don't have control over where this is
 | 
					    // First strip any existing `X-Forwarded-*` headers (for security purposes?)
 | 
				
			||||||
    // called the extra options availabled will be different.
 | 
					    headLines = headLines.filter(function (line) {
 | 
				
			||||||
    var agent = new require('http').Agent({});
 | 
					      return !/^x-forwarded/i.test(line);
 | 
				
			||||||
    agent.createConnection = deps.net.createConnection;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var proxy = require('http-proxy').createProxyServer({
 | 
					 | 
				
			||||||
      agent: agent
 | 
					 | 
				
			||||||
    , target: 'http://' + mod.address
 | 
					 | 
				
			||||||
    , xfwd: true
 | 
					 | 
				
			||||||
    , toProxy: true
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    // Then add our own `X-Forwarded` headers at the end.
 | 
				
			||||||
    // We want to override the default value for some headers with the extra information we
 | 
					    if (conf.http.trustProxy && headers['x-forwarded-proto']) {
 | 
				
			||||||
    // have available to us in the opts object attached to the connection.
 | 
					      headLines.push('X-Forwarded-Proto: ' + headers['x-forwarded-proto']);
 | 
				
			||||||
    proxy.on('proxyReq', function (proxyReq, req) {
 | 
					 | 
				
			||||||
      var conn = req.connection;
 | 
					 | 
				
			||||||
      var opts = conn.__opts;
 | 
					 | 
				
			||||||
      proxyReq.setHeader('X-Forwarded-For', opts.remoteAddress || conn.remoteAddress);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    proxy.on('error', function (err, req, res) {
 | 
					 | 
				
			||||||
      console.log(err);
 | 
					 | 
				
			||||||
      res.statusCode = 502;
 | 
					 | 
				
			||||||
      res.setHeader('Content-Type', 'text/html');
 | 
					 | 
				
			||||||
      res.setHeader('Connection', 'close');
 | 
					 | 
				
			||||||
      res.end(require('../proxy-err-resp').getRespBody(err, conf.debug));
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      web: function (req, res, next) {
 | 
					 | 
				
			||||||
        if (moduleMatchesHost(req, mod)) {
 | 
					 | 
				
			||||||
          proxy.web(req, res);
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
          next();
 | 
					      headLines.push('X-Forwarded-Proto: ' + conn.encrypted ? 'https' : 'http');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
      }
 | 
					    var proxyChain = (headers['x-forwarded-for'] || '').split(/ *, */).filter(Boolean);
 | 
				
			||||||
    , ws: function (req, socket, head, next) {
 | 
					    proxyChain.push(opts.remoteAddress || opts.address || conn.remoteAddress);
 | 
				
			||||||
        if (moduleMatchesHost(req, mod)) {
 | 
					    headLines.push('X-Forwarded-For: ' + proxyChain.join(', '));
 | 
				
			||||||
          proxy.ws(req, socket, head);
 | 
					    headLines.push('X-Forwarded-Host: ' + headers.host);
 | 
				
			||||||
        } else {
 | 
					    // Then convert all of the head lines back into a header buffer.
 | 
				
			||||||
          next();
 | 
					    head = Buffer.from(headLines.join('\r\n'));
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
      }
 | 
					    opts.firstChunk = Buffer.concat([head, body]);
 | 
				
			||||||
    };
 | 
					
 | 
				
			||||||
 | 
					    var newConnOpts = separatePort(mod.address || '');
 | 
				
			||||||
 | 
					    newConnOpts.port = newConnOpts.port || mod.port;
 | 
				
			||||||
 | 
					    newConnOpts.host = newConnOpts.host || mod.host || 'localhost';
 | 
				
			||||||
 | 
					    newConnOpts.servername = separatePort(headers.host).host;
 | 
				
			||||||
 | 
					    newConnOpts.data = opts.firstChunk;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    newConnOpts.remoteFamily  = opts.family  || conn.remoteFamily;
 | 
				
			||||||
 | 
					    newConnOpts.remoteAddress = opts.address || conn.remoteAddress;
 | 
				
			||||||
 | 
					    newConnOpts.remotePort    = opts.port    || conn.remotePort;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deps.proxy(conn, newConnOpts, opts.firstChunk);
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function createRedirectRoute(mod) {
 | 
					  function checkRedirect(mod, conn, opts, headers) {
 | 
				
			||||||
 | 
					    if (!mod.fromRe || mod.fromRe.origSrc !== mod.from) {
 | 
				
			||||||
      // Escape any characters that (can) have special meaning in regular expression
 | 
					      // Escape any characters that (can) have special meaning in regular expression
 | 
				
			||||||
      // but that aren't the special characters we have interest in.
 | 
					      // but that aren't the special characters we have interest in.
 | 
				
			||||||
      var from = mod.from.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&');
 | 
					      var from = mod.from.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&');
 | 
				
			||||||
@ -148,99 +247,153 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
 | 
				
			|||||||
      // the regular expression after being compiled.
 | 
					      // the regular expression after being compiled.
 | 
				
			||||||
      from = from.replace(/\*/g, '(.*)');
 | 
					      from = from.replace(/\*/g, '(.*)');
 | 
				
			||||||
      var fromRe = new RegExp('^' + from + '/?$');
 | 
					      var fromRe = new RegExp('^' + from + '/?$');
 | 
				
			||||||
 | 
					      fromRe.origSrc = mod.from;
 | 
				
			||||||
    return function (req, res, next) {
 | 
					      // We don't want this property showing up in the actual config file or the API,
 | 
				
			||||||
      if (!moduleMatchesHost(req, mod)) {
 | 
					      // so we define it this way so it's not enumberable.
 | 
				
			||||||
        next();
 | 
					      Object.defineProperty(mod, 'fromRe', {value: fromRe, configurable: true});
 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var match = fromRe.exec(req.url);
 | 
					    var match = mod.fromRe.exec(headers.url);
 | 
				
			||||||
    if (!match) {
 | 
					    if (!match) {
 | 
				
			||||||
        next();
 | 
					      return false;
 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var to = mod.to;
 | 
					    var to = mod.to;
 | 
				
			||||||
    match.slice(1).forEach(function (globMatch, index) {
 | 
					    match.slice(1).forEach(function (globMatch, index) {
 | 
				
			||||||
      to = to.replace(':'+(index+1), globMatch);
 | 
					      to = to.replace(':'+(index+1), globMatch);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
      res.writeHead(mod.status || 301, { 'Location': to });
 | 
					    var status = mod.status || 301;
 | 
				
			||||||
      res.end();
 | 
					    var code = require('http').STATUS_CODES[status] || 'Unknown';
 | 
				
			||||||
    };
 | 
					
 | 
				
			||||||
 | 
					    conn.end([
 | 
				
			||||||
 | 
					      'HTTP/1.1 ' + status + ' ' + code
 | 
				
			||||||
 | 
					    , 'Date: ' + (new Date()).toUTCString()
 | 
				
			||||||
 | 
					    , 'Location: ' + to
 | 
				
			||||||
 | 
					    , 'Connection: close'
 | 
				
			||||||
 | 
					    , 'Content-Length: 0'
 | 
				
			||||||
 | 
					    , ''
 | 
				
			||||||
 | 
					    , ''
 | 
				
			||||||
 | 
					    ].join('\r\n'));
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function createStaticRoute(mod) {
 | 
					  var staticServer;
 | 
				
			||||||
    var getStaticApp, staticApp;
 | 
					  var staticHandlers = {};
 | 
				
			||||||
    if (/:hostname/.test(mod.root)) {
 | 
					  function serveStatic(req, res) {
 | 
				
			||||||
      staticApp = {};
 | 
					    var rootDir = req.connection.rootDir;
 | 
				
			||||||
      getStaticApp = function (hostname) {
 | 
					
 | 
				
			||||||
        if (!staticApp[hostname]) {
 | 
					    if (!staticHandlers[rootDir]) {
 | 
				
			||||||
          staticApp[hostname] = express.static(mod.root.replace(':hostname', hostname));
 | 
					      staticHandlers[rootDir] = require('express').static(rootDir, { fallthrough: false });
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return staticApp[hostname];
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else {
 | 
					 | 
				
			||||||
      staticApp = express.static(mod.root);
 | 
					 | 
				
			||||||
      getStaticApp = function () {
 | 
					 | 
				
			||||||
        return staticApp;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return function (req, res, next) {
 | 
					    staticHandlers[rootDir](req, res, function (err) {
 | 
				
			||||||
      if (moduleMatchesHost(req, mod)) {
 | 
					      if (err) {
 | 
				
			||||||
        getStaticApp(separatePort(req.headers.host).host)(req, res, next);
 | 
					        res.statusCode = err.statusCode;
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        next();
 | 
					        res.statusCode = 404;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      res.setHeader('Content-Type', 'text/html');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (res.statusCode === 404) {
 | 
				
			||||||
 | 
					        res.end('File Not Found');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        res.end(require('http').STATUS_CODES[res.statusCode]);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  function checkStatic(mod, conn, opts, headers) {
 | 
				
			||||||
 | 
					    var rootDir = mod.root.replace(':hostname', separatePort(headers.host).host);
 | 
				
			||||||
 | 
					    return statAsync(rootDir)
 | 
				
			||||||
 | 
					      .then(function (stats) {
 | 
				
			||||||
 | 
					        if (!stats || !stats.isDirectory()) {
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!staticServer) {
 | 
				
			||||||
 | 
					          staticServer = require('http').createServer(serveStatic);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        conn.rootDir = rootDir;
 | 
				
			||||||
 | 
					        return emitConnection(staticServer, conn, opts);
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch(function (err) {
 | 
				
			||||||
 | 
					        if (err.code !== 'ENOENT') {
 | 
				
			||||||
 | 
					          console.warn('errored stating', rootDir, 'for serving static files', err);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      ;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var moduleChecks = {
 | 
				
			||||||
 | 
					    proxy:    checkProxy
 | 
				
			||||||
 | 
					  , redirect: checkRedirect
 | 
				
			||||||
 | 
					  , static:   checkStatic
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function handleConnection(conn) {
 | 
				
			||||||
 | 
					    var opts = conn.__opts;
 | 
				
			||||||
 | 
					    parseHeaders(conn, opts)
 | 
				
			||||||
 | 
					      .then(function (headers) {
 | 
				
			||||||
 | 
					        if (checkACME(conn, opts, headers))  { return; }
 | 
				
			||||||
 | 
					        if (checkHttps(conn, opts, headers)) { return; }
 | 
				
			||||||
 | 
					        if (checkAdmin(conn, opts, headers)) { return; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var prom = PromiseA.resolve(false);
 | 
				
			||||||
 | 
					        (conf.http.domains || []).forEach(function (dom) {
 | 
				
			||||||
 | 
					          prom = prom.then(function (handled) {
 | 
				
			||||||
 | 
					            if (handled) {
 | 
				
			||||||
 | 
					              return handled;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!hostMatchesDomains(headers, dom.names)) {
 | 
				
			||||||
 | 
					              return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return dom.modules.some(function (mod) {
 | 
				
			||||||
 | 
					              if (moduleChecks[mod.name]) {
 | 
				
			||||||
 | 
					                return moduleChecks[mod.name](mod, conn, opts, headers);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              console.warn('unknown HTTP module under domains', dom.names.join(','), mod);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        (conf.http.modules || []).forEach(function (mod) {
 | 
				
			||||||
 | 
					          prom = prom.then(function (handled) {
 | 
				
			||||||
 | 
					            if (handled) {
 | 
				
			||||||
 | 
					              return handled;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!hostMatchesDomains(headers, mod.domains)) {
 | 
				
			||||||
 | 
					              return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (moduleChecks[mod.name]) {
 | 
				
			||||||
 | 
					              return moduleChecks[mod.name](mod, conn, opts, headers);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            console.warn('unknown HTTP module found', mod);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        prom.then(function (handled) {
 | 
				
			||||||
 | 
					          if (!handled) {
 | 
				
			||||||
 | 
					            conn.end([
 | 
				
			||||||
 | 
					              'HTTP/1.1 404 Not Found'
 | 
				
			||||||
 | 
					            , 'Date: ' + (new Date()).toUTCString()
 | 
				
			||||||
 | 
					            , 'Content-Type: text/html'
 | 
				
			||||||
 | 
					            , 'Content-Length: 9'
 | 
				
			||||||
 | 
					            , 'Connection: close'
 | 
				
			||||||
 | 
					            , ''
 | 
				
			||||||
 | 
					            , 'Not Found'
 | 
				
			||||||
 | 
					            ].join('\r\n'));
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      ;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    emit: function (type, value) {
 | 
				
			||||||
 | 
					      if (type === 'connection') {
 | 
				
			||||||
 | 
					        handleConnection(value);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  app.use(greenlockMiddleware);
 | 
					 | 
				
			||||||
  app.use(redirectHttps);
 | 
					 | 
				
			||||||
  app.use(handleAdmin);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  (conf.http.modules || []).forEach(function (mod) {
 | 
					 | 
				
			||||||
    if (mod.name === 'proxy') {
 | 
					 | 
				
			||||||
      var proxyRoute = createProxyRoute(mod);
 | 
					 | 
				
			||||||
      proxyRoutes.push(proxyRoute);
 | 
					 | 
				
			||||||
      app.use(proxyRoute.web);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else if (mod.name === 'redirect') {
 | 
					 | 
				
			||||||
      app.use(createRedirectRoute(mod));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else if (mod.name === 'static') {
 | 
					 | 
				
			||||||
      app.use(createStaticRoute(mod));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else {
 | 
					 | 
				
			||||||
      console.warn('unknown HTTP module', mod);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  app.use(respond404);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var server = require('http').createServer(function (req, res) {
 | 
					 | 
				
			||||||
    app(req, res)
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.on('upgrade', function (req, socket, head) {
 | 
					 | 
				
			||||||
    if (!proxyRoutes.length) {
 | 
					 | 
				
			||||||
      socket.end();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var prs = proxyRoutes.slice();
 | 
					 | 
				
			||||||
    function proxyWs() {
 | 
					 | 
				
			||||||
      var proxyRoute = prs.shift();
 | 
					 | 
				
			||||||
      if (!proxyRoute) {
 | 
					 | 
				
			||||||
        socket.end();
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      proxyRoute.ws(req, socket, head, proxyWs);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    proxyWs();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return server;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -9,52 +9,81 @@ module.exports.create = function (deps, config, netHandler) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  function extractSocketProp(socket, propName) {
 | 
					  function extractSocketProp(socket, propName) {
 | 
				
			||||||
    // remoteAddress, remotePort... ugh... https://github.com/nodejs/node/issues/8854
 | 
					    // remoteAddress, remotePort... ugh... https://github.com/nodejs/node/issues/8854
 | 
				
			||||||
    return socket[propName]
 | 
					    var value = socket[propName] || socket['_' + propName];
 | 
				
			||||||
      || socket['_' + propName]
 | 
					    try {
 | 
				
			||||||
      || socket._handle._parent.owner.stream[propName]
 | 
					      value = value || socket._handle._parent.owner.stream[propName];
 | 
				
			||||||
      ;
 | 
					    } catch (e) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      value = value || socket._handle._parentWrap[propName];
 | 
				
			||||||
 | 
					      value = value || socket._handle._parentWrap._handle.owner.stream[propName];
 | 
				
			||||||
 | 
					    } catch (e) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return value || '';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function wrapSocket(socket, opts) {
 | 
					  function nameMatchesDomains(name, domains) {
 | 
				
			||||||
    var myDuplex = require('tunnel-packer').Stream.create(socket);
 | 
					    return domains.some(function (pattern) {
 | 
				
			||||||
    myDuplex.remoteFamily = opts.remoteFamily || myDuplex.remoteFamily;
 | 
					      return domainMatches(pattern, name);
 | 
				
			||||||
    myDuplex.remoteAddress = opts.remoteAddress || myDuplex.remoteAddress;
 | 
					    });
 | 
				
			||||||
    myDuplex.remotePort = opts.remotePort || myDuplex.remotePort;
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    socket.on('data', function (chunk) {
 | 
					  var addressNames = [
 | 
				
			||||||
      console.log('[' + Date.now() + '] tls socket data', chunk.byteLength);
 | 
					    'remoteAddress'
 | 
				
			||||||
      myDuplex.push(chunk);
 | 
					  , 'remotePort'
 | 
				
			||||||
    });
 | 
					  , 'remoteFamily'
 | 
				
			||||||
    socket.on('error', function (err) {
 | 
					  , 'localAddress'
 | 
				
			||||||
      console.error('[error] httpsTunnel (Admin) TODO close');
 | 
					  , 'localPort'
 | 
				
			||||||
      console.error(err);
 | 
					  ];
 | 
				
			||||||
      myDuplex.emit('error', err);
 | 
					  function wrapSocket(socket, opts) {
 | 
				
			||||||
    });
 | 
					    var reader = require('socket-pair').create(function (err, writer) {
 | 
				
			||||||
    socket.on('close', function () {
 | 
					      if (err) {
 | 
				
			||||||
      myDuplex.end();
 | 
					        reader.emit('error', err);
 | 
				
			||||||
    });
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      process.nextTick(function () {
 | 
					      process.nextTick(function () {
 | 
				
			||||||
      // this must happen after the socket is emitted to the next in the chain,
 | 
					 | 
				
			||||||
      // but before any more data comes in via the network
 | 
					 | 
				
			||||||
        socket.unshift(opts.firstChunk);
 | 
					        socket.unshift(opts.firstChunk);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return myDuplex;
 | 
					      socket.pipe(writer);
 | 
				
			||||||
 | 
					      writer.pipe(socket);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      socket.on('error', function (err) {
 | 
				
			||||||
 | 
					        console.log('wrapped TLS socket error', err);
 | 
				
			||||||
 | 
					        reader.emit('error', err);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      writer.on('error', function (err) {
 | 
				
			||||||
 | 
					        console.error('socket-pair writer error', err);
 | 
				
			||||||
 | 
					        // If the writer had an error the reader probably did too, and I don't think we'll
 | 
				
			||||||
 | 
					        // get much out of emitting this on the original socket, so logging is enough.
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We can't set these properties the normal way because there is a getter without a setter,
 | 
				
			||||||
 | 
					    // but we can use defineProperty. We reuse the descriptor even though we will be manipulating
 | 
				
			||||||
 | 
					    // it because we will only ever set the value and we set it every time.
 | 
				
			||||||
 | 
					    var descriptor = {enumerable: true, configurable: true, writable: true};
 | 
				
			||||||
 | 
					    addressNames.forEach(function (name) {
 | 
				
			||||||
 | 
					      descriptor.value = opts[name] || extractSocketProp(socket, name);
 | 
				
			||||||
 | 
					      Object.defineProperty(reader, name, descriptor);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return reader;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var le = greenlock.create({
 | 
					  var le = greenlock.create({
 | 
				
			||||||
    // server: 'staging'
 | 
					 | 
				
			||||||
    server: 'https://acme-v01.api.letsencrypt.org/directory'
 | 
					    server: 'https://acme-v01.api.letsencrypt.org/directory'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  , challenges: {
 | 
					  , challenges: {
 | 
				
			||||||
      'http-01': require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges', debug: config.debug })
 | 
					      'http-01': require('le-challenge-fs').create({ debug: config.debug })
 | 
				
			||||||
    , 'tls-sni-01': require('le-challenge-sni').create({ debug: config.debug })
 | 
					    , 'tls-sni-01': require('le-challenge-sni').create({ debug: config.debug })
 | 
				
			||||||
      // TODO dns-01
 | 
					      // TODO dns-01
 | 
				
			||||||
      //, 'dns-01': require('le-challenge-ddns').create()
 | 
					      //, 'dns-01': require('le-challenge-ddns').create({ debug: config.debug })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  , challengeType: 'http-01'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  , store: require('le-store-certbot').create({ webrootPath: '/tmp/acme-challenges' })
 | 
					  , store: require('le-store-certbot').create({ debug: config.debug })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  , approveDomains: function (opts, certs, cb) {
 | 
					  , approveDomains: function (opts, certs, cb) {
 | 
				
			||||||
      // This is where you check your database and associated
 | 
					      // This is where you check your database and associated
 | 
				
			||||||
@ -65,60 +94,87 @@ module.exports.create = function (deps, config, netHandler) {
 | 
				
			|||||||
      if (certs) {
 | 
					      if (certs) {
 | 
				
			||||||
        // TODO make sure the same options are used for renewal as for registration?
 | 
					        // TODO make sure the same options are used for renewal as for registration?
 | 
				
			||||||
        opts.domains = certs.altnames;
 | 
					        opts.domains = certs.altnames;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        cb(null, { options: opts, certs: certs });
 | 
					        cb(null, { options: opts, certs: certs });
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      function complete(optsOverride) {
 | 
					      function complete(optsOverride, domains) {
 | 
				
			||||||
        Object.keys(optsOverride).forEach(function (key) {
 | 
					        if (!cb) {
 | 
				
			||||||
          opts[key] = optsOverride[key];
 | 
					          console.warn('tried to complete domain approval multiple times');
 | 
				
			||||||
        });
 | 
					          return;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        cb(null, { options: opts, certs: certs });
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // We can't request certificates for wildcard domains, so filter any of those
 | 
				
			||||||
 | 
					        // out of this list and put the domain that triggered this in the list if needed.
 | 
				
			||||||
 | 
					        domains = (domains || []).filter(function (dom) { return dom[0] !== '*'; });
 | 
				
			||||||
 | 
					        if (domains.indexOf(opts.domain) < 0) {
 | 
				
			||||||
 | 
					          domains.push(opts.domain);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // TODO: allow user to specify options for challenges or storage.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // check config for domain name
 | 
					        Object.assign(opts, optsOverride, { domains: domains, agreeTos: true });
 | 
				
			||||||
      if (-1 !== (config.tls.servernames || []).indexOf(opts.domain)) {
 | 
					        cb(null, { options: opts, certs: certs });
 | 
				
			||||||
        // TODO how to handle SANs?
 | 
					        cb = null;
 | 
				
			||||||
        // TODO fetch domain-specific email
 | 
					      }
 | 
				
			||||||
        // TODO fetch domain-specific acmeDirectory
 | 
					
 | 
				
			||||||
        // NOTE: you can also change other options such as `challengeType` and `challenge`
 | 
					      var handled = false;
 | 
				
			||||||
        // opts.challengeType = 'http-01';
 | 
					      if (Array.isArray(config.tls.domains)) {
 | 
				
			||||||
        // opts.challenge = require('le-challenge-fs').create({}); // TODO this doesn't actually work yet
 | 
					        handled = config.tls.domains.some(function (dom) {
 | 
				
			||||||
        complete({
 | 
					          if (!nameMatchesDomains(opts.domain, dom.names)) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return dom.modules.some(function (mod) {
 | 
				
			||||||
 | 
					            if (mod.name !== 'acme') {
 | 
				
			||||||
 | 
					              return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            complete(mod, dom.names);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (handled) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (Array.isArray(config.tls.modules)) {
 | 
				
			||||||
 | 
					        handled = config.tls.modules.some(function (mod) {
 | 
				
			||||||
 | 
					          if (mod.name !== 'acme') {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (!nameMatchesDomains(opts.domain, mod.domains)) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          complete(mod, mod.domains);
 | 
				
			||||||
 | 
					          return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (handled) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var defAcmeConf;
 | 
				
			||||||
 | 
					      if (config.tls.acme) {
 | 
				
			||||||
 | 
					        defAcmeConf = config.tls.acme;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        defAcmeConf = {
 | 
				
			||||||
          email: config.tls.email
 | 
					          email: config.tls.email
 | 
				
			||||||
        , agreeTos: true
 | 
					 | 
				
			||||||
        , server: config.tls.acmeDirectoryUrl || le.server
 | 
					        , server: config.tls.acmeDirectoryUrl || le.server
 | 
				
			||||||
        , challengeType: config.tls.challengeType || 'http-01'
 | 
					        , challengeType: config.tls.challengeType || le.challengeType
 | 
				
			||||||
        });
 | 
					        , approvedDomains: config.tls.servernames
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Check config for domain name
 | 
				
			||||||
 | 
					      // TODO: if `approvedDomains` isn't defined check all other modules to see if they can
 | 
				
			||||||
 | 
					      // handle this domain (and what other domains it's grouped with).
 | 
				
			||||||
 | 
					      if (-1 !== (defAcmeConf.approvedDomains || []).indexOf(opts.domain)) {
 | 
				
			||||||
 | 
					        complete(defAcmeConf, defAcmeConf.approvedDomains);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // TODO ask http module (and potentially all other modules) about what domains it can
 | 
					 | 
				
			||||||
      // handle. We can allow any domains that other modules will handle after we terminate TLS.
 | 
					 | 
				
			||||||
      cb(new Error('domain is not allowed'));
 | 
					      cb(new Error('domain is not allowed'));
 | 
				
			||||||
      // if (!modules.http) {
 | 
					 | 
				
			||||||
      //   modules.http = require('./modules/http.js').create(deps, config);
 | 
					 | 
				
			||||||
      // }
 | 
					 | 
				
			||||||
      // modules.http.checkServername(opts.domain).then(function (stuff) {
 | 
					 | 
				
			||||||
      //   if (!stuff || !stuff.domains) {
 | 
					 | 
				
			||||||
      //     // TODO once precheck is implemented we can just let it pass if it passes, yknow?
 | 
					 | 
				
			||||||
      //     cb(new Error('domain is not allowed'));
 | 
					 | 
				
			||||||
      //     return;
 | 
					 | 
				
			||||||
      //   }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      //   complete({
 | 
					 | 
				
			||||||
      //     domain: stuff.domain || stuff.domains[0]
 | 
					 | 
				
			||||||
      //   , domains: stuff.domains
 | 
					 | 
				
			||||||
      //   , email: stuff.email || program.email
 | 
					 | 
				
			||||||
      //   , server: stuff.acmeDirectoryUrl || program.acmeDirectoryUrl
 | 
					 | 
				
			||||||
      //   , challengeType: stuff.challengeType || program.challengeType
 | 
					 | 
				
			||||||
      //   , challenge: stuff.challenge
 | 
					 | 
				
			||||||
      //   });
 | 
					 | 
				
			||||||
      //   return;
 | 
					 | 
				
			||||||
      // }, cb);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  le.tlsOptions = le.tlsOptions || le.httpsOptions;
 | 
					  le.tlsOptions = le.tlsOptions || le.httpsOptions;
 | 
				
			||||||
@ -163,49 +219,27 @@ module.exports.create = function (deps, config, netHandler) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function proxy(socket, opts, mod) {
 | 
					  function proxy(socket, opts, mod) {
 | 
				
			||||||
    var destination = mod.address.split(':');
 | 
					    var newConnOpts = require('../domain-utils').separatePort(mod.address || '');
 | 
				
			||||||
    var connected = false;
 | 
					    newConnOpts.port = newConnOpts.port || mod.port;
 | 
				
			||||||
 | 
					    newConnOpts.host = newConnOpts.host || mod.host || 'localhost';
 | 
				
			||||||
 | 
					    newConnOpts.servername = opts.servername;
 | 
				
			||||||
 | 
					    newConnOpts.data = opts.firstChunk;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var newConn = deps.net.createConnection({
 | 
					    newConnOpts.remoteFamily  = opts.family  || extractSocketProp(socket, 'remoteFamily');
 | 
				
			||||||
        port: destination[1]
 | 
					    newConnOpts.remoteAddress = opts.address || extractSocketProp(socket, 'remoteAddress');
 | 
				
			||||||
      , host: destination[0] || '127.0.0.1'
 | 
					    newConnOpts.remotePort    = opts.port    || extractSocketProp(socket, 'remotePort');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      , servername: opts.servername
 | 
					    deps.proxy(socket, newConnOpts, opts.firstChunk, function () {
 | 
				
			||||||
      , data: opts.firstChunk
 | 
					      // This function is called in the event of a connection error and should decrypt
 | 
				
			||||||
      , remoteFamily:  opts.family  || extractSocketProp(socket, 'remoteFamily')
 | 
					      // the socket so the proxy module can send a 502 HTTP response.
 | 
				
			||||||
      , remoteAddress: opts.address || extractSocketProp(socket, 'remoteAddress')
 | 
					 | 
				
			||||||
      , remotePort:    opts.port    || extractSocketProp(socket, 'remotePort')
 | 
					 | 
				
			||||||
    }, function () {
 | 
					 | 
				
			||||||
      connected = true;
 | 
					 | 
				
			||||||
      if (!opts.hyperPeek) {
 | 
					 | 
				
			||||||
        newConn.write(opts.firstChunk);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      newConn.pipe(socket);
 | 
					 | 
				
			||||||
      socket.pipe(newConn);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Not sure how to effectively report this to the user or client, but we need to listen
 | 
					 | 
				
			||||||
    // for the event to prevent it from crashing us.
 | 
					 | 
				
			||||||
    newConn.on('error', function (err) {
 | 
					 | 
				
			||||||
      if (connected) {
 | 
					 | 
				
			||||||
        console.error('TLS proxy remote error', err);
 | 
					 | 
				
			||||||
        socket.end();
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log('TLS proxy connection error', err);
 | 
					 | 
				
			||||||
      var tlsOpts = localhostCerts.mergeTlsOptions('localhost.daplie.me', {isServer: true});
 | 
					      var tlsOpts = localhostCerts.mergeTlsOptions('localhost.daplie.me', {isServer: true});
 | 
				
			||||||
        var decrypted;
 | 
					 | 
				
			||||||
      if (opts.hyperPeek) {
 | 
					      if (opts.hyperPeek) {
 | 
				
			||||||
          decrypted = new tls.TLSSocket(socket, tlsOpts);
 | 
					        return new tls.TLSSocket(socket, tlsOpts);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
          decrypted = new tls.TLSSocket(wrapSocket(socket, opts), tlsOpts);
 | 
					        return new tls.TLSSocket(wrapSocket(socket, opts), tlsOpts);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        require('../proxy-err-resp').sendBadGateway(decrypted, err, config.debug);
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    socket.on('error', function (err) {
 | 
					    return true;
 | 
				
			||||||
      console.error('TLS proxy client error', err);
 | 
					 | 
				
			||||||
      newConn.end();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function terminate(socket, opts) {
 | 
					  function terminate(socket, opts) {
 | 
				
			||||||
@ -246,31 +280,40 @@ module.exports.create = function (deps, config, netHandler) {
 | 
				
			|||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var handled = (config.tls.modules || []).some(function (mod) {
 | 
					    function checkModule(mod) {
 | 
				
			||||||
      var relevant = mod.domains.some(function (pattern) {
 | 
					 | 
				
			||||||
        return domainMatches(pattern, opts.servername);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      if (!relevant) {
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (mod.name === 'proxy') {
 | 
					      if (mod.name === 'proxy') {
 | 
				
			||||||
        proxy(socket, opts, mod);
 | 
					        return proxy(socket, opts, mod);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else {
 | 
					      if (mod.name !== 'acme') {
 | 
				
			||||||
        console.error('saw unknown TLS module', mod);
 | 
					        console.error('saw unknown TLS module', mod);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var handled = (config.tls.domains || []).some(function (dom) {
 | 
				
			||||||
 | 
					      if (!nameMatchesDomains(opts.servername, dom.names)) {
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return true;
 | 
					      return dom.modules.some(checkModule);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    if (handled) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    handled = (config.tls.modules || []).some(function (mod) {
 | 
				
			||||||
 | 
					      if (!nameMatchesDomains(opts.servername, mod.domains)) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return checkModule(mod);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (handled) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: figure out all of the domains that the other modules intend to handle, and only
 | 
					    // TODO: figure out all of the domains that the other modules intend to handle, and only
 | 
				
			||||||
    // terminate those ones, closing connections for all others.
 | 
					    // terminate those ones, closing connections for all others.
 | 
				
			||||||
    if (!handled) {
 | 
					 | 
				
			||||||
    terminate(socket, opts);
 | 
					    terminate(socket, opts);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    emit: function (type, socket) {
 | 
					    emit: function (type, socket) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										76
									
								
								lib/proxy-conn.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								lib/proxy-conn.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getRespBody(err, debug) {
 | 
				
			||||||
 | 
					  if (debug) {
 | 
				
			||||||
 | 
					    return err.toString();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (err.code === 'ECONNREFUSED') {
 | 
				
			||||||
 | 
					    return 'The connection was refused. Most likely the service being connected to '
 | 
				
			||||||
 | 
					      + 'has stopped running or the configuration is wrong.';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return 'Bad Gateway: ' + err.code;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function sendBadGateway(conn, err, debug) {
 | 
				
			||||||
 | 
					  var body = getRespBody(err, debug);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  conn.write([
 | 
				
			||||||
 | 
					    'HTTP/1.1 502 Bad Gateway'
 | 
				
			||||||
 | 
					  , 'Date: ' + (new Date()).toUTCString()
 | 
				
			||||||
 | 
					  , 'Connection: close'
 | 
				
			||||||
 | 
					  , 'Content-Type: text/html'
 | 
				
			||||||
 | 
					  , 'Content-Length: ' + body.length
 | 
				
			||||||
 | 
					  , ''
 | 
				
			||||||
 | 
					  , body
 | 
				
			||||||
 | 
					  ].join('\r\n'));
 | 
				
			||||||
 | 
					  conn.end();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.getRespBody = getRespBody;
 | 
				
			||||||
 | 
					module.exports.sendBadGateway = sendBadGateway;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.create = function (deps, config) {
 | 
				
			||||||
 | 
					  return function proxy(conn, newConnOpts, firstChunk, decrypt) {
 | 
				
			||||||
 | 
					    var connected = false;
 | 
				
			||||||
 | 
					    var newConn = deps.net.createConnection(newConnOpts, function () {
 | 
				
			||||||
 | 
					      connected = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (firstChunk) {
 | 
				
			||||||
 | 
					        newConn.write(firstChunk);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      newConn.pipe(conn);
 | 
				
			||||||
 | 
					      conn.pipe(newConn);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Listening for this largely to prevent uncaught exceptions.
 | 
				
			||||||
 | 
					    conn.on('error', function (err) {
 | 
				
			||||||
 | 
					      console.log('proxy client error', err);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    newConn.on('error', function (err) {
 | 
				
			||||||
 | 
					      if (connected) {
 | 
				
			||||||
 | 
					        // Not sure how to report this to a user or a client. We can assume that some data
 | 
				
			||||||
 | 
					        // has already been exchanged, so we can't really be sure what we can send in addition
 | 
				
			||||||
 | 
					        // that wouldn't result in a parse error.
 | 
				
			||||||
 | 
					        console.log('proxy remote error', err);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        console.log('proxy connection error', err);
 | 
				
			||||||
 | 
					        if (decrypt) {
 | 
				
			||||||
 | 
					          sendBadGateway(decrypt(conn), err, config.debug);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          sendBadGateway(conn, err, config.debug);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Make sure that once one side closes, no I/O activity will happen on the other side.
 | 
				
			||||||
 | 
					    conn.on('close', function () {
 | 
				
			||||||
 | 
					      newConn.destroy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    newConn.on('close', function () {
 | 
				
			||||||
 | 
					      conn.destroy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,32 +0,0 @@
 | 
				
			|||||||
'use strict';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getRespBody(err, debug) {
 | 
					 | 
				
			||||||
  if (debug) {
 | 
					 | 
				
			||||||
    return err.toString();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (err.code === 'ECONNREFUSED') {
 | 
					 | 
				
			||||||
    return 'The connection was refused. Most likely the service being connected to '
 | 
					 | 
				
			||||||
      + 'has stopped running or the configuration is wrong.';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return 'Bad Gateway: ' + err.code;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function sendBadGateway(conn, err, debug) {
 | 
					 | 
				
			||||||
  var body = getRespBody(err, debug);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  conn.write([
 | 
					 | 
				
			||||||
    'HTTP/1.1 502 Bad Gateway'
 | 
					 | 
				
			||||||
  , 'Date: ' + (new Date()).toUTCString()
 | 
					 | 
				
			||||||
  , 'Connection: close'
 | 
					 | 
				
			||||||
  , 'Content-Type: text/html'
 | 
					 | 
				
			||||||
  , 'Content-Length: ' + body.length
 | 
					 | 
				
			||||||
  , ''
 | 
					 | 
				
			||||||
  , body
 | 
					 | 
				
			||||||
  ].join('\r\n'));
 | 
					 | 
				
			||||||
  conn.end();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports.getRespBody = getRespBody;
 | 
					 | 
				
			||||||
module.exports.sendBadGateway = sendBadGateway;
 | 
					 | 
				
			||||||
							
								
								
									
										144
									
								
								lib/tunnel.js
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								lib/tunnel.js
									
									
									
									
									
								
							@ -1,144 +0,0 @@
 | 
				
			|||||||
'use strict';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports.create = function (opts, servers) {
 | 
					 | 
				
			||||||
  // servers = { plainserver, server }
 | 
					 | 
				
			||||||
  var Oauth3 = require('oauth3-cli');
 | 
					 | 
				
			||||||
  var Tunnel = require('daplie-tunnel').create({
 | 
					 | 
				
			||||||
    Oauth3: Oauth3
 | 
					 | 
				
			||||||
  , PromiseA: opts.PromiseA
 | 
					 | 
				
			||||||
  , CLI: {
 | 
					 | 
				
			||||||
      init: function (rs, ws/*, state, options*/) {
 | 
					 | 
				
			||||||
        // noop
 | 
					 | 
				
			||||||
        return ws;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }).Tunnel;
 | 
					 | 
				
			||||||
  var stunnel = require('stunnel');
 | 
					 | 
				
			||||||
  var killcount = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
  var Dup = {
 | 
					 | 
				
			||||||
    write: function (chunk, encoding, cb) {
 | 
					 | 
				
			||||||
      this.__my_socket.push(chunk, encoding);
 | 
					 | 
				
			||||||
      cb();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  , read: function (size) {
 | 
					 | 
				
			||||||
      var x = this.__my_socket.read(size);
 | 
					 | 
				
			||||||
      if (x) { this.push(x); }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  , setTimeout: function () {
 | 
					 | 
				
			||||||
      console.log('TODO implement setTimeout on Duplex');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var httpServer = require('http').createServer(function (req, res) {
 | 
					 | 
				
			||||||
    console.log('req.socket.encrypted', req.socket.encrypted);
 | 
					 | 
				
			||||||
    res.end('Hello, tunneled World!');
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var tlsServer = require('tls').createServer(opts.httpsOptions, function (tlsSocket) {
 | 
					 | 
				
			||||||
    console.log('tls connection');
 | 
					 | 
				
			||||||
    // things get a little messed up here
 | 
					 | 
				
			||||||
    httpServer.emit('connection', tlsSocket);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // try again
 | 
					 | 
				
			||||||
    //servers.server.emit('connection', tlsSocket);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  process.on('SIGINT', function () {
 | 
					 | 
				
			||||||
    killcount += 1;
 | 
					 | 
				
			||||||
    console.log('[quit] closing http and https servers');
 | 
					 | 
				
			||||||
    if (killcount >= 3) {
 | 
					 | 
				
			||||||
      process.exit(1);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (servers.server) {
 | 
					 | 
				
			||||||
      servers.server.close();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (servers.insecureServer) {
 | 
					 | 
				
			||||||
      servers.insecureServer.close();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return Tunnel.token({
 | 
					 | 
				
			||||||
    refreshToken: opts.refreshToken
 | 
					 | 
				
			||||||
  , email: opts.email
 | 
					 | 
				
			||||||
  , domains: opts.sites.map(function (site) {
 | 
					 | 
				
			||||||
      return site.name;
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  , device: { hostname: opts.devicename || opts.device }
 | 
					 | 
				
			||||||
  }).then(function (result) {
 | 
					 | 
				
			||||||
    // { jwt, tunnelUrl }
 | 
					 | 
				
			||||||
    var locals = [];
 | 
					 | 
				
			||||||
    opts.sites.map(function (site) {
 | 
					 | 
				
			||||||
      locals.push({
 | 
					 | 
				
			||||||
        protocol: 'https'
 | 
					 | 
				
			||||||
      , hostname: site.name
 | 
					 | 
				
			||||||
      , port: opts.port
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      locals.push({
 | 
					 | 
				
			||||||
        protocol: 'http'
 | 
					 | 
				
			||||||
      , hostname: site.name
 | 
					 | 
				
			||||||
      , port: opts.insecurePort || opts.port
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    return stunnel.connect({
 | 
					 | 
				
			||||||
      token: result.jwt
 | 
					 | 
				
			||||||
    , stunneld: result.tunnelUrl
 | 
					 | 
				
			||||||
      // XXX TODO BUG // this is just for testing
 | 
					 | 
				
			||||||
    , insecure: /*opts.insecure*/ true
 | 
					 | 
				
			||||||
    , locals: locals
 | 
					 | 
				
			||||||
      // a simple passthru is proving to not be so simple
 | 
					 | 
				
			||||||
    , net: require('net') /*
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        createConnection: function (info, cb) {
 | 
					 | 
				
			||||||
          // data is the hello packet / first chunk
 | 
					 | 
				
			||||||
          // info = { data, servername, port, host, remoteAddress: { family, address, port } }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          var myDuplex = new (require('stream').Duplex)();
 | 
					 | 
				
			||||||
          var myDuplex2 = new (require('stream').Duplex)();
 | 
					 | 
				
			||||||
          // duplex = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          myDuplex2.__my_socket = myDuplex;
 | 
					 | 
				
			||||||
          myDuplex.__my_socket = myDuplex2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          myDuplex2._write = Dup.write;
 | 
					 | 
				
			||||||
          myDuplex2._read = Dup.read;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          myDuplex._write = Dup.write;
 | 
					 | 
				
			||||||
          myDuplex._read = Dup.read;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          myDuplex.remoteFamily = info.remoteFamily;
 | 
					 | 
				
			||||||
          myDuplex.remoteAddress = info.remoteAddress;
 | 
					 | 
				
			||||||
          myDuplex.remotePort = info.remotePort;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          // socket.local{Family,Address,Port}
 | 
					 | 
				
			||||||
          myDuplex.localFamily = 'IPv4';
 | 
					 | 
				
			||||||
          myDuplex.localAddress = '127.0.01';
 | 
					 | 
				
			||||||
          myDuplex.localPort = info.port;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          myDuplex.setTimeout = Dup.setTimeout;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          // this doesn't seem to work so well
 | 
					 | 
				
			||||||
          //servers.server.emit('connection', myDuplex);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          // try a little more manual wrapping / unwrapping
 | 
					 | 
				
			||||||
          var firstByte = info.data[0];
 | 
					 | 
				
			||||||
          if (firstByte < 32 || firstByte >= 127) {
 | 
					 | 
				
			||||||
            tlsServer.emit('connection', myDuplex);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          else {
 | 
					 | 
				
			||||||
            httpServer.emit('connection', myDuplex);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (cb) {
 | 
					 | 
				
			||||||
            process.nextTick(cb);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          return myDuplex2;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      //*/
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -9,5 +9,7 @@ process.on('message', function (conf) {
 | 
				
			|||||||
    // HTTP proxying connection creation is not something we currently control.
 | 
					    // HTTP proxying connection creation is not something we currently control.
 | 
				
			||||||
  , net: require('net')
 | 
					  , net: require('net')
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					  deps.proxy = require('./proxy-conn').create(deps, conf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  require('./goldilocks.js').create(deps, conf);
 | 
					  require('./goldilocks.js').create(deps, conf);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										13
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								package.json
									
									
									
									
									
								
							@ -41,30 +41,27 @@
 | 
				
			|||||||
    "bluebird": "^3.4.6",
 | 
					    "bluebird": "^3.4.6",
 | 
				
			||||||
    "body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1",
 | 
					    "body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1",
 | 
				
			||||||
    "commander": "^2.9.0",
 | 
					    "commander": "^2.9.0",
 | 
				
			||||||
    "daplie-tunnel": "git+https://git.daplie.com/Daplie/daplie-cli-tunnel.git#master",
 | 
					    "dns-suite": "git+https://git@git.daplie.com:Daplie/dns-suite#v1",
 | 
				
			||||||
    "ddns-cli": "git+https://git.daplie.com/Daplie/node-ddns-client.git#master",
 | 
					 | 
				
			||||||
    "express": "git+https://github.com/expressjs/express.git#4.x",
 | 
					    "express": "git+https://github.com/expressjs/express.git#4.x",
 | 
				
			||||||
    "finalhandler": "^0.4.0",
 | 
					    "finalhandler": "^0.4.0",
 | 
				
			||||||
    "greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master",
 | 
					    "greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master",
 | 
				
			||||||
    "greenlock-express": "git+https://git.daplie.com/Daplie/greenlock-express.git#master",
 | 
					 | 
				
			||||||
    "http-proxy": "^1.16.2",
 | 
					 | 
				
			||||||
    "httpolyglot": "^0.1.1",
 | 
					 | 
				
			||||||
    "ipaddr.js": "git+https://github.com/whitequark/ipaddr.js.git#v1.3.0",
 | 
					    "ipaddr.js": "git+https://github.com/whitequark/ipaddr.js.git#v1.3.0",
 | 
				
			||||||
    "ipify": "^1.1.0",
 | 
					    "ipify": "^1.1.0",
 | 
				
			||||||
    "js-yaml": "^3.8.3",
 | 
					    "js-yaml": "^3.8.3",
 | 
				
			||||||
 | 
					    "jsonwebtoken": "^7.4.0",
 | 
				
			||||||
    "le-challenge-ddns": "git+https://git.daplie.com/Daplie/le-challenge-ddns.git#master",
 | 
					    "le-challenge-ddns": "git+https://git.daplie.com/Daplie/le-challenge-ddns.git#master",
 | 
				
			||||||
    "le-challenge-fs": "git+https://git.daplie.com/Daplie/le-challenge-webroot.git#master",
 | 
					    "le-challenge-fs": "git+https://git.daplie.com/Daplie/le-challenge-webroot.git#master",
 | 
				
			||||||
    "le-challenge-sni": "^2.0.1",
 | 
					    "le-challenge-sni": "^2.0.1",
 | 
				
			||||||
    "livereload": "^0.6.0",
 | 
					    "le-store-certbot": "git+https://git.daplie.com/Daplie/le-store-certbot.git#master",
 | 
				
			||||||
    "localhost.daplie.me-certificates": "^1.3.0",
 | 
					    "localhost.daplie.me-certificates": "^1.3.0",
 | 
				
			||||||
    "minimist": "^1.1.1",
 | 
					 | 
				
			||||||
    "oauth3-cli": "git+https://git.daplie.com/OAuth3/oauth3-cli.git#master",
 | 
					 | 
				
			||||||
    "recase": "git+https://git.daplie.com/coolaj86/recase-js.git#v1.0.4",
 | 
					    "recase": "git+https://git.daplie.com/coolaj86/recase-js.git#v1.0.4",
 | 
				
			||||||
    "redirect-https": "^1.1.0",
 | 
					    "redirect-https": "^1.1.0",
 | 
				
			||||||
 | 
					    "request": "^2.81.0",
 | 
				
			||||||
    "scmp": "git+https://github.com/freewil/scmp.git#1.x",
 | 
					    "scmp": "git+https://github.com/freewil/scmp.git#1.x",
 | 
				
			||||||
    "serve-index": "^1.7.0",
 | 
					    "serve-index": "^1.7.0",
 | 
				
			||||||
    "serve-static": "^1.10.0",
 | 
					    "serve-static": "^1.10.0",
 | 
				
			||||||
    "server-destroy": "^1.0.1",
 | 
					    "server-destroy": "^1.0.1",
 | 
				
			||||||
 | 
					    "sni": "^1.0.0",
 | 
				
			||||||
    "socket-pair": "^1.0.0",
 | 
					    "socket-pair": "^1.0.0",
 | 
				
			||||||
    "stream-pair": "^1.0.3",
 | 
					    "stream-pair": "^1.0.3",
 | 
				
			||||||
    "stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#v1"
 | 
					    "stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#v1"
 | 
				
			||||||
 | 
				
			|||||||
@ -1,23 +0,0 @@
 | 
				
			|||||||
'use strict';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var https = require('httpolyglot');
 | 
					 | 
				
			||||||
var httpsOptions = require('localhost.daplie.me-certificates').merge({});
 | 
					 | 
				
			||||||
var httpsPort = 8443;
 | 
					 | 
				
			||||||
var redirectApp = require('redirect-https')({
 | 
					 | 
				
			||||||
  port: httpsPort
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var server = https.createServer(httpsOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
server.on('request', function (req, res) {
 | 
					 | 
				
			||||||
  if (!req.socket.encrypted) {
 | 
					 | 
				
			||||||
    redirectApp(req, res);
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  res.end("Hello, Encrypted World!");
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
server.listen(httpsPort, function () {
 | 
					 | 
				
			||||||
  console.log('https://' + 'localhost.daplie.me' + (443 === httpsPort ? ':' : ':' + httpsPort));
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user