Compare commits
	
		
			51 Commits
		
	
	
		
			installer-
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | dccebfe16b | ||
|  | a87e69e332 | ||
| 8fb910ddf9 | |||
| 158892f88c | |||
| e462978154 | |||
| 3a7e4cd2ab | |||
| 4f16f92208 | |||
|  | 34dff39358 | ||
|  | 136431d493 | ||
|  | 4b9e07842d | ||
|  | 43105ba266 | ||
|  | add6745475 | ||
|  | 2969eb3247 | ||
|  | 2c6e5cfa46 | ||
|  | 037c4df6e0 | ||
|  | dd7bc74dad | ||
|  | 12c2fd1819 | ||
|  | a8aedcbc31 | ||
|  | ea010427e8 | ||
|  | d8cc8fe8e6 | ||
|  | 11f2d37044 | ||
|  | 40bd1d9cc6 | ||
|  | 2277b22d9d | ||
|  | b6b9d5f2f3 | ||
|  | 0a233cfcf0 | ||
|  | dd035219a3 | ||
|  | 57f97eebdb | ||
|  | 0024d51289 | ||
|  | 1382b8b4e2 | ||
|  | 828712bf12 | ||
|  | ac36a35c19 | ||
|  | 8c34316979 | ||
| 65920f8fce | |||
| 75d2680830 | |||
| 0b464cab36 | |||
| 35016cd124 | |||
|  | 4b2e6b1600 | ||
|  | e071b8c3eb | ||
|  | 307d81690d | ||
|  | 2f06c7fbdc | ||
|  | b324016056 | ||
|  | eda766e48c | ||
|  | a27252eb77 | ||
|  | 7423d6065f | ||
|  | 9ec642237c | ||
|  | 16589e65f6 | ||
|  | 9a63f30bf2 | ||
|  | c697008573 | ||
|  | c132861cab | ||
|  | c637671c78 | ||
|  | 5534ba2ef1 | 
| @ -1,3 +1,10 @@ | ||||
| v1.1.5 - Implemented dns-01 ACME challenges | ||||
| 
 | ||||
| v1.1.4 - Improved responsiveness to config updates | ||||
|   * changed which TCP/UDP ports are bound to on config update | ||||
|   * update tunnel server settings on config update | ||||
|   * update socks5 setting on config update | ||||
| 
 | ||||
| v1.1.3 - Better late than never... here's some stuff we've got | ||||
|   * fixed (probably) network settings not being readable | ||||
|   * supports timeouts in loopback check | ||||
|  | ||||
							
								
								
									
										32
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								README.md
									
									
									
									
									
								
							| @ -23,13 +23,13 @@ Install Standalone | ||||
| ### curl | bash | ||||
| 
 | ||||
| ```bash | ||||
| curl -fsSL https://git.daplie.com/Daplie/goldilocks.js/raw/v1.1/installer/get.sh | bash | ||||
| curl -fsSL https://git.coolaj86.com/coolaj86/goldilocks.js/raw/v1.1/installer/get.sh | bash | ||||
| ``` | ||||
| 
 | ||||
| ### git | ||||
| 
 | ||||
| ```bash | ||||
| git clone https://git.daplie.com/Daplie/goldilocks.js | ||||
| git clone https://git.coolaj86.com/coolaj86/goldilocks.js | ||||
| pushd goldilocks.js | ||||
| git checkout v1.1 | ||||
| bash installer/install.sh | ||||
| @ -39,10 +39,10 @@ bash installer/install.sh | ||||
| 
 | ||||
| ```bash | ||||
| # v1 in git (unauthenticated) | ||||
| npm install -g git+https://git@git.daplie.com:Daplie/goldilocks.js#v1 | ||||
| npm install -g git+https://git@git.coolaj86.com:coolaj86/goldilocks.js#v1 | ||||
| 
 | ||||
| # v1 in git (via ssh) | ||||
| npm install -g git+ssh://git@git.daplie.com:Daplie/goldilocks.js#v1 | ||||
| npm install -g git+ssh://git@git.coolaj86.com:coolaj86/goldilocks.js#v1 | ||||
| 
 | ||||
| # v1 in npm | ||||
| npm install -g goldilocks@v1 | ||||
| @ -50,8 +50,16 @@ npm install -g goldilocks@v1 | ||||
| 
 | ||||
| ### Uninstall | ||||
| 
 | ||||
| Remove goldilocks and services: | ||||
| 
 | ||||
| ``` | ||||
| rm -rf /srv/goldilocks/ /var/goldilocks/ /etc/goldilocks/ /opt/goldilocks/ /var/log/goldilocks/ /etc/tmpfiles.d/goldilocks.conf /etc/systemd/system/goldilocks.service /etc/ssl/goldilocks | ||||
| rm -rf /opt/goldilocks/ /srv/goldilocks/ /var/goldilocks/ /var/log/goldilocks/ /etc/tmpfiles.d/goldilocks.conf /etc/systemd/system/goldilocks.service | ||||
| ``` | ||||
| 
 | ||||
| Remove config as well | ||||
| 
 | ||||
| ``` | ||||
| rm -rf /etc/goldilocks/ /etc/ssl/goldilocks | ||||
| ``` | ||||
| 
 | ||||
| Usage | ||||
| @ -73,7 +81,7 @@ We have service support for | ||||
| * launchd (macOS) | ||||
| 
 | ||||
| ```bash | ||||
| curl https://git.daplie.com/Daplie/goldilocks.js/raw/master/install.sh | bash | ||||
| curl https://git.coolaj86.com/coolaj86/goldilocks.js/raw/master/install.sh | bash | ||||
| ``` | ||||
| 
 | ||||
| Modules & Configuration | ||||
| @ -297,6 +305,12 @@ tls: | ||||
|       challenge_type: 'http-01' | ||||
| ``` | ||||
| 
 | ||||
| **NOTE:** If you specify `dns-01` as the challenge type there must also be a | ||||
| [DDNS module](#ddns) defined for all of the relevant domains (though not all | ||||
| domains handled by a single TLS module need to be handled by the same DDNS | ||||
| module). The DDNS module provides all of the information needed to actually | ||||
| set the DNS records needed to verify ownership. | ||||
| 
 | ||||
| ### tcp | ||||
| 
 | ||||
| The tcp system handles both *raw* and *tls-terminated* tcp network traffic | ||||
| @ -398,7 +412,7 @@ sni = vpn.example.com | ||||
| connect = example.com:443 | ||||
| ``` | ||||
| 
 | ||||
| 3) [Use stunnel.js](https://git.daplie.com/Daplie/node-tunnel-client) as described in the "tunnel_server" section below. | ||||
| 3) [Use stunnel.js](https://git.coolaj86.com/coolaj86/tunnel-client.js) as described in the "tunnel_server" section below. | ||||
| 
 | ||||
| ### tcp.forward | ||||
| 
 | ||||
| @ -602,7 +616,7 @@ mdns: | ||||
| You can discover goldilocks with `mdig`. | ||||
| 
 | ||||
| ``` | ||||
| npm install -g git+https://git.daplie.com/Daplie/mdig.git | ||||
| npm install -g git+https://git.coolaj86.com/coolaj86/mdig.js.git | ||||
| 
 | ||||
| mdig _cloud._tcp.local | ||||
| ``` | ||||
| @ -631,7 +645,7 @@ TODO | ||||
| * [ ] http - redirect based on domain name (not just path) | ||||
| * [ ] tcp - bind should be able to specify localhost, uniquelocal, private, or ip | ||||
| * [ ] tcp - if destination host is omitted default to localhost, if dst port is missing, default to src | ||||
| * [ ] sys - `curl https://daplie.me/goldilocks | bash -s example.com` | ||||
| * [ ] sys - `curl https://coolaj86.com/goldilocks | bash -s example.com` | ||||
| * [ ] oauth3 - `example.com/.well-known/domains@oauth3.org/directives.json` | ||||
| * [ ] oauth3 - commandline questionnaire | ||||
| * [x] modules - use consistent conventions (i.e. address vs host + port) | ||||
|  | ||||
| @ -7,7 +7,7 @@ my_ver=master | ||||
| my_tmp=$(mktemp -d) | ||||
| 
 | ||||
| mkdir -p $my_tmp/opt/$my_name/lib/node_modules/$my_name | ||||
| git clone https://git.daplie.com/Daplie/goldilocks.js.git $my_tmp/opt/$my_name/lib/node_modules/$my_name | ||||
| git clone https://git.coolaj86.com/coolaj86/goldilocks.js.git $my_tmp/opt/$my_name/lib/node_modules/$my_name | ||||
| 
 | ||||
| echo "Installing to $my_tmp (will be moved after install)" | ||||
| pushd $my_tmp/opt/$my_name/lib/node_modules/$my_name | ||||
|  | ||||
| @ -5,7 +5,7 @@ | ||||
| #                             # | ||||
| ############################### | ||||
| 
 | ||||
| # See https://git.daplie.com/Daplie/daplie-snippets/blob/master/bash/http-get.sh | ||||
| # See https://git.coolaj86.com/coolaj86/snippets/blob/master/bash/http-get.sh | ||||
| 
 | ||||
| _h_http_get="" | ||||
| _h_http_opts="" | ||||
|  | ||||
| @ -11,11 +11,13 @@ sed "s/MY_USER/$my_user/g" "$my_app_dist/$my_app_systemd_service" > "$my_app_dis | ||||
| sed "s/MY_GROUP/$my_group/g" "$my_app_dist/$my_app_systemd_service.2" > "$my_app_dist/$my_app_systemd_service" | ||||
| rm "$my_app_dist/$my_app_systemd_service.2" | ||||
| safe_copy_config "$my_app_dist/$my_app_systemd_service" "$my_root/$my_app_systemd_service" | ||||
| $sudo_cmd chown root:root "$my_root/$my_app_systemd_service" | ||||
| 
 | ||||
| sed "s/MY_USER/$my_user/g" "$my_app_dist/$my_app_systemd_tmpfiles" > "$my_app_dist/$my_app_systemd_tmpfiles.2" | ||||
| sed "s/MY_GROUP/$my_group/g" "$my_app_dist/$my_app_systemd_tmpfiles.2" > "$my_app_dist/$my_app_systemd_tmpfiles" | ||||
| rm "$my_app_dist/$my_app_systemd_tmpfiles.2" | ||||
| safe_copy_config "$my_app_dist/$my_app_systemd_tmpfiles" "$my_root/$my_app_systemd_tmpfiles" | ||||
| $sudo_cmd chown root:root "$my_root/$my_app_systemd_tmpfiles" | ||||
| 
 | ||||
| $sudo_cmd systemctl stop "${my_app_name}.service" >/dev/null 2>/dev/null || true | ||||
| $sudo_cmd systemctl daemon-reload | ||||
|  | ||||
| @ -7,10 +7,10 @@ set -u | ||||
| ### IMPORTANT ### | ||||
| ###  VERSION  ### | ||||
| my_name=goldilocks | ||||
| my_app_pkg_name=com.daplie.goldilocks.web | ||||
| my_app_pkg_name=com.coolaj86.goldilocks.web | ||||
| my_app_ver="v1.1" | ||||
| my_azp_oauth3_ver="v1.2" | ||||
| export NODE_VERSION="v8.9.0" | ||||
| my_azp_oauth3_ver="v1.2.3" | ||||
| export NODE_VERSION="v8.9.3" | ||||
| 
 | ||||
| if [ -z "${my_tmp-}" ]; then | ||||
|   my_tmp="$(mktemp -d)" | ||||
| @ -29,7 +29,7 @@ my_npm="$NPM_CONFIG_PREFIX/bin/npm" | ||||
| 
 | ||||
| 
 | ||||
| my_app_dist=$my_tmp/opt/$my_name/lib/node_modules/$my_name/dist | ||||
| installer_base="https://git.daplie.com/Daplie/goldilocks.js/raw/$my_app_ver" | ||||
| installer_base="https://git.coolaj86.com/coolaj86/goldilocks.js/raw/$my_app_ver" | ||||
| 
 | ||||
| # Backwards compat | ||||
| # some scripts still use the old names | ||||
| @ -71,13 +71,13 @@ pushd $my_tmp/opt/$my_name/lib/node_modules/$my_name | ||||
|   $my_npm install | ||||
| popd | ||||
| pushd $my_tmp/opt/$my_name/lib/node_modules/$my_name/packages/assets | ||||
|   OAUTH3_GIT_URL="https://git.daplie.com/Oauth3/oauth3.js.git" | ||||
|   OAUTH3_GIT_URL="https://git.oauth3.org/OAuth3/oauth3.js.git" | ||||
|   git clone ${OAUTH3_GIT_URL} oauth3.org || true | ||||
|   ln -s oauth3.org org.oauth3 | ||||
|   pushd oauth3.org | ||||
|     git remote set-url origin ${OAUTH3_GIT_URL} | ||||
|     git checkout $my_azp_oauth3_ver | ||||
|     git pull | ||||
|     #git pull | ||||
|   popd | ||||
| 
 | ||||
|   mkdir -p jquery.com | ||||
| @ -119,6 +119,8 @@ set -e | ||||
| source ./installer/my-user-my-group.sh | ||||
| echo "User $my_user Group $my_group" | ||||
| 
 | ||||
| source ./installer/install-system-service.sh | ||||
| 
 | ||||
| $sudo_cmd chown -R $my_user:$my_group $my_tmp/* | ||||
| $sudo_cmd chown root:root $my_tmp/* | ||||
| $sudo_cmd chown root:root $my_tmp | ||||
| @ -126,7 +128,6 @@ $sudo_cmd chmod 0755 $my_tmp | ||||
| # don't change permissions on /, /etc, etc | ||||
| $sudo_cmd rsync -a --ignore-existing $my_tmp/ $my_root/ | ||||
| $sudo_cmd rsync -a --ignore-existing $my_app_dist/etc/$my_name/$my_name.yml $my_root/etc/$my_name/$my_name.yml | ||||
| source ./installer/install-system-service.sh | ||||
| 
 | ||||
| # Change to admin perms | ||||
| $sudo_cmd chown -R $my_user:$my_group $my_root/opt/$my_name | ||||
|  | ||||
| @ -174,6 +174,14 @@ var mdnsSchema = { | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| var tunnelSvrSchema = { | ||||
|   type: 'object' | ||||
| , properties: { | ||||
|     servernames: { type: 'array', items: { type: 'string' }} | ||||
|   , secret:      { type: 'string' } | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| var ddnsSchema = { | ||||
|   type: 'object' | ||||
| , properties: { | ||||
| @ -223,6 +231,7 @@ var mainSchema = { | ||||
|   , ddns:   ddnsSchema | ||||
|   , socks5: socks5Schema | ||||
|   , device: deviceSchema | ||||
|   , tunnel_server: tunnelSvrSchema | ||||
|   } | ||||
| , additionalProperties: false | ||||
| }; | ||||
|  | ||||
							
								
								
									
										122
									
								
								lib/ddns/challenge-responder.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								lib/ddns/challenge-responder.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,122 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| // Much of this file was based on the `le-challenge-ddns` library (which we are not using
 | ||||
| // here because it's method of setting records requires things we don't really want).
 | ||||
| module.exports.create = function (deps, conf, utils) { | ||||
| 
 | ||||
|   function getReleventSessionId(domain) { | ||||
|     var sessId; | ||||
| 
 | ||||
|     utils.iterateAllModules(function (mod, domainList) { | ||||
|       // We return a truthy value in these cases because of the way the iterate function
 | ||||
|       // handles modules grouped by domain. By returning true we are saying these domains
 | ||||
|       // are "handled" and so if there are multiple modules we won't be given the rest.
 | ||||
|       if (sessId) { return true; } | ||||
|       if (domainList.indexOf(domain) < 0) { return true; } | ||||
| 
 | ||||
|       // But if the domains are relevant but we don't know how to handle the module we
 | ||||
|       // return false to allow us to look at any other modules that might exist here.
 | ||||
|       if (mod.type !== 'dns@oauth3.org')  { return false; } | ||||
| 
 | ||||
|       sessId = mod.tokenId || mod.token_id; | ||||
|       return true; | ||||
|     }); | ||||
| 
 | ||||
|     return sessId; | ||||
|   } | ||||
| 
 | ||||
|   function get(args, domain, challenge, done) { | ||||
|     done(new Error("Challenge.get() does not need an implementation for dns-01. (did you mean Challenge.loopback?)")); | ||||
|   } | ||||
|   // same as get, but external
 | ||||
|   function loopback(args, domain, challenge, done) { | ||||
|     var challengeDomain = (args.test || '') + args.acmeChallengeDns + domain; | ||||
|     require('dns').resolveTxt(challengeDomain, done); | ||||
|   } | ||||
| 
 | ||||
|   var activeChallenges = {}; | ||||
|   async function removeAsync(args, domain) { | ||||
|     var data = activeChallenges[domain]; | ||||
|     if (!data) { | ||||
|       console.warn(new Error('cannot remove DNS challenge for ' + domain + ': already removed')); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     var session = await utils.getSession(data.sessId); | ||||
|     var directives = await deps.OAUTH3.discover(session.token.aud); | ||||
|     var apiOpts = { | ||||
|       api: 'dns.unset' | ||||
|     , session: session | ||||
|     , type: 'TXT' | ||||
|     , value: data.keyAuthDigest | ||||
|     }; | ||||
|     await deps.OAUTH3.api(directives.api, Object.assign({}, apiOpts, data.splitDomain)); | ||||
| 
 | ||||
|     delete activeChallenges[domain]; | ||||
|   } | ||||
|   async function setAsync(args, domain, challenge, keyAuth) { | ||||
|     if (activeChallenges[domain]) { | ||||
|       await removeAsync(args, domain, challenge); | ||||
|     } | ||||
| 
 | ||||
|     var sessId = getReleventSessionId(domain); | ||||
|     if (!sessId) { | ||||
|       throw new Error('no DDNS module handles the domain ' + domain); | ||||
|     } | ||||
|     var session = await utils.getSession(sessId); | ||||
|     var directives = await deps.OAUTH3.discover(session.token.aud); | ||||
| 
 | ||||
|     // I'm not sure what role challenge is supposed to play since even in the library
 | ||||
|     // this code is based on it was never used, but check for it anyway because ...
 | ||||
|     if (!challenge || keyAuth) { | ||||
|       console.warn(new Error('DDNS challenge missing challenge or keyAuth')); | ||||
|     } | ||||
|     var keyAuthDigest = require('crypto').createHash('sha256').update(keyAuth || '').digest('base64') | ||||
|       .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); | ||||
| 
 | ||||
|     var challengeDomain = (args.test || '') + args.acmeChallengeDns + domain; | ||||
|     var splitDomain = (await utils.splitDomains(directives.api, [challengeDomain]))[0]; | ||||
| 
 | ||||
|     var apiOpts = { | ||||
|       api: 'dns.set' | ||||
|     , session: session | ||||
|     , type: 'TXT' | ||||
|     , value: keyAuthDigest | ||||
|     , ttl: args.ttl || 0 | ||||
|     }; | ||||
|     await deps.OAUTH3.api(directives.api, Object.assign({}, apiOpts, splitDomain)); | ||||
| 
 | ||||
|     activeChallenges[domain] = { | ||||
|       sessId | ||||
|     , keyAuthDigest | ||||
|     , splitDomain | ||||
|     }; | ||||
| 
 | ||||
|     return new Promise(res => setTimeout(res, 1000)); | ||||
|   } | ||||
| 
 | ||||
|   // It might be slightly easier to use arguments and apply, but the library that will use
 | ||||
|   // this function counts the arguments we expect.
 | ||||
|   function set(a, b, c, d, done) { | ||||
|     setAsync(a, b, c, d).then(result => done(null, result), done); | ||||
|   } | ||||
|   function remove(a, b, c, done) { | ||||
|     removeAsync(a, b, c).then(result => done(null, result), done); | ||||
|   } | ||||
| 
 | ||||
|   function getOptions() { | ||||
|     return { | ||||
|       oauth3: 'oauth3.org' | ||||
|     , debug: conf.debug | ||||
|     , acmeChallengeDns: '_acme-challenge.' | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     getOptions | ||||
|   , set | ||||
|   , get | ||||
|   , remove | ||||
|   , loopback | ||||
|   }; | ||||
| }; | ||||
| @ -1,6 +1,6 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports.create = function (deps, conf) { | ||||
| module.exports.create = function (deps, conf, utils) { | ||||
|   function dnsType(addr) { | ||||
|     if (/^\d+\.\d+\.\d+\.\d+$/.test(addr)) { | ||||
|       return 'A'; | ||||
| @ -10,62 +10,6 @@ module.exports.create = function (deps, conf) { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   var tldCache = {}; | ||||
|   async function getTlds(provider) { | ||||
|     async function updateCache() { | ||||
|       var reqObj = { | ||||
|         url: deps.OAUTH3.url.normalize(provider)+'/api/com.daplie.domains/prices' | ||||
|       , method: 'GET' | ||||
|       , json: true | ||||
|       }; | ||||
| 
 | ||||
|       var resp = await deps.OAUTH3.request(reqObj); | ||||
|       var tldObj = {}; | ||||
|       resp.data.forEach(function (tldInfo) { | ||||
|         if (tldInfo.enabled) { | ||||
|           tldObj[tldInfo.tld] = true; | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       tldCache[provider] = { | ||||
|         time: Date.now() | ||||
|       , tlds: tldObj | ||||
|       }; | ||||
|       return tldObj; | ||||
|     } | ||||
| 
 | ||||
|     // If we've never cached the results we need to return the promise that will fetch the recult,
 | ||||
|     // otherwise we can return the cached value. If the cached value has "expired", we can still
 | ||||
|     // return the cached value we just want to update the cache in parellel (making sure we only
 | ||||
|     // update once).
 | ||||
|     if (!tldCache[provider]) { | ||||
|       return updateCache(); | ||||
|     } | ||||
|     if (!tldCache[provider].updating && Date.now() - tldCache[provider].time > 24*60*60*1000) { | ||||
|       tldCache[provider].updating = true; | ||||
|       updateCache(); | ||||
|     } | ||||
| 
 | ||||
|     return tldCache[provider].tlds; | ||||
|   } | ||||
| 
 | ||||
|   async function splitDomains(provider, domains) { | ||||
|     var tlds = await getTlds(provider); | ||||
|     return domains.map(function (domain) { | ||||
|       var split = domain.split('.'); | ||||
|       var tldSegCnt = tlds[split.slice(-2).join('.')] ? 2 : 1; | ||||
| 
 | ||||
|       // Currently assuming that the sld can't contain dots, and that the tld can have at
 | ||||
|       // most one dot. Not 100% sure this is a valid assumption, but exceptions should be
 | ||||
|       // rare even if the assumption isn't valid.
 | ||||
|       return { | ||||
|         tld: split.slice(-tldSegCnt).join('.') | ||||
|       , sld: split.slice(-tldSegCnt-1, -tldSegCnt).join('.') | ||||
|       , sub: split.slice(0, -tldSegCnt-1).join('.') | ||||
|       }; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   async function setDeviceAddress(session, addr, domains) { | ||||
|     var directives = await deps.OAUTH3.discover(session.token.aud); | ||||
| 
 | ||||
| @ -111,7 +55,7 @@ module.exports.create = function (deps, conf) { | ||||
|       return goodAddrDomains.indexOf(domain) < 0; | ||||
|     }); | ||||
| 
 | ||||
|     var oldDns = await splitDomains(directives.api, badAddrDomains); | ||||
|     var oldDns = await utils.splitDomains(directives.api, badAddrDomains); | ||||
|     var common = { | ||||
|       api: 'devices.detach' | ||||
|     , session: session | ||||
| @ -124,7 +68,7 @@ module.exports.create = function (deps, conf) { | ||||
|       console.log('removed bad DNS records for ' + badAddrDomains.join(', ')); | ||||
|     } | ||||
| 
 | ||||
|     var newDns = await splitDomains(directives.api, requiredUpdates); | ||||
|     var newDns = await utils.splitDomains(directives.api, requiredUpdates); | ||||
|     common = { | ||||
|       api: 'devices.attach' | ||||
|     , session: session | ||||
| @ -169,7 +113,7 @@ module.exports.create = function (deps, conf) { | ||||
|   async function removeDomains(session, domains) { | ||||
|     var directives = await deps.OAUTH3.discover(session.token.aud); | ||||
| 
 | ||||
|     var oldDns = await splitDomains(directives.api, domains); | ||||
|     var oldDns = await utils.splitDomains(directives.api, domains); | ||||
|     var common = { | ||||
|       api: 'devices.detach' | ||||
|     , session: session | ||||
|  | ||||
| @ -3,48 +3,21 @@ | ||||
| module.exports.create = function (deps, conf) { | ||||
|   var dns = deps.PromiseA.promisifyAll(require('dns')); | ||||
|   var network = deps.PromiseA.promisifyAll(deps.recase.camelCopy(require('network'))); | ||||
|   var loopback = require('./loopback').create(deps, conf); | ||||
|   var dnsCtrl = require('./dns-ctrl').create(deps, conf); | ||||
|   var equal = require('deep-equal'); | ||||
| 
 | ||||
|   var utils = require('./utils').create(deps, conf); | ||||
|   var loopback = require('./loopback').create(deps, conf, utils); | ||||
|   var dnsCtrl = require('./dns-ctrl').create(deps, conf, utils); | ||||
|   var challenge = require('./challenge-responder').create(deps, conf, utils); | ||||
|   var tunnelClients = require('./tunnel-client-manager').create(deps, conf, utils); | ||||
| 
 | ||||
|   var loopbackDomain; | ||||
| 
 | ||||
|   function iterateAllModules(action, curConf) { | ||||
|     curConf = curConf || conf; | ||||
|     var promises = curConf.ddns.modules.map(function (mod) { | ||||
|       return action(mod, mod.domains); | ||||
|     }); | ||||
| 
 | ||||
|     curConf.domains.forEach(function (dom) { | ||||
|       if (!dom.modules || !Array.isArray(dom.modules.ddns) || !dom.modules.ddns.length) { | ||||
|         return null; | ||||
|       } | ||||
| 
 | ||||
|       // For the time being all of our things should only be tried once (regardless if it succeeded)
 | ||||
|       // TODO: revisit this behavior when we support multiple ways of setting records, and/or
 | ||||
|       // if we want to allow later modules to run if early modules fail.
 | ||||
|       promises.push(dom.modules.ddns.reduce(function (prom, mod) { | ||||
|         if (prom) { return prom; } | ||||
|         return action(mod, dom.names); | ||||
|       }, null)); | ||||
|     }); | ||||
| 
 | ||||
|     return deps.PromiseA.all(promises.filter(Boolean)); | ||||
|   } | ||||
| 
 | ||||
|   async function getSession(id) { | ||||
|     var session = await deps.storage.tokens.get(id); | ||||
|     if (!session) { | ||||
|       throw new Error('no user token with ID "'+id+'"'); | ||||
|     } | ||||
|     return session; | ||||
|   } | ||||
| 
 | ||||
|   var tunnelActive = false; | ||||
|   async function startTunnel(tunnelSession, mod, domainList) { | ||||
|     try { | ||||
|       var dnsSession = await getSession(mod.tokenId); | ||||
|       var tunnelDomain = await deps.tunnelClients.start(tunnelSession || dnsSession, domainList); | ||||
|       var dnsSession = await utils.getSession(mod.tokenId); | ||||
|       var tunnelDomain = await tunnelClients.start(tunnelSession || dnsSession, domainList); | ||||
| 
 | ||||
|       var addrList; | ||||
|       try { | ||||
| @ -59,7 +32,9 @@ module.exports.create = function (deps, conf) { | ||||
|         throw new Error('failed to lookup IP for tunnel domain "' + tunnelDomain + '"'); | ||||
|       } | ||||
| 
 | ||||
|       await dnsCtrl.setDeviceAddress(dnsSession, addrList[0], domainList); | ||||
|       if (!mod.disabled) { | ||||
|         await dnsCtrl.setDeviceAddress(dnsSession, addrList[0], domainList); | ||||
|       } | ||||
|     } catch (err) { | ||||
|       console.log('error starting tunnel for', domainList.join(', ')); | ||||
|       console.log(err); | ||||
| @ -73,7 +48,7 @@ module.exports.create = function (deps, conf) { | ||||
|       tunnelSession = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId); | ||||
|     } | ||||
| 
 | ||||
|     await iterateAllModules(function (mod, domainList) { | ||||
|     await utils.iterateAllModules(function (mod, domainList) { | ||||
|       if (mod.type !== 'dns@oauth3.org') { return null; } | ||||
| 
 | ||||
|       return startTunnel(tunnelSession, mod, domainList); | ||||
| @ -82,14 +57,14 @@ module.exports.create = function (deps, conf) { | ||||
|     tunnelActive = true; | ||||
|   } | ||||
|   async function disconnectTunnels() { | ||||
|     deps.tunnelClients.disconnect(); | ||||
|     tunnelClients.disconnect(); | ||||
|     tunnelActive = false; | ||||
|     await Promise.resolve(); | ||||
|   } | ||||
|   async function checkTunnelTokens() { | ||||
|     var oldTokens = deps.tunnelClients.current(); | ||||
|     var oldTokens = tunnelClients.current(); | ||||
| 
 | ||||
|     var newTokens = await iterateAllModules(function checkTokens(mod, domainList) { | ||||
|     var newTokens = await utils.iterateAllModules(function checkTokens(mod, domainList) { | ||||
|       if (mod.type !== 'dns@oauth3.org') { return null; } | ||||
| 
 | ||||
|       var domainStr = domainList.slice().sort().join(','); | ||||
| @ -103,7 +78,7 @@ module.exports.create = function (deps, conf) { | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     await Promise.all(Object.values(oldTokens).map(deps.tunnelClients.remove)); | ||||
|     await Promise.all(Object.values(oldTokens).map(tunnelClients.remove)); | ||||
| 
 | ||||
|     if (!newTokens.length) { return; } | ||||
| 
 | ||||
| @ -187,10 +162,10 @@ module.exports.create = function (deps, conf) { | ||||
|     } | ||||
|     publicAddress = addr; | ||||
| 
 | ||||
|     await iterateAllModules(function setModuleDNS(mod, domainList) { | ||||
|     await utils.iterateAllModules(function setModuleDNS(mod, domainList) { | ||||
|       if (mod.type !== 'dns@oauth3.org' || mod.disabled) { return null; } | ||||
| 
 | ||||
|       return getSession(mod.tokenId).then(function (session) { | ||||
|       return utils.getSession(mod.tokenId).then(function (session) { | ||||
|         return dnsCtrl.setDeviceAddress(session, addr, domainList); | ||||
|       }).catch(function (err) { | ||||
|         console.log('error setting DNS records for', domainList.join(', ')); | ||||
| @ -205,13 +180,13 @@ module.exports.create = function (deps, conf) { | ||||
| 
 | ||||
|     // this returns a Promise, but since the functions we use are synchronous
 | ||||
|     // and change our enclosed variables we don't need to wait for the return.
 | ||||
|     iterateAllModules(function (mod, domainList) { | ||||
|     utils.iterateAllModules(function (mod, domainList) { | ||||
|       if (mod.type !== 'dns@oauth3.org') { return; } | ||||
| 
 | ||||
|       prevMods[mod.id] = { mod, domainList }; | ||||
|       return true; | ||||
|     }, prevConf); | ||||
|     iterateAllModules(function (mod, domainList) { | ||||
|     utils.iterateAllModules(function (mod, domainList) { | ||||
|       if (mod.type !== 'dns@oauth3.org') { return; } | ||||
| 
 | ||||
|       curMods[mod.id] = { mod, domainList }; | ||||
| @ -234,8 +209,11 @@ module.exports.create = function (deps, conf) { | ||||
| 
 | ||||
|     // Then remove DNS records for the domains that we are no longer responsible for.
 | ||||
|     await Promise.all(Object.values(prevMods).map(function ({mod, domainList}) { | ||||
|       // If the module was disabled before there should be any records that we need to clean up
 | ||||
|       if (mod.disabled) { return; } | ||||
| 
 | ||||
|       var oldDomains; | ||||
|       if (!curMods[mod.id] || mod.tokenId !== curMods[mod.id].mod.tokenId) { | ||||
|       if (!curMods[mod.id] || curMods[mod.id].disabled || mod.tokenId !== curMods[mod.id].mod.tokenId) { | ||||
|         oldDomains = domainList.slice(); | ||||
|       } else { | ||||
|         oldDomains = domainList.filter(function (domain) { | ||||
| @ -249,7 +227,7 @@ module.exports.create = function (deps, conf) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       return getSession(mod.tokenId).then(function (session) { | ||||
|       return utils.getSession(mod.tokenId).then(function (session) { | ||||
|         return dnsCtrl.removeDomains(session, oldDomains); | ||||
|       }); | ||||
|     }).filter(Boolean)); | ||||
| @ -259,6 +237,9 @@ module.exports.create = function (deps, conf) { | ||||
| 
 | ||||
|     // And add DNS records for any newly added domains.
 | ||||
|     await Promise.all(Object.values(curMods).map(function ({mod, domainList}) { | ||||
|       // Don't set any new records if the module has been disabled.
 | ||||
|       if (mod.disabled) { return; } | ||||
| 
 | ||||
|       var newDomains; | ||||
|       if (!prevMods[mod.id] || mod.tokenId !== prevMods[mod.id].mod.tokenId) { | ||||
|         newDomains = domainList.slice(); | ||||
| @ -274,7 +255,7 @@ module.exports.create = function (deps, conf) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       return getSession(mod.tokenId).then(function (session) { | ||||
|       return utils.getSession(mod.tokenId).then(function (session) { | ||||
|         return dnsCtrl.setDeviceAddress(session, publicAddress, newDomains); | ||||
|       }); | ||||
|     }).filter(Boolean)); | ||||
| @ -340,5 +321,6 @@ module.exports.create = function (deps, conf) { | ||||
|   , getDeviceAddresses: dnsCtrl.getDeviceAddresses | ||||
|   , recheckPubAddr:     recheckPubAddr | ||||
|   , updateConf:         updateConf | ||||
|   , challenge | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @ -6,6 +6,52 @@ module.exports.create = function (deps, config) { | ||||
|   var activeTunnels = {}; | ||||
|   var activeDomains = {}; | ||||
| 
 | ||||
|   var customNet = { | ||||
|     createConnection: function (opts, cb) { | ||||
|       console.log('[gl.tunnel] creating connection'); | ||||
| 
 | ||||
|       // here "reader" means the socket that looks like the connection being accepted
 | ||||
|       // here "writer" means the remote-looking part of the socket that driving the connection
 | ||||
|       var writer; | ||||
| 
 | ||||
|       function usePair(err, reader) { | ||||
|         if (err) { | ||||
|           process.nextTick(function () { | ||||
|             writer.emit('error', err); | ||||
|           }); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         var wrapOpts = Object.assign({localAddress: '127.0.0.2', localPort: 'tunnel-0'}, opts); | ||||
|         wrapOpts.firstChunk = opts.data; | ||||
|         wrapOpts.hyperPeek = !!opts.data; | ||||
| 
 | ||||
|         // Also override the remote and local address info. We use `defineProperty` because
 | ||||
|         // otherwise we run into problems of setting properties with only getters defined.
 | ||||
|         Object.defineProperty(reader, 'remoteAddress', { value: wrapOpts.remoteAddress }); | ||||
|         Object.defineProperty(reader, 'remotePort',    { value: wrapOpts.remotePort }); | ||||
|         Object.defineProperty(reader, 'remoteFamiliy', { value: wrapOpts.remoteFamiliy }); | ||||
|         Object.defineProperty(reader, 'localAddress',  { value: wrapOpts.localAddress }); | ||||
|         Object.defineProperty(reader, 'localPort',     { value: wrapOpts.localPort }); | ||||
|         Object.defineProperty(reader, 'localFamiliy',  { value: wrapOpts.localFamiliy }); | ||||
| 
 | ||||
|         deps.tcp.handler(reader, wrapOpts); | ||||
|         process.nextTick(function () { | ||||
|           // this cb will cause the stream to emit its (actually) first data event
 | ||||
|           // (even though it already gave a peek into that first data chunk)
 | ||||
|           console.log('[tunnel] callback, data should begin to flow'); | ||||
|           cb(); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       // We used to use `stream-pair` for non-tls connections, but there are places
 | ||||
|       // that require properties/functions to be present on the socket that aren't
 | ||||
|       // present on a JSStream so it caused problems.
 | ||||
|       writer = require('socket-pair').create(usePair); | ||||
|       return writer; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   function fillData(data) { | ||||
|     if (typeof data === 'string') { | ||||
|       data = { jwt: data }; | ||||
| @ -70,7 +116,7 @@ module.exports.create = function (deps, config) { | ||||
|       // get the promise that should tell us more about if it worked or not.
 | ||||
|       activeTunnels[data.tunnelUrl] = stunnel.connect({ | ||||
|         stunneld: data.tunnelUrl | ||||
|       , net: deps.tunnel.net | ||||
|       , net: customNet | ||||
|         // NOTE: the ports here aren't that important since we are providing a custom
 | ||||
|         // `net.createConnection` that doesn't actually use the port. What is important
 | ||||
|         // is that any services we are interested in are listed in this object and have
 | ||||
							
								
								
									
										102
									
								
								lib/ddns/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								lib/ddns/utils.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports.create = function (deps, conf) { | ||||
| 
 | ||||
|   async function getSession(id) { | ||||
|     var session = await deps.storage.tokens.get(id); | ||||
|     if (!session) { | ||||
|       throw new Error('no user token with ID "' + id + '"'); | ||||
|     } | ||||
|     return session; | ||||
|   } | ||||
| 
 | ||||
|   function iterateAllModules(action, curConf) { | ||||
|     curConf = curConf || conf; | ||||
|     var promises = []; | ||||
| 
 | ||||
|     curConf.domains.forEach(function (dom) { | ||||
|       if (!dom.modules || !Array.isArray(dom.modules.ddns) || !dom.modules.ddns.length) { | ||||
|         return null; | ||||
|       } | ||||
| 
 | ||||
|       // For the time being all of our things should only be tried once (regardless if it succeeded)
 | ||||
|       // TODO: revisit this behavior when we support multiple ways of setting records, and/or
 | ||||
|       // if we want to allow later modules to run if early modules fail.
 | ||||
|       promises.push(dom.modules.ddns.reduce(function (prom, mod) { | ||||
|         if (prom) { return prom; } | ||||
|         return action(mod, dom.names); | ||||
|       }, null)); | ||||
|     }); | ||||
| 
 | ||||
|     curConf.ddns.modules.forEach(function (mod) { | ||||
|       promises.push(action(mod, mod.domains)); | ||||
|     }); | ||||
| 
 | ||||
|     return Promise.all(promises.filter(Boolean)); | ||||
|   } | ||||
| 
 | ||||
|   var tldCache = {}; | ||||
|   async function updateTldCache(provider) { | ||||
|     var reqObj = { | ||||
|       url: deps.OAUTH3.url.normalize(provider) + '/api/com.daplie.domains/prices' | ||||
|     , method: 'GET' | ||||
|     , json: true | ||||
|     }; | ||||
| 
 | ||||
|     var resp = await deps.OAUTH3.request(reqObj); | ||||
|     var tldObj = {}; | ||||
|     resp.data.forEach(function (tldInfo) { | ||||
|       if (tldInfo.enabled) { | ||||
|         tldObj[tldInfo.tld] = true; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     tldCache[provider] = { | ||||
|       time: Date.now() | ||||
|     , tlds: tldObj | ||||
|     }; | ||||
|     return tldObj; | ||||
|   } | ||||
|   async function getTlds(provider) { | ||||
|     // If we've never cached the results we need to return the promise that will fetch the result,
 | ||||
|     // otherwise we can return the cached value. If the cached value has "expired", we can still
 | ||||
|     // return the cached value we just want to update the cache in parellel (making sure we only
 | ||||
|     // update once).
 | ||||
|     if (!tldCache[provider]) { | ||||
|       tldCache[provider] = { | ||||
|         updating: true | ||||
|       , tlds: updateTldCache(provider) | ||||
|       }; | ||||
|     } | ||||
|     if (!tldCache[provider].updating && Date.now() - tldCache[provider].time > 24 * 60 * 60 * 1000) { | ||||
|       tldCache[provider].updating = true; | ||||
|       updateTldCache(provider); | ||||
|     } | ||||
| 
 | ||||
|     return tldCache[provider].tlds; | ||||
|   } | ||||
| 
 | ||||
|   async function splitDomains(provider, domains) { | ||||
|     var tlds = await getTlds(provider); | ||||
|     return domains.map(function (domain) { | ||||
|       var split = domain.split('.'); | ||||
|       var tldSegCnt = tlds[split.slice(-2).join('.')] ? 2 : 1; | ||||
| 
 | ||||
|       // Currently assuming that the sld can't contain dots, and that the tld can have at
 | ||||
|       // most one dot. Not 100% sure this is a valid assumption, but exceptions should be
 | ||||
|       // rare even if the assumption isn't valid.
 | ||||
|       return { | ||||
|         tld: split.slice(-tldSegCnt).join('.') | ||||
|       , sld: split.slice(-tldSegCnt - 1, -tldSegCnt).join('.') | ||||
|       , sub: split.slice(0, -tldSegCnt - 1).join('.') | ||||
|       }; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     getSession | ||||
|   , iterateAllModules | ||||
|   , getTlds | ||||
|   , splitDomains | ||||
|   }; | ||||
| }; | ||||
| @ -1,303 +0,0 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports.create = function (deps, config) { | ||||
|   console.log('config', config); | ||||
| 
 | ||||
|   //var PromiseA = global.Promise;
 | ||||
|   var PromiseA = require('bluebird'); | ||||
|   var listeners = require('./servers').listeners; | ||||
|   var domainUtils = require('./domain-utils'); | ||||
|   var modules; | ||||
| 
 | ||||
|   var addrProperties = [ | ||||
|     'remoteAddress' | ||||
|   , 'remotePort' | ||||
|   , 'remoteFamily' | ||||
|   , 'localAddress' | ||||
|   , 'localPort' | ||||
|   , 'localFamily' | ||||
|   ]; | ||||
| 
 | ||||
|   function nameMatchesDomains(name, domainList) { | ||||
|     return domainList.some(function (pattern) { | ||||
|       return domainUtils.match(pattern, name); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function loadModules() { | ||||
|     modules = {}; | ||||
| 
 | ||||
|     modules.tls  = require('./modules/tls').create(deps, config, tcpHandler); | ||||
|     modules.http = require('./modules/http').create(deps, config, modules.tls.middleware); | ||||
|   } | ||||
| 
 | ||||
|   function checkTcpProxy(conn, opts) { | ||||
|     var proxied = false; | ||||
| 
 | ||||
|     // TCP Proxying (ie forwarding based on domain name not incoming port) only works for
 | ||||
|     // TLS wrapped connections, so if the opts don't give us a servername or don't tell us
 | ||||
|     // this is the decrypted side of a TLS connection we can't handle it here.
 | ||||
|     if (!opts.servername || !opts.encrypted) { return proxied; } | ||||
| 
 | ||||
|     function proxy(mod) { | ||||
|       // First thing we need to add to the connection options is where to proxy the connection to
 | ||||
|       var newConnOpts = domainUtils.separatePort(mod.address || ''); | ||||
|       newConnOpts.port = newConnOpts.port || mod.port; | ||||
|       newConnOpts.host = newConnOpts.host || mod.host || 'localhost'; | ||||
| 
 | ||||
|       // Then we add all of the connection address information. We need to prefix all of the
 | ||||
|       // properties with '_' so we can provide the information to any connection `createConnection`
 | ||||
|       // implementation but not have the default implementation try to bind the same local port.
 | ||||
|       addrProperties.forEach(function (name) { | ||||
|         newConnOpts['_' + name] = opts[name] || opts['_'+name] || conn[name] || conn['_'+name]; | ||||
|       }); | ||||
| 
 | ||||
|       deps.proxy(conn, newConnOpts); | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     proxied = config.domains.some(function (dom) { | ||||
|       if (!dom.modules || !Array.isArray(dom.modules.tcp)) { return false; } | ||||
|       if (!nameMatchesDomains(opts.servername, dom.names)) { return false; } | ||||
| 
 | ||||
|       return dom.modules.tcp.some(function (mod) { | ||||
|         if (mod.type !== 'proxy') { return false; } | ||||
| 
 | ||||
|         return proxy(mod); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     proxied = proxied || config.tcp.modules.some(function (mod) { | ||||
|       if (mod.type !== 'proxy') { return false; } | ||||
|       if (!nameMatchesDomains(opts.servername, mod.domains)) { return false; } | ||||
| 
 | ||||
|       return proxy(mod); | ||||
|     }); | ||||
| 
 | ||||
|     return proxied; | ||||
|   } | ||||
| 
 | ||||
|   // opts = { servername, encrypted, peek, data, remoteAddress, remotePort }
 | ||||
|   function peek(conn, firstChunk, opts) { | ||||
|     if (!modules) { | ||||
|       loadModules(); | ||||
|     } | ||||
| 
 | ||||
|     opts.firstChunk = firstChunk; | ||||
|     conn.__opts = opts; | ||||
|     // TODO port/service-based routing can do here
 | ||||
| 
 | ||||
|     // TLS byte 1 is handshake and byte 6 is client hello
 | ||||
|     if (0x16 === firstChunk[0]/* && 0x01 === firstChunk[5]*/) { | ||||
|       modules.tls.emit('connection', conn); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // This doesn't work with TLS, but now that we know this isn't a TLS connection we can
 | ||||
|     // unshift the first chunk back onto the connection for future use. The unshift should
 | ||||
|     // happen after any listeners are attached to it but before any new data comes in.
 | ||||
|     if (!opts.hyperPeek) { | ||||
|       process.nextTick(function () { | ||||
|         conn.unshift(firstChunk); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     // Connection is not TLS, check for HTTP next.
 | ||||
|     if (firstChunk[0] > 32 && firstChunk[0] < 127) { | ||||
|       var firstStr = firstChunk.toString(); | ||||
|       if (/HTTP\//i.test(firstStr)) { | ||||
|         modules.http.emit('connection', conn); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     console.warn('failed to identify protocol from first chunk', firstChunk); | ||||
|     conn.destroy(); | ||||
|   } | ||||
|   function tcpHandler(conn, opts) { | ||||
|     function getProp(name) { | ||||
|       return opts[name] || opts['_'+name] || conn[name] || conn['_'+name]; | ||||
|     } | ||||
|     opts = opts || {}; | ||||
|     var logName = getProp('remoteAddress') + ':' + getProp('remotePort') + ' -> ' + | ||||
|                   getProp('localAddress')  + ':' + getProp('localPort'); | ||||
|     console.log('[tcpHandler]', logName, 'connection started - encrypted: ' + (opts.encrypted || false)); | ||||
| 
 | ||||
|     var start = Date.now(); | ||||
|     conn.on('timeout', function () { | ||||
|       console.log('[tcpHandler]', logName, 'connection timed out', (Date.now()-start)/1000); | ||||
|     }); | ||||
|     conn.on('end', function () { | ||||
|       console.log('[tcpHandler]', logName, 'connection ended', (Date.now()-start)/1000); | ||||
|     }); | ||||
|     conn.on('close', function () { | ||||
|       console.log('[tcpHandler]', logName, 'connection closed', (Date.now()-start)/1000); | ||||
|     }); | ||||
| 
 | ||||
|     if (checkTcpProxy(conn, opts)) { return; } | ||||
| 
 | ||||
|     // XXX PEEK COMMENT XXX
 | ||||
|     // TODO we can have our cake and eat it too
 | ||||
|     // we can skip the need to wrap the TLS connection twice
 | ||||
|     // because we've already peeked at the data,
 | ||||
|     // but this needs to be handled better before we enable that
 | ||||
|     // (because it creates new edge cases)
 | ||||
|     if (opts.hyperPeek) { | ||||
|       console.log('hyperpeek'); | ||||
|       peek(conn, opts.firstChunk, opts); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     function onError(err) { | ||||
|       console.error('[error] socket errored peeking -', err); | ||||
|       conn.destroy(); | ||||
|     } | ||||
|     conn.once('error', onError); | ||||
|     conn.once('data', function (chunk) { | ||||
|       conn.removeListener('error', onError); | ||||
|       peek(conn, chunk, opts); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function udpHandler(port, msg) { | ||||
|     if (!Array.isArray(config.udp.modules)) { | ||||
|       return; | ||||
|     } | ||||
|     var socket = require('dgram').createSocket('udp4'); | ||||
|     config.udp.modules.forEach(function (mod) { | ||||
|       if (mod.type !== 'forward') { | ||||
|         console.warn('found bad DNS module', mod); | ||||
|         return; | ||||
|       } | ||||
|       if (mod.ports.indexOf(port) < 0) { | ||||
|         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) { | ||||
|     var dest = require('./domain-utils').separatePort(mod.address || ''); | ||||
|     dest.port = dest.port || mod.port; | ||||
|     dest.host = dest.host || mod.host || 'localhost'; | ||||
| 
 | ||||
|     return function (conn) { | ||||
|       var newConnOpts = {}; | ||||
|       addrProperties.forEach(function (name) { | ||||
|         newConnOpts['_'+name] = conn[name]; | ||||
|       }); | ||||
| 
 | ||||
|       deps.proxy(conn, Object.assign(newConnOpts, dest)); | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   deps.tunnel = deps.tunnel || {}; | ||||
|   deps.tunnel.net = { | ||||
|     createConnection: function (opts, cb) { | ||||
|       console.log('[gl.tunnel] creating connection'); | ||||
| 
 | ||||
|       // here "reader" means the socket that looks like the connection being accepted
 | ||||
|       // here "writer" means the remote-looking part of the socket that driving the connection
 | ||||
|       var writer; | ||||
|       var wrapOpts = {}; | ||||
| 
 | ||||
|       function usePair(err, reader) { | ||||
|         if (err) { | ||||
|           process.nextTick(function () { | ||||
|             writer.emit('error', err); | ||||
|           }); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         // this has the normal net/tcp stuff plus our custom stuff
 | ||||
|         // opts = { address, port,
 | ||||
|         //          hostname, servername, tls, encrypted, data, localAddress, localPort, remoteAddress, remotePort, remoteFamily }
 | ||||
|         Object.keys(opts).forEach(function (key) { | ||||
|           wrapOpts[key] = opts[key]; | ||||
|           try { | ||||
|             reader[key] = opts[key]; | ||||
|           } catch(e) { | ||||
|             // can't set real socket getters, like remoteAddr
 | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         // A few more extra specialty options
 | ||||
|         wrapOpts.localAddress = wrapOpts.localAddress || '127.0.0.2'; // TODO use the tunnel's external address
 | ||||
|         wrapOpts.localPort = wrapOpts.localPort || 'tunnel-0'; | ||||
|         try { | ||||
|           reader._remoteAddress = wrapOpts.remoteAddress; | ||||
|           reader._remotePort = wrapOpts.remotePort; | ||||
|           reader._remoteFamily = wrapOpts.remoteFamily; | ||||
|           reader._localAddress = wrapOpts.localAddress; | ||||
|           reader._localPort = wrapOpts.localPort; | ||||
|           reader._localFamily = wrapOpts.localFamily; | ||||
|         } catch(e) { | ||||
|         } | ||||
| 
 | ||||
|         tcpHandler(reader, wrapOpts); | ||||
| 
 | ||||
|         process.nextTick(function () { | ||||
|           // this cb will cause the stream to emit its (actually) first data event
 | ||||
|           // (even though it already gave a peek into that first data chunk)
 | ||||
|           console.log('[tunnel] callback, data should begin to flow'); | ||||
|           cb(); | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       wrapOpts.firstChunk = opts.data; | ||||
|       wrapOpts.hyperPeek = !!opts.data; | ||||
| 
 | ||||
|       // We used to use `stream-pair` for non-tls connections, but there are places
 | ||||
|       // that require properties/functions to be present on the socket that aren't
 | ||||
|       // present on a JSStream so it caused problems.
 | ||||
|       writer = require('socket-pair').create(usePair); | ||||
|       return writer; | ||||
|     } | ||||
|   }; | ||||
|   deps.tunnelClients = require('./tunnel-client-manager').create(deps, config); | ||||
|   deps.tunnelServer = require('./tunnel-server-manager').create(deps, config); | ||||
| 
 | ||||
|   var listenPromises = []; | ||||
|   var tcpPortMap = {}; | ||||
|   config.tcp.bind.filter(Number).forEach(function (port) { | ||||
|     tcpPortMap[port] = true; | ||||
|   }); | ||||
| 
 | ||||
|   (config.tcp.modules || []).forEach(function (mod) { | ||||
|     if (mod.type === 'forward') { | ||||
|       var forwarder = createTcpForwarder(mod); | ||||
|       mod.ports.forEach(function (port) { | ||||
|         if (!tcpPortMap[port]) { | ||||
|           console.log("forwarding port", port, "that wasn't specified in bind"); | ||||
|         } else { | ||||
|           delete tcpPortMap[port]; | ||||
|         } | ||||
|         listenPromises.push(listeners.tcp.add(port, forwarder)); | ||||
|       }); | ||||
|     } | ||||
|     else if (mod.type !== 'proxy') { | ||||
|       console.warn('unknown TCP module specified', mod); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   var portList = Object.keys(tcpPortMap).map(Number).sort(); | ||||
|   portList.forEach(function (port) { | ||||
|     listenPromises.push(listeners.tcp.add(port, tcpHandler)); | ||||
|   }); | ||||
| 
 | ||||
|   if (config.udp.bind) { | ||||
|     config.udp.bind.forEach(function (port) { | ||||
|       listenPromises.push(listeners.udp.add(port, udpHandler.bind(port))); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   if (!config.mdns.disabled) { | ||||
|     require('./mdns').start(deps, config, portList[0]); | ||||
|   } | ||||
| 
 | ||||
|   return PromiseA.all(listenPromises); | ||||
| }; | ||||
							
								
								
									
										102
									
								
								lib/mdns.js
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								lib/mdns.js
									
									
									
									
									
								
							| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| var PromiseA = require('bluebird'); | ||||
| var queryName = '_cloud._tcp.local'; | ||||
| var dnsSuite = require('dns-suite'); | ||||
| 
 | ||||
| function createResponse(name, ownerIds, packet, ttl, mainPort) { | ||||
|   var rpacket = { | ||||
| @ -85,20 +86,19 @@ function createResponse(name, ownerIds, packet, ttl, mainPort) { | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   return require('dns-suite').DNSPacket.write(rpacket); | ||||
|   return dnsSuite.DNSPacket.write(rpacket); | ||||
| } | ||||
| 
 | ||||
| module.exports.start = function (deps, config, mainPort) { | ||||
|   var socket = require('dgram').createSocket({ type: 'udp4', reuseAddr: true }); | ||||
|   var dns = require('dns-suite'); | ||||
| module.exports.create = function (deps, config) { | ||||
|   var socket; | ||||
|   var nextBroadcast = -1; | ||||
| 
 | ||||
|   socket.on('message', function (message, rinfo) { | ||||
|   function handlePacket(message, rinfo) { | ||||
|     // console.log('Received %d bytes from %s:%d', message.length, rinfo.address, rinfo.port);
 | ||||
| 
 | ||||
|     var packet; | ||||
|     try { | ||||
|       packet = dns.DNSPacket.parse(message); | ||||
|       packet = dnsSuite.DNSPacket.parse(message); | ||||
|     } | ||||
|     catch (er) { | ||||
|       // `dns-suite` actually errors on a lot of the packets floating around in our network,
 | ||||
| @ -108,16 +108,12 @@ module.exports.start = function (deps, config, mainPort) { | ||||
|     } | ||||
| 
 | ||||
|     // Only respond to queries.
 | ||||
|     if (packet.header.qr !== 0) { | ||||
|       return; | ||||
|     } | ||||
|     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; | ||||
|     } | ||||
|     if (packet.question[0].typeName !== 'PTR' || packet.question[0].className !== 'IN' ) { | ||||
|       return; | ||||
|     } | ||||
|     if (packet.question.length !== 1)           { return; } | ||||
|     if (packet.question[0].name !== queryName)  { return; } | ||||
|     if (packet.question[0].typeName !== 'PTR')  { return; } | ||||
|     if (packet.question[0].className !== 'IN' ) { return; } | ||||
| 
 | ||||
|     var proms = [ | ||||
|       deps.storage.mdnsId.get() | ||||
| @ -131,7 +127,7 @@ module.exports.start = function (deps, config, mainPort) { | ||||
|     ]; | ||||
| 
 | ||||
|     PromiseA.all(proms).then(function (results) { | ||||
|       var resp = createResponse(results[0], results[1], packet, config.mdns.ttl, mainPort); | ||||
|       var resp = createResponse(results[0], results[1], packet, config.mdns.ttl, deps.tcp.mainPort); | ||||
|       var now = Date.now(); | ||||
|       if (now > nextBroadcast) { | ||||
|         socket.send(resp, config.mdns.port, config.mdns.broadcast); | ||||
| @ -140,18 +136,68 @@ module.exports.start = function (deps, config, mainPort) { | ||||
|         socket.send(resp, rinfo.port, rinfo.address); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
|   } | ||||
| 
 | ||||
|   socket.bind(config.mdns.port, function () { | ||||
|     var addr = this.address(); | ||||
|     console.log('bound on UDP %s:%d for mDNS', addr.address, addr.port); | ||||
|   function start() { | ||||
|     socket = require('dgram').createSocket({ type: 'udp4', reuseAddr: true }); | ||||
|     socket.on('message', handlePacket); | ||||
| 
 | ||||
|     socket.setBroadcast(true); | ||||
|     socket.addMembership(config.mdns.broadcast); | ||||
|     // This is supposed to be a local device discovery mechanism, so we shouldn't
 | ||||
|     // need to hop through any gateways. This helps with security by making it
 | ||||
|     // much more difficult for someone to use us as part of a DDoS attack by
 | ||||
|     // spoofing the UDP address a request came from.
 | ||||
|     socket.setTTL(1); | ||||
|   }); | ||||
|     return new Promise(function (resolve, reject) { | ||||
|       socket.once('error', reject); | ||||
| 
 | ||||
|       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); | ||||
|         // This is supposed to be a local device discovery mechanism, so we shouldn't
 | ||||
|         // need to hop through any gateways. This helps with security by making it
 | ||||
|         // much more difficult for someone to use us as part of a DDoS attack by
 | ||||
|         // spoofing the UDP address a request came from.
 | ||||
|         socket.setTTL(1); | ||||
| 
 | ||||
|         socket.removeListener('error', reject); | ||||
|         resolve(); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
|   function stop() { | ||||
|     return new Promise(function (resolve, reject) { | ||||
|       socket.once('error', reject); | ||||
| 
 | ||||
|       socket.close(function () { | ||||
|         socket.removeListener('error', reject); | ||||
|         socket = null; | ||||
|         resolve(); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function updateConf() { | ||||
|     var promise; | ||||
|     if (config.mdns.disabled) { | ||||
|       if (socket) { | ||||
|         promise = stop(); | ||||
|       } | ||||
|     } else { | ||||
|       if (!socket) { | ||||
|         promise = start(); | ||||
|       } else if (socket.address().port !== config.mdns.port) { | ||||
|         promise = stop().then(start); | ||||
|       } else { | ||||
|         // Can't check membership, so just add the current broadcast address to make sure
 | ||||
|         // it's set. If it's already set it will throw an exception (at least on linux).
 | ||||
|         try { | ||||
|           socket.addMembership(config.mdns.broadcast); | ||||
|         } catch (e) {} | ||||
|         promise = Promise.resolve(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   updateConf(); | ||||
| 
 | ||||
|   return { | ||||
|     updateConf | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @ -10,20 +10,16 @@ module.exports.addTcpListener = function (port, handler) { | ||||
| 
 | ||||
|     if (stat) { | ||||
|       if (stat._closing) { | ||||
|         module.exports.destroyTcpListener(port); | ||||
|       } | ||||
|       else if (handler !== stat.handler) { | ||||
| 
 | ||||
|         // we'll replace the current listener
 | ||||
|         stat.server.destroy(); | ||||
|       } else { | ||||
|         // We're already listening on the port, so we only have 2 options. We can either
 | ||||
|         // replace the handler or reject with an error. (Though neither is really needed
 | ||||
|         // if the handlers are the same). Until there is reason to do otherwise we are
 | ||||
|         // opting for the replacement.
 | ||||
|         stat.handler = handler; | ||||
|         resolve(); | ||||
|         return; | ||||
|       } | ||||
|       else { | ||||
|         // this exact listener is already open
 | ||||
|         resolve(); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     var enableDestroy = require('server-destroy'); | ||||
| @ -34,7 +30,7 @@ module.exports.addTcpListener = function (port, handler) { | ||||
|     stat = serversMap[port] = { | ||||
|       server: server | ||||
|     , handler: handler | ||||
|     , _closing: null | ||||
|     , _closing: false | ||||
|     }; | ||||
| 
 | ||||
|     // Add .destroy so we can close all open connections. Better if added before listen
 | ||||
| @ -66,14 +62,24 @@ module.exports.addTcpListener = function (port, handler) { | ||||
|     }); | ||||
|   }); | ||||
| }; | ||||
| module.exports.closeTcpListener = function (port) { | ||||
| module.exports.closeTcpListener = function (port, timeout) { | ||||
|   return new PromiseA(function (resolve) { | ||||
|     var stat = serversMap[port]; | ||||
|     if (!stat) { | ||||
|       resolve(); | ||||
|       return; | ||||
|     } | ||||
|     stat.server.once('close', resolve); | ||||
|     stat._closing = true; | ||||
| 
 | ||||
|     var timeoutId; | ||||
|     if (timeout) { | ||||
|       timeoutId = setTimeout(() => stat.server.destroy(), timeout); | ||||
|     } | ||||
| 
 | ||||
|     stat.server.once('close', function () { | ||||
|       clearTimeout(timeoutId); | ||||
|       resolve(); | ||||
|     }); | ||||
|     stat.server.close(); | ||||
|   }); | ||||
| }; | ||||
| @ -84,7 +90,9 @@ module.exports.destroyTcpListener = function (port) { | ||||
|   } | ||||
| }; | ||||
| module.exports.listTcpListeners = function () { | ||||
|   return Object.keys(serversMap).map(Number).filter(Boolean); | ||||
|   return Object.keys(serversMap).map(Number).filter(function (port) { | ||||
|     return port && !serversMap[port]._closing; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -63,15 +63,29 @@ module.exports.create = function (deps, config) { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   if (config.socks5 && config.socks5.enabled) { | ||||
|     start(config.socks5.port).catch(function (err) { | ||||
|       console.error('failed to start Socks5 proxy', err); | ||||
|     }); | ||||
|   var configEnabled = false; | ||||
|   function updateConf() { | ||||
|     var wanted = config.socks5 && config.socks5.enabled; | ||||
| 
 | ||||
|     if (configEnabled && !wanted) { | ||||
|       stop().catch(function (err) { | ||||
|         console.error('failed to stop socks5 proxy on config change', err); | ||||
|       }); | ||||
|       configEnabled = false; | ||||
|     } | ||||
|     if (wanted && !configEnabled) { | ||||
|       start(config.socks5.port).catch(function (err) { | ||||
|         console.error('failed to start Socks5 proxy', err); | ||||
|       }); | ||||
|       configEnabled = true; | ||||
|     } | ||||
|   } | ||||
|   process.nextTick(updateConf); | ||||
| 
 | ||||
|   return { | ||||
|     curState: curState | ||||
|   , start: start | ||||
|   , stop: stop | ||||
|     curState | ||||
|   , start | ||||
|   , stop | ||||
|   , updateConf | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports.create = function (deps, conf, greenlockMiddleware) { | ||||
| module.exports.create = function (deps, conf, tcpMods) { | ||||
|   var PromiseA = require('bluebird'); | ||||
|   var statAsync = PromiseA.promisify(require('fs').stat); | ||||
|   var domainMatches = require('../domain-utils').match; | ||||
| @ -162,8 +162,8 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     if (deps.tunnelServer.isClientDomain(separatePort(headers.host).host)) { | ||||
|       deps.tunnelServer.handleClientConn(conn); | ||||
|     if (deps.stunneld.isClientDomain(separatePort(headers.host).host)) { | ||||
|       deps.stunneld.handleClientConn(conn); | ||||
|       process.nextTick(function () { | ||||
|         conn.unshift(opts.firstChunk); | ||||
|         conn.resume(); | ||||
| @ -172,7 +172,7 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { | ||||
|     } | ||||
| 
 | ||||
|     if (!acmeServer) { | ||||
|       acmeServer = require('http').createServer(greenlockMiddleware); | ||||
|       acmeServer = require('http').createServer(tcpMods.tls.middleware); | ||||
|     } | ||||
|     return emitConnection(acmeServer, conn, opts); | ||||
|   } | ||||
| @ -214,8 +214,8 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { | ||||
|       return emitConnection(adminServer, conn, opts); | ||||
|     } | ||||
| 
 | ||||
|     if (deps.tunnelServer.isAdminDomain(host)) { | ||||
|       deps.tunnelServer.handleAdminConn(conn); | ||||
|     if (deps.stunneld.isAdminDomain(host)) { | ||||
|       deps.stunneld.handleAdminConn(conn); | ||||
|       process.nextTick(function () { | ||||
|         conn.unshift(opts.firstChunk); | ||||
|         conn.resume(); | ||||
| @ -241,7 +241,7 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { | ||||
|       res.statusCode = 502; | ||||
|       res.setHeader('Connection', 'close'); | ||||
|       res.setHeader('Content-Type', 'text/html'); | ||||
|       res.end(require('../proxy-conn').getRespBody(err, conf.debug)); | ||||
|       res.end(tcpMods.proxy.getRespBody(err, conf.debug)); | ||||
|     }); | ||||
| 
 | ||||
|     proxyServer = http.createServer(function (req, res) { | ||||
| @ -292,7 +292,7 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { | ||||
|     newConnOpts.remoteAddress = opts.address || conn.remoteAddress; | ||||
|     newConnOpts.remotePort    = opts.port    || conn.remotePort; | ||||
| 
 | ||||
|     deps.proxy(conn, newConnOpts, opts.firstChunk); | ||||
|     tcpMods.proxy(conn, newConnOpts, opts.firstChunk); | ||||
|   } | ||||
| 
 | ||||
|   function checkProxy(mod, conn, opts, headers) { | ||||
							
								
								
									
										242
									
								
								lib/tcp/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								lib/tcp/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,242 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports.create = function (deps, config) { | ||||
|   console.log('config', config); | ||||
| 
 | ||||
|   var listeners = require('../servers').listeners.tcp; | ||||
|   var domainUtils = require('../domain-utils'); | ||||
|   var modules; | ||||
| 
 | ||||
|   var addrProperties = [ | ||||
|     'remoteAddress' | ||||
|   , 'remotePort' | ||||
|   , 'remoteFamily' | ||||
|   , 'localAddress' | ||||
|   , 'localPort' | ||||
|   , 'localFamily' | ||||
|   ]; | ||||
| 
 | ||||
|   function nameMatchesDomains(name, domainList) { | ||||
|     return domainList.some(function (pattern) { | ||||
|       return domainUtils.match(pattern, name); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function proxy(mod, conn, opts) { | ||||
|     // First thing we need to add to the connection options is where to proxy the connection to
 | ||||
|     var newConnOpts = domainUtils.separatePort(mod.address || ''); | ||||
|     newConnOpts.port = newConnOpts.port || mod.port; | ||||
|     newConnOpts.host = newConnOpts.host || mod.host || 'localhost'; | ||||
| 
 | ||||
|     // Then we add all of the connection address information. We need to prefix all of the
 | ||||
|     // properties with '_' so we can provide the information to any connection `createConnection`
 | ||||
|     // implementation but not have the default implementation try to bind the same local port.
 | ||||
|     addrProperties.forEach(function (name) { | ||||
|       newConnOpts['_' + name] = opts[name] || opts['_'+name] || conn[name] || conn['_'+name]; | ||||
|     }); | ||||
| 
 | ||||
|     modules.proxy(conn, newConnOpts); | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   function checkTcpProxy(conn, opts) { | ||||
|     var proxied = false; | ||||
| 
 | ||||
|     // TCP Proxying (ie routing based on domain name [vs local port]) only works for
 | ||||
|     // TLS wrapped connections, so if the opts don't give us a servername or don't tell us
 | ||||
|     // this is the decrypted side of a TLS connection we can't handle it here.
 | ||||
|     if (!opts.servername || !opts.encrypted) { return proxied; } | ||||
| 
 | ||||
|     proxied = config.domains.some(function (dom) { | ||||
|       if (!dom.modules || !Array.isArray(dom.modules.tcp)) { return false; } | ||||
|       if (!nameMatchesDomains(opts.servername, dom.names)) { return false; } | ||||
| 
 | ||||
|       return dom.modules.tcp.some(function (mod) { | ||||
|         if (mod.type !== 'proxy') { return false; } | ||||
| 
 | ||||
|         return proxy(mod, conn, opts); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     proxied = proxied || config.tcp.modules.some(function (mod) { | ||||
|       if (mod.type !== 'proxy') { return false; } | ||||
|       if (!nameMatchesDomains(opts.servername, mod.domains)) { return false; } | ||||
| 
 | ||||
|       return proxy(mod, conn, opts); | ||||
|     }); | ||||
| 
 | ||||
|     return proxied; | ||||
|   } | ||||
| 
 | ||||
|   function checkTcpForward(conn, opts) { | ||||
|     // TCP forwarding (ie routing connections based on local port) requires the local port
 | ||||
|     if (!conn.localPort) { return false; } | ||||
| 
 | ||||
|     return config.tcp.modules.some(function (mod) { | ||||
|       if (mod.type !== 'forward')                { return false; } | ||||
|       if (mod.ports.indexOf(conn.localPort) < 0) { return false; } | ||||
| 
 | ||||
|       return proxy(mod, conn, opts); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // opts = { servername, encrypted, peek, data, remoteAddress, remotePort }
 | ||||
|   function peek(conn, firstChunk, opts) { | ||||
|     opts.firstChunk = firstChunk; | ||||
|     conn.__opts = opts; | ||||
|     // TODO port/service-based routing can do here
 | ||||
| 
 | ||||
|     // TLS byte 1 is handshake and byte 6 is client hello
 | ||||
|     if (0x16 === firstChunk[0]/* && 0x01 === firstChunk[5]*/) { | ||||
|       modules.tls.emit('connection', conn); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // This doesn't work with TLS, but now that we know this isn't a TLS connection we can
 | ||||
|     // unshift the first chunk back onto the connection for future use. The unshift should
 | ||||
|     // happen after any listeners are attached to it but before any new data comes in.
 | ||||
|     if (!opts.hyperPeek) { | ||||
|       process.nextTick(function () { | ||||
|         conn.unshift(firstChunk); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     // Connection is not TLS, check for HTTP next.
 | ||||
|     if (firstChunk[0] > 32 && firstChunk[0] < 127) { | ||||
|       var firstStr = firstChunk.toString(); | ||||
|       if (/HTTP\//i.test(firstStr)) { | ||||
|         modules.http.emit('connection', conn); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     console.warn('failed to identify protocol from first chunk', firstChunk); | ||||
|     conn.destroy(); | ||||
|   } | ||||
|   function tcpHandler(conn, opts) { | ||||
|     function getProp(name) { | ||||
|       return opts[name] || opts['_'+name] || conn[name] || conn['_'+name]; | ||||
|     } | ||||
|     opts = opts || {}; | ||||
|     var logName = getProp('remoteAddress') + ':' + getProp('remotePort') + ' -> ' + | ||||
|                   getProp('localAddress')  + ':' + getProp('localPort'); | ||||
|     console.log('[tcpHandler]', logName, 'connection started - encrypted: ' + (opts.encrypted || false)); | ||||
| 
 | ||||
|     var start = Date.now(); | ||||
|     conn.on('timeout', function () { | ||||
|       console.log('[tcpHandler]', logName, 'connection timed out', (Date.now()-start)/1000); | ||||
|     }); | ||||
|     conn.on('end', function () { | ||||
|       console.log('[tcpHandler]', logName, 'connection ended', (Date.now()-start)/1000); | ||||
|     }); | ||||
|     conn.on('close', function () { | ||||
|       console.log('[tcpHandler]', logName, 'connection closed', (Date.now()-start)/1000); | ||||
|     }); | ||||
| 
 | ||||
|     if (checkTcpForward(conn, opts)) { return; } | ||||
|     if (checkTcpProxy(conn, opts))   { return; } | ||||
| 
 | ||||
|     // XXX PEEK COMMENT XXX
 | ||||
|     // TODO we can have our cake and eat it too
 | ||||
|     // we can skip the need to wrap the TLS connection twice
 | ||||
|     // because we've already peeked at the data,
 | ||||
|     // but this needs to be handled better before we enable that
 | ||||
|     // (because it creates new edge cases)
 | ||||
|     if (opts.hyperPeek) { | ||||
|       console.log('hyperpeek'); | ||||
|       peek(conn, opts.firstChunk, opts); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     function onError(err) { | ||||
|       console.error('[error] socket errored peeking -', err); | ||||
|       conn.destroy(); | ||||
|     } | ||||
|     conn.once('error', onError); | ||||
|     conn.once('data', function (chunk) { | ||||
|       conn.removeListener('error', onError); | ||||
|       peek(conn, chunk, opts); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   process.nextTick(function () { | ||||
|     modules = {}; | ||||
|     modules.tcpHandler = tcpHandler; | ||||
|     modules.proxy = require('./proxy-conn').create(deps, config); | ||||
|     modules.tls   = require('./tls').create(deps, config, modules); | ||||
|     modules.http  = require('./http').create(deps, config, modules); | ||||
|   }); | ||||
| 
 | ||||
|   function updateListeners() { | ||||
|     var current = listeners.list(); | ||||
|     var wanted = config.tcp.bind; | ||||
| 
 | ||||
|     if (!Array.isArray(wanted)) { wanted = []; } | ||||
|     wanted = wanted.map(Number).filter((port) => port > 0 && port < 65356); | ||||
| 
 | ||||
|     var closeProms = current.filter(function (port) { | ||||
|       return wanted.indexOf(port) < 0; | ||||
|     }).map(function (port) { | ||||
|       return listeners.close(port, 1000); | ||||
|     }); | ||||
| 
 | ||||
|     // We don't really need to filter here since listening on the same port with the
 | ||||
|     // same handler function twice is basically a no-op.
 | ||||
|     var openProms = wanted.map(function (port) { | ||||
|       return listeners.add(port, tcpHandler); | ||||
|     }); | ||||
| 
 | ||||
|     return Promise.all(closeProms.concat(openProms)); | ||||
|   } | ||||
| 
 | ||||
|   var mainPort; | ||||
|   function updateConf() { | ||||
|     updateListeners().catch(function (err) { | ||||
|       console.error('Error updating TCP listeners to match bind configuration'); | ||||
|       console.error(err); | ||||
|     }); | ||||
| 
 | ||||
|     var unforwarded = {}; | ||||
|     config.tcp.bind.forEach(function (port) { | ||||
|       unforwarded[port] = true; | ||||
|     }); | ||||
| 
 | ||||
|     config.tcp.modules.forEach(function (mod) { | ||||
|       if (['forward', 'proxy'].indexOf(mod.type) < 0) { | ||||
|         console.warn('unknown TCP module type specified', JSON.stringify(mod)); | ||||
|       } | ||||
|       if (mod.type !== 'forward') { return; } | ||||
| 
 | ||||
|       mod.ports.forEach(function (port) { | ||||
|         if (!unforwarded[port]) { | ||||
|           console.warn('trying to forward TCP port ' + port + ' multiple times or it is unbound'); | ||||
|         } else { | ||||
|           delete unforwarded[port]; | ||||
|         } | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     // Not really sure what we can reasonably do to prevent this. At least not without making
 | ||||
|     // our configuration validation more complicated.
 | ||||
|     if (!Object.keys(unforwarded).length) { | ||||
|       console.warn('no bound TCP ports are not being forwarded, admin interface will be inaccessible'); | ||||
|     } | ||||
| 
 | ||||
|     // If we are listening on port 443 make that the main port we respond to mDNS queries with
 | ||||
|     // otherwise choose the lowest number port we are bound to but not forwarding.
 | ||||
|     if (unforwarded['443']) { | ||||
|       mainPort = 443; | ||||
|     } else { | ||||
|       mainPort = Object.keys(unforwarded).map(Number).sort((a, b) => a - b)[0]; | ||||
|     } | ||||
|   } | ||||
|   updateConf(); | ||||
| 
 | ||||
|   var result =  { | ||||
|     updateConf | ||||
|   , handler: tcpHandler | ||||
|   }; | ||||
|   Object.defineProperty(result, 'mainPort', {enumerable: true, get: () => mainPort}); | ||||
| 
 | ||||
|   return result; | ||||
| }; | ||||
| @ -32,7 +32,7 @@ module.exports.getRespBody = getRespBody; | ||||
| module.exports.sendBadGateway = sendBadGateway; | ||||
| 
 | ||||
| module.exports.create = function (deps, config) { | ||||
|   return function proxy(conn, newConnOpts, firstChunk, decrypt) { | ||||
|   function proxy(conn, newConnOpts, firstChunk, decrypt) { | ||||
|     var connected = false; | ||||
|     newConnOpts.allowHalfOpen = true; | ||||
|     var newConn = deps.net.createConnection(newConnOpts, function () { | ||||
| @ -73,5 +73,9 @@ module.exports.create = function (deps, config) { | ||||
|     newConn.on('close', function () { | ||||
|       conn.destroy(); | ||||
|     }); | ||||
|   }; | ||||
|   } | ||||
| 
 | ||||
|   proxy.getRespBody = getRespBody; | ||||
|   proxy.sendBadGateway = sendBadGateway; | ||||
|   return proxy; | ||||
| }; | ||||
| @ -1,6 +1,6 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports.create = function (deps, config, netHandler) { | ||||
| module.exports.create = function (deps, config, tcpMods) { | ||||
|   var path = require('path'); | ||||
|   var tls = require('tls'); | ||||
|   var parseSni = require('sni'); | ||||
| @ -86,8 +86,7 @@ module.exports.create = function (deps, config, netHandler) { | ||||
|   , challenges: { | ||||
|       'http-01': require('le-challenge-fs').create({ debug: config.debug }) | ||||
|     , 'tls-sni-01': require('le-challenge-sni').create({ debug: config.debug }) | ||||
|       // TODO dns-01
 | ||||
|       //, 'dns-01': require('le-challenge-ddns').create({ debug: config.debug })
 | ||||
|     , 'dns-01': deps.ddns.challenge | ||||
|     } | ||||
|   , challengeType: 'http-01' | ||||
| 
 | ||||
| @ -208,7 +207,7 @@ module.exports.create = function (deps, config, netHandler) { | ||||
|   var terminateServer = tls.createServer(terminatorOpts, function (socket) { | ||||
|     console.log('(post-terminated) tls connection, addr:', extractSocketProp(socket, 'remoteAddress')); | ||||
| 
 | ||||
|     netHandler(socket, { | ||||
|     tcpMods.tcpHandler(socket, { | ||||
|       servername: socket.servername | ||||
|     , encrypted: true | ||||
|       // remoteAddress... ugh... https://github.com/nodejs/node/issues/8854
 | ||||
| @ -232,7 +231,7 @@ module.exports.create = function (deps, config, netHandler) { | ||||
|     newConnOpts.remoteAddress = opts.address || extractSocketProp(socket, 'remoteAddress'); | ||||
|     newConnOpts.remotePort    = opts.port    || extractSocketProp(socket, 'remotePort'); | ||||
| 
 | ||||
|     deps.proxy(socket, newConnOpts, opts.firstChunk, function () { | ||||
|     tcpMods.proxy(socket, newConnOpts, opts.firstChunk, function () { | ||||
|       // This function is called in the event of a connection error and should decrypt
 | ||||
|       // the socket so the proxy module can send a 502 HTTP response.
 | ||||
|       var tlsOpts = localhostCerts.mergeTlsOptions('localhost.daplie.me', {isServer: true}); | ||||
| @ -291,8 +290,8 @@ module.exports.create = function (deps, config, netHandler) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (deps.tunnelServer.isClientDomain(opts.servername)) { | ||||
|       deps.tunnelServer.handleClientConn(socket); | ||||
|     if (deps.stunneld.isClientDomain(opts.servername)) { | ||||
|       deps.stunneld.handleClientConn(socket); | ||||
|       if (!opts.hyperPeek) { | ||||
|         process.nextTick(function () { | ||||
|           socket.unshift(opts.firstChunk); | ||||
| @ -1,61 +1,131 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports.create = function (deps, config) { | ||||
|   if (!config.tunnelServer || !Array.isArray(config.tunnelServer.servernames) || !config.tunnelServer.secret) { | ||||
|     return { | ||||
|       isAdminDomain:  function () { return false; } | ||||
|     , isClientDomain: function () { return false; } | ||||
|     }; | ||||
|   } | ||||
| function httpsTunnel(servername, conn) { | ||||
|   console.error('tunnel server received encrypted connection to', servername); | ||||
|   conn.end(); | ||||
| } | ||||
| function handleHttp(servername, conn) { | ||||
|   console.error('tunnel server received un-encrypted connection to', servername); | ||||
|   conn.end([ | ||||
|     'HTTP/1.1 404 Not Found' | ||||
|   , 'Date: ' + (new Date()).toUTCString() | ||||
|   , 'Connection: close' | ||||
|   , 'Content-Type: text/html' | ||||
|   , 'Content-Length: 9' | ||||
|   , '' | ||||
|   , 'Not Found' | ||||
|   ].join('\r\n')); | ||||
| } | ||||
| function rejectNonWebsocket(req, res) { | ||||
|   // status code 426 = Upgrade Required
 | ||||
|   res.statusCode = 426; | ||||
|   res.setHeader('Content-Type', 'application/json'); | ||||
|   res.send({error: { message: 'Only websockets accepted for tunnel server' }}); | ||||
| } | ||||
| 
 | ||||
|   var tunnelOpts = Object.assign({}, config.tunnelServer); | ||||
|   // This function should not be called because connections to the admin domains
 | ||||
| var defaultConfig = { | ||||
|   servernames: [] | ||||
| , secret: null | ||||
| }; | ||||
| var tunnelFuncs = { | ||||
|   // These functions should not be called because connections to the admin domains
 | ||||
|   // should already be decrypted, and connections to non-client domains should never
 | ||||
|   // be given to us in the first place.
 | ||||
|   tunnelOpts.httpsTunnel = function (servername, conn) { | ||||
|     console.error('tunnel server received encrypted connection to', servername); | ||||
|     conn.end(); | ||||
|   }; | ||||
|   tunnelOpts.httpsInvalid = tunnelOpts.httpsTunnel; | ||||
|   // This function should not be called because ACME challenges should be handled
 | ||||
|   httpsTunnel:  httpsTunnel | ||||
| , httpsInvalid: httpsTunnel | ||||
|   // These function should not be called because ACME challenges should be handled
 | ||||
|   // before admin domain connections are given to us, and the only non-encrypted
 | ||||
|   // client connections that should be given to us are ACME challenges.
 | ||||
|   tunnelOpts.handleHttp = function (servername, conn) { | ||||
|     console.error('tunnel server received un-encrypted connection to', servername); | ||||
|     conn.end([ | ||||
|       'HTTP/1.1 404 Not Found' | ||||
|     , 'Date: ' + (new Date()).toUTCString() | ||||
|     , 'Connection: close' | ||||
|     , 'Content-Type: text/html' | ||||
|     , 'Content-Length: 9' | ||||
|     , '' | ||||
|     , 'Not Found' | ||||
|     ].join('\r\n')); | ||||
|   }; | ||||
|   tunnelOpts.handleInsecureHttp = tunnelOpts.handleHttp; | ||||
| , handleHttp:         handleHttp | ||||
| , handleInsecureHttp: handleHttp | ||||
| }; | ||||
| 
 | ||||
|   var tunnelServer = require('stunneld').create(tunnelOpts); | ||||
| module.exports.create = function (deps, config) { | ||||
|   var equal = require('deep-equal'); | ||||
|   var enableDestroy = require('server-destroy'); | ||||
|   var currentOpts = Object.assign({}, defaultConfig); | ||||
| 
 | ||||
|   var httpServer = require('http').createServer(function (req, res) { | ||||
|     // status code 426 = Upgrade Required
 | ||||
|     res.statusCode = 426; | ||||
|     res.setHeader('Content-Type', 'application/json'); | ||||
|     res.end(JSON.stringify({error: { | ||||
|       message: 'Only websockets accepted for tunnel server' | ||||
|     }})); | ||||
|   }); | ||||
|   var wsServer = new (require('ws').Server)({ server: httpServer }); | ||||
|   wsServer.on('connection', tunnelServer.ws); | ||||
|   var httpServer, wsServer, stunneld; | ||||
|   function start() { | ||||
|     if (httpServer || wsServer || stunneld) { | ||||
|       throw new Error('trying to start already started tunnel server'); | ||||
|     } | ||||
|     httpServer = require('http').createServer(rejectNonWebsocket); | ||||
|     enableDestroy(httpServer); | ||||
| 
 | ||||
|     wsServer = new (require('ws').Server)({ server: httpServer }); | ||||
| 
 | ||||
|     var tunnelOpts = Object.assign({}, tunnelFuncs, currentOpts); | ||||
|     stunneld = require('stunneld').create(tunnelOpts); | ||||
|     wsServer.on('connection', stunneld.ws); | ||||
|   } | ||||
| 
 | ||||
|   function stop() { | ||||
|     if (!httpServer || !wsServer || !stunneld) { | ||||
|       throw new Error('trying to stop unstarted tunnel server (or it got into semi-initialized state'); | ||||
|     } | ||||
|     wsServer.close(); | ||||
|     wsServer = null; | ||||
|     httpServer.destroy(); | ||||
|     httpServer = null; | ||||
|     // Nothing to close here, just need to set it to null to allow it to be garbage-collected.
 | ||||
|     stunneld = null; | ||||
|   } | ||||
| 
 | ||||
|   function updateConf() { | ||||
|     var newOpts = Object.assign({}, defaultConfig, config.tunnelServer); | ||||
|     if (!Array.isArray(newOpts.servernames)) { | ||||
|       newOpts.servernames = []; | ||||
|     } | ||||
|     var trimmedOpts = { | ||||
|       servernames: newOpts.servernames.slice().sort() | ||||
|     , secret:      newOpts.secret | ||||
|     }; | ||||
| 
 | ||||
|     if (equal(trimmedOpts, currentOpts)) { | ||||
|       return; | ||||
|     } | ||||
|     currentOpts = trimmedOpts; | ||||
| 
 | ||||
|     // Stop what's currently running, then if we are still supposed to be running then we
 | ||||
|     // can start it again with the updated options. It might be possible to make use of
 | ||||
|     // the existing http and ws servers when the config changes, but I'm not sure what
 | ||||
|     // state the actions needed to close all existing connections would put them in.
 | ||||
|     if (httpServer || wsServer || stunneld) { | ||||
|       stop(); | ||||
|     } | ||||
|     if (currentOpts.servernames.length && currentOpts.secret) { | ||||
|       start(); | ||||
|     } | ||||
|   } | ||||
|   process.nextTick(updateConf); | ||||
| 
 | ||||
|   return { | ||||
|     isAdminDomain: function (domain) { | ||||
|       return config.tunnelServer.servernames.indexOf(domain) !== -1; | ||||
|       return currentOpts.servernames.indexOf(domain) !== -1; | ||||
|     } | ||||
|   , handleAdminConn: function (conn) { | ||||
|       httpServer.emit('connection', conn); | ||||
|       if (!httpServer) { | ||||
|         console.error(new Error('handleAdminConn called with no active tunnel server')); | ||||
|         conn.end(); | ||||
|       } else { | ||||
|         return httpServer.emit('connection', conn); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|   , isClientDomain: tunnelServer.isClientDomain | ||||
|   , handleClientConn: tunnelServer.tcp | ||||
|   , isClientDomain: function (domain) { | ||||
|       if (!stunneld) { return false; } | ||||
|       return stunneld.isClientDomain(domain); | ||||
|     } | ||||
|   , handleClientConn: function (conn) { | ||||
|       if (!stunneld) { | ||||
|         console.error(new Error('handleClientConn called with no active tunnel server')); | ||||
|         conn.end(); | ||||
|       } else { | ||||
|         return stunneld.tcp(conn); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|   , updateConf | ||||
|   }; | ||||
| }; | ||||
|  | ||||
							
								
								
									
										57
									
								
								lib/udp.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								lib/udp.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| module.exports.create = function (deps, config) { | ||||
|   var listeners = require('./servers').listeners.udp; | ||||
| 
 | ||||
|   function packetHandler(port, msg) { | ||||
|     if (!Array.isArray(config.udp.modules)) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     var socket = require('dgram').createSocket('udp4'); | ||||
|     config.udp.modules.forEach(function (mod) { | ||||
|       if (mod.type !== 'forward') { | ||||
|         // To avoid logging bad modules every time we get a UDP packet we assign a warned
 | ||||
|         // property to the module (non-enumerable so it won't be saved to the config or
 | ||||
|         // show up in the API).
 | ||||
|         if (!mod.warned) { | ||||
|           console.warn('found bad DNS module', mod); | ||||
|           Object.defineProperty(mod, 'warned', {value: true, enumerable: false}); | ||||
|         } | ||||
|         return; | ||||
|       } | ||||
|       if (mod.ports.indexOf(port) < 0) { | ||||
|         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 updateListeners() { | ||||
|     var current = listeners.list(); | ||||
|     var wanted = config.udp.bind; | ||||
| 
 | ||||
|     if (!Array.isArray(wanted)) { wanted = []; } | ||||
|     wanted = wanted.map(Number).filter((port) => port > 0 && port < 65356); | ||||
| 
 | ||||
|     current.forEach(function (port) { | ||||
|       if (wanted.indexOf(port) < 0) { | ||||
|         listeners.close(port); | ||||
|       } | ||||
|     }); | ||||
|     wanted.forEach(function (port) { | ||||
|       if (current.indexOf(port) < 0) { | ||||
|         listeners.add(port, packetHandler.bind(port)); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   updateListeners(); | ||||
|   return { | ||||
|     updateConf: updateListeners | ||||
|   }; | ||||
| }; | ||||
| @ -48,13 +48,15 @@ function create(conf) { | ||||
| 
 | ||||
|   modules = { | ||||
|     storage:  require('./storage').create(deps, conf) | ||||
|   , proxy:    require('./proxy-conn').create(deps, conf) | ||||
|   , socks5:   require('./socks5-server').create(deps, conf) | ||||
|   , ddns:     require('./ddns').create(deps, conf) | ||||
|   , mdns:     require('./mdns').create(deps, conf) | ||||
|   , udp:      require('./udp').create(deps, conf) | ||||
|   , tcp:      require('./tcp').create(deps, conf) | ||||
|   , stunneld: require('./tunnel-server-manager').create(deps, config) | ||||
|   }; | ||||
|   Object.assign(deps, modules); | ||||
| 
 | ||||
|   require('./goldilocks.js').create(deps, conf); | ||||
|   process.removeListener('message', create); | ||||
|   process.on('message', update); | ||||
| } | ||||
|  | ||||
							
								
								
									
										2316
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2316
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										35
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								package.json
									
									
									
									
									
								
							| @ -1,13 +1,13 @@ | ||||
| { | ||||
|   "name": "goldilocks", | ||||
|   "version": "1.1.3", | ||||
|   "version": "1.1.6", | ||||
|   "description": "The node.js webserver that's just right, Greenlock (HTTPS/TLS/SSL via ACME/Let's Encrypt) and tunneling (RVPN) included.", | ||||
|   "main": "bin/goldilocks.js", | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git@git.daplie.com:Daplie/goldilocks.js.git" | ||||
|     "url": "git.coolaj86.com:coolaj86/goldilocks.js.git" | ||||
|   }, | ||||
|   "author": "AJ ONeal <aj@daplie.com> (https://daplie.com/)", | ||||
|   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||
|   "license": "(MIT OR Apache-2.0)", | ||||
|   "scripts": { | ||||
|     "test": "node bin/goldilocks.js -p 8443 -d /tmp/" | ||||
| @ -34,42 +34,41 @@ | ||||
|     "server" | ||||
|   ], | ||||
|   "bugs": { | ||||
|     "url": "https://git.daplie.com/Daplie/server-https/issues" | ||||
|     "url": "https://git.coolaj86.com/coolaj86/goldilocks.js/issues" | ||||
|   }, | ||||
|   "homepage": "https://git.daplie.com/Daplie/goldilocks.js", | ||||
|   "homepage": "https://git.coolaj86.com/coolaj86/goldilocks.js", | ||||
|   "dependencies": { | ||||
|     "bluebird": "^3.4.6", | ||||
|     "body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1", | ||||
|     "body-parser": "1", | ||||
|     "commander": "^2.9.0", | ||||
|     "deep-equal": "^1.0.1", | ||||
|     "dns-suite": "git+https://git@git.daplie.com/Daplie/dns-suite#v1", | ||||
|     "express": "git+https://github.com/expressjs/express.git#4.x", | ||||
|     "dns-suite": "1", | ||||
|     "express": "4", | ||||
|     "finalhandler": "^0.4.0", | ||||
|     "greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master", | ||||
|     "greenlock": "2.1", | ||||
|     "http-proxy": "^1.16.2", | ||||
|     "human-readable-ids": "git+https://git.daplie.com/Daplie/human-readable-ids-js#master", | ||||
|     "ipaddr.js": "git+https://github.com/whitequark/ipaddr.js.git#v1.3.0", | ||||
|     "human-readable-ids": "1", | ||||
|     "ipaddr.js": "v1.3", | ||||
|     "js-yaml": "^3.8.3", | ||||
|     "jsonschema": "^1.2.0", | ||||
|     "jsonwebtoken": "^7.4.0", | ||||
|     "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": "2", | ||||
|     "le-challenge-sni": "^2.0.1", | ||||
|     "le-store-certbot": "git+https://git.daplie.com/Daplie/le-store-certbot.git#master", | ||||
|     "le-store-certbot": "2", | ||||
|     "localhost.daplie.me-certificates": "^1.3.5", | ||||
|     "network": "^0.4.0", | ||||
|     "recase": "git+https://git.daplie.com/coolaj86/recase-js.git#v1.0.4", | ||||
|     "recase": "v1.0.4", | ||||
|     "redirect-https": "^1.1.0", | ||||
|     "request": "^2.81.0", | ||||
|     "scmp": "git+https://github.com/freewil/scmp.git#1.x", | ||||
|     "scmp": "1", | ||||
|     "serve-index": "^1.7.0", | ||||
|     "serve-static": "^1.10.0", | ||||
|     "server-destroy": "^1.0.1", | ||||
|     "sni": "^1.0.0", | ||||
|     "socket-pair": "^1.0.3", | ||||
|     "socksv5": "0.0.6", | ||||
|     "stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#v1", | ||||
|     "stunneld": "git+https://git.daplie.com/Daplie/node-tunnel-server.git#v1", | ||||
|     "stunnel": "1.0", | ||||
|     "stunneld": "0.9", | ||||
|     "tunnel-packer": "^1.3.0", | ||||
|     "ws": "^2.3.1" | ||||
|   } | ||||
|  | ||||
							
								
								
									
										3
									
								
								terms.sh
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								terms.sh
									
									
									
									
									
								
							| @ -1,3 +0,0 @@ | ||||
| # adding TOS to TXT DNS Record | ||||
| daplie dns:set -n _terms._cloud.localhost.foo.daplie.me -t TXT -a '{"url":"oauth3.org/tos/draft","explicit":true}' --ttl 3600 | ||||
| daplie dns:set -n _terms._cloud.localhost.alpha.daplie.me -t TXT -a '{"url":"oauth3.org/tos/draft","explicit":true}' --ttl 3600 | ||||
| @ -1,17 +0,0 @@ | ||||
| #!/bin/bash | ||||
| 
 | ||||
| node serve.js \ | ||||
|   --port 8443 \ | ||||
|   --key node_modules/localhost.daplie.me-certificates/privkey.pem \ | ||||
|   --cert node_modules/localhost.daplie.me-certificates/fullchain.pem \ | ||||
|   --root node_modules/localhost.daplie.me-certificates/root.pem \ | ||||
|   -c "$(cat node_modules/localhost.daplie.me-certificates/root.pem)" & | ||||
| 
 | ||||
| PID=$! | ||||
| 
 | ||||
| sleep 1 | ||||
| curl -s --insecure http://localhost.daplie.me:8443 > ./root.pem | ||||
| curl -s https://localhost.daplie.me:8443 --cacert ./root.pem | ||||
| 
 | ||||
| rm ./root.pem | ||||
| kill $PID 2>/dev/null | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user