Compare commits
	
		
			83 Commits
		
	
	
		
			e3c99636c5
			...
			34dff39358
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 34dff39358 | ||
|  | 136431d493 | ||
|  | 4b9e07842d | ||
|  | 43105ba266 | ||
|  | add6745475 | ||
|  | 2969eb3247 | ||
|  | 2c6e5cfa46 | ||
|  | 037c4df6e0 | ||
|  | dd7bc74dad | ||
|  | 12c2fd1819 | ||
|  | a8aedcbc31 | ||
|  | ea010427e8 | ||
|  | d8cc8fe8e6 | ||
|  | 11f2d37044 | ||
|  | 40bd1d9cc6 | ||
|  | 2277b22d9d | ||
|  | 11809030c6 | ||
|  | b6b9d5f2f3 | ||
|  | b307a2bcf2 | ||
|  | 0a233cfcf0 | ||
|  | 4ffad8d3c3 | ||
|  | 0e1437bcd7 | ||
|  | a17f7d52ba | ||
|  | dd035219a3 | ||
|  | 57f97eebdb | ||
|  | ce31c2c02d | ||
|  | 4baf475e35 | ||
|  | 0611645ef0 | ||
|  | 0024d51289 | ||
|  | 62b4c79236 | ||
|  | fbdf0e8a28 | ||
|  | 1382b8b4e2 | ||
|  | 828712bf12 | ||
|  | ccf45ab06e | ||
|  | ac36a35c19 | ||
|  | a2d81e4302 | ||
|  | 6ae1e463c9 | ||
|  | 8ee24fcd77 | ||
|  | 8c34316979 | ||
|  | 011559b1a4 | ||
| 65920f8fce | |||
| 32f2f707cc | |||
| 75d2680830 | |||
| a2d1797d0f | |||
| 0b464cab36 | |||
| 07920b594c | |||
| 0935e3e4b3 | |||
| 35016cd124 | |||
|  | cec4f1ee95 | ||
|  | 4b2e6b1600 | ||
|  | 352b1b0a4a | ||
|  | c40a17dceb | ||
|  | 186a68a0ad | ||
|  | e071b8c3eb | ||
|  | fe477300aa | ||
|  | 278ba38398 | ||
|  | 041138f4b2 | ||
|  | 3bb6dc9680 | ||
|  | 5c7a5c0b2e | ||
|  | 55f81ca1b6 | ||
|  | ecf5f038dd | ||
|  | 307d81690d | ||
|  | 2f06c7fbdc | ||
|  | b332b1fc89 | ||
|  | 33c54149c0 | ||
|  | 669587a07e | ||
|  | 64fc41377f | ||
|  | 680cb05f89 | ||
|  | 847824f97a | ||
|  | 11715f1405 | ||
|  | e0fe188846 | ||
|  | 34ce5ed4ee | ||
|  | 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 | v1.1.3 - Better late than never... here's some stuff we've got | ||||||
|   * fixed (probably) network settings not being readable |   * fixed (probably) network settings not being readable | ||||||
|   * supports timeouts in loopback check |   * supports timeouts in loopback check | ||||||
|  | |||||||
							
								
								
									
										48
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								README.md
									
									
									
									
									
								
							| @ -20,17 +20,51 @@ The node.js netserver that's just right. | |||||||
| Install Standalone | Install Standalone | ||||||
| ------- | ------- | ||||||
| 
 | 
 | ||||||
|  | ### curl | bash | ||||||
|  | 
 | ||||||
| ```bash | ```bash | ||||||
| # v1 in npm | curl -fsSL https://git.daplie.com/Daplie/goldilocks.js/raw/v1.1/installer/get.sh | bash | ||||||
| npm install -g goldilocks | ``` | ||||||
|  | 
 | ||||||
|  | ### git | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | git clone https://git.daplie.com/Daplie/goldilocks.js | ||||||
|  | pushd goldilocks.js | ||||||
|  | git checkout v1.1 | ||||||
|  | bash installer/install.sh | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### npm | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # v1 in git (unauthenticated) | ||||||
|  | npm install -g git+https://git@git.daplie.com:Daplie/goldilocks.js#v1 | ||||||
| 
 | 
 | ||||||
| # v1 in git (via ssh) | # 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.daplie.com:Daplie/goldilocks.js#v1 | ||||||
| 
 | 
 | ||||||
| # v1 in git (unauthenticated) | # v1 in npm | ||||||
| npm install -g git+https://git@git.daplie.com:Daplie/goldilocks.js#v1 | npm install -g goldilocks@v1 | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | ### Uninstall | ||||||
|  | 
 | ||||||
|  | Remove goldilocks and services: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | 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 | ||||||
|  | ----- | ||||||
|  | 
 | ||||||
| ```bash | ```bash | ||||||
| goldilocks | goldilocks | ||||||
| ``` | ``` | ||||||
| @ -271,6 +305,12 @@ tls: | |||||||
|       challenge_type: 'http-01' |       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 | ### tcp | ||||||
| 
 | 
 | ||||||
| The tcp system handles both *raw* and *tls-terminated* tcp network traffic | The tcp system handles both *raw* and *tls-terminated* tcp network traffic | ||||||
|  | |||||||
| @ -19,14 +19,14 @@ StartLimitBurst=3 | |||||||
| 
 | 
 | ||||||
| # User and group the process will run as | # User and group the process will run as | ||||||
| # (www-data is the de facto standard on most systems) | # (www-data is the de facto standard on most systems) | ||||||
| User=www-data | User=MY_USER | ||||||
| Group=www-data | Group=MY_GROUP | ||||||
| 
 | 
 | ||||||
| # If we need to pass environment variables in the future | # If we need to pass environment variables in the future | ||||||
| Environment=GOLDILOCKS_PATH=/srv/www NODE_PATH=/opt/goldilocks/lib/node_modules NPM_CONFIG_PREFIX=/opt/goldilocks | Environment=GOLDILOCKS_PATH=/srv/www NODE_PATH=/opt/goldilocks/lib/node_modules NPM_CONFIG_PREFIX=/opt/goldilocks | ||||||
| 
 | 
 | ||||||
| # Set a sane working directory, sane flags, and specify how to reload the config file | # Set a sane working directory, sane flags, and specify how to reload the config file | ||||||
| WorkingDirectory=/srv/www | WorkingDirectory=/opt/goldilocks | ||||||
| ExecStart=/opt/goldilocks/bin/node /opt/goldilocks/bin/goldilocks --config /etc/goldilocks/goldilocks.yml | ExecStart=/opt/goldilocks/bin/node /opt/goldilocks/bin/goldilocks --config /etc/goldilocks/goldilocks.yml | ||||||
| ExecReload=/bin/kill -USR1 $MAINPID | ExecReload=/bin/kill -USR1 $MAINPID | ||||||
| 
 | 
 | ||||||
| @ -46,7 +46,7 @@ ProtectSystem=full | |||||||
| # … except TLS/SSL, ACME, and Let's Encrypt certificates | # … except TLS/SSL, ACME, and Let's Encrypt certificates | ||||||
| #   and /var/log/goldilocks, because we want a place where logs can go. | #   and /var/log/goldilocks, because we want a place where logs can go. | ||||||
| #   This merely retains r/w access rights, it does not add any new. Must still be writable on the host! | #   This merely retains r/w access rights, it does not add any new. Must still be writable on the host! | ||||||
| ReadWriteDirectories=/etc/goldilocks /etc/ssl /srv/www /var/log/goldilocks | ReadWriteDirectories=/etc/goldilocks /etc/ssl /srv/www /var/log/goldilocks /opt/goldilocks | ||||||
| # you may also want to add other directories such as /opt/goldilocks /etc/acme /etc/letsencrypt | # you may also want to add other directories such as /opt/goldilocks /etc/acme /etc/letsencrypt | ||||||
| 
 | 
 | ||||||
| # Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories | # Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories | ||||||
							
								
								
									
										5
									
								
								dist/etc/tmpfiles.d/goldilocks.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								dist/etc/tmpfiles.d/goldilocks.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | # /etc/tmpfiles.d/goldilocks.conf | ||||||
|  | # See https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html | ||||||
|  | 
 | ||||||
|  | # Type Path           Mode UID      GID      Age Argument | ||||||
|  | d /run/goldilocks     0755 MY_USER  MY_GROUP -   - | ||||||
| @ -1,10 +0,0 @@ | |||||||
| # /etc/tmpfiles.d/goldilocks.conf |  | ||||||
| # See https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html |  | ||||||
| 
 |  | ||||||
| # Type Path           Mode UID      GID      Age Argument |  | ||||||
| d /etc/goldilocks          0755 www-data www-data -   - |  | ||||||
| d /opt/goldilocks          0775 www-data www-data -   - |  | ||||||
| d /srv/www                 0775 www-data www-data -   - |  | ||||||
| d /etc/ssl/goldilocks      0750 www-data www-data -   - |  | ||||||
| d /var/log/goldilocks      0750 www-data www-data -   - |  | ||||||
| #d /run/goldilocks          0755 www-data www-data -   - |  | ||||||
							
								
								
									
										224
									
								
								install.sh
									
									
									
									
									
								
							
							
						
						
									
										224
									
								
								install.sh
									
									
									
									
									
								
							| @ -1,224 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
| 
 |  | ||||||
| # something or other about android and tmux using PREFIX |  | ||||||
| #: "${PREFIX:=''}" |  | ||||||
| MY_ROOT="" |  | ||||||
| if [ -z "${PREFIX-}" ]; then |  | ||||||
|   MY_ROOT="" |  | ||||||
| else |  | ||||||
|   MY_ROOT="$PREFIX" |  | ||||||
| fi |  | ||||||
| # Not every platform has or needs sudo, gotta save them O(1)s... |  | ||||||
| sudo_cmd="" |  | ||||||
| ((EUID)) && [[ -z "$ANDROID_ROOT" ]] && sudo_cmd="sudo" |  | ||||||
| 
 |  | ||||||
| ############################### |  | ||||||
| #                             # |  | ||||||
| #         http_get            # |  | ||||||
| # boilerplate for curl / wget # |  | ||||||
| #                             # |  | ||||||
| ############################### |  | ||||||
| 
 |  | ||||||
| # See https://git.daplie.com/Daplie/daplie-snippets/blob/master/bash/http-get.sh |  | ||||||
| 
 |  | ||||||
| http_get="" |  | ||||||
| http_opts="" |  | ||||||
| http_out="" |  | ||||||
| 
 |  | ||||||
| detect_http_get() |  | ||||||
| { |  | ||||||
|   if type -p curl >/dev/null 2>&1; then |  | ||||||
|     http_get="curl" |  | ||||||
|     http_opts="-fsSL" |  | ||||||
|     http_out="-o" |  | ||||||
|   elif type -p wget >/dev/null 2>&1; then |  | ||||||
|     http_get="wget" |  | ||||||
|     http_opts="--quiet" |  | ||||||
|     http_out="-O" |  | ||||||
|   else |  | ||||||
|     echo "Aborted, could not find curl or wget" |  | ||||||
|     return 7 |  | ||||||
|   fi |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| dap_dl() |  | ||||||
| { |  | ||||||
|   $http_get $http_opts $http_out "$2" "$1" |  | ||||||
|   touch "$2" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| dap_dl_bash() |  | ||||||
| { |  | ||||||
|   dap_url=$1 |  | ||||||
|   #dap_args=$2 |  | ||||||
|   rm -rf dap-tmp-runner.sh |  | ||||||
|   $http_get $http_opts $http_out dap-tmp-runner.sh "$dap_url"; bash dap-tmp-runner.sh; rm dap-tmp-runner.sh |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| detect_http_get |  | ||||||
| 
 |  | ||||||
| ## END HTTP_GET ## |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ################### |  | ||||||
| #                 # |  | ||||||
| # Install service # |  | ||||||
| #                 # |  | ||||||
| ################### |  | ||||||
| 
 |  | ||||||
| my_app_name=goldilocks |  | ||||||
| my_app_pkg_name=com.daplie.goldilocks.web |  | ||||||
| my_app_dir=$(mktemp -d) |  | ||||||
| installer_base="https://git.daplie.com/Daplie/goldilocks.js/raw/master" |  | ||||||
| 
 |  | ||||||
| my_app_etc_config="etc/${my_app_name}/${my_app_name}.yml" |  | ||||||
| my_app_etc_example_config="etc/${my_app_name}/${my_app_name}.example.yml" |  | ||||||
| my_app_systemd_service="etc/systemd/system/${my_app_name}.service" |  | ||||||
| my_app_systemd_tmpfiles="etc/tmpfiles.d/${my_app_name}.conf" |  | ||||||
| my_app_launchd_service="Library/LaunchDaemons/${my_app_pkg_name}.plist" |  | ||||||
| 
 |  | ||||||
| install_for_systemd() |  | ||||||
| { |  | ||||||
|   echo "" |  | ||||||
|   echo "Installing as systemd service" |  | ||||||
|   echo "" |  | ||||||
|   mkdir -p $(dirname "$my_app_dir/$my_app_systemd_service") |  | ||||||
|   dap_dl "$installer_base/$my_app_systemd_service" "$my_app_dir/$my_app_systemd_service" |  | ||||||
|   $sudo_cmd mv "$my_app_dir/$my_app_systemd_service" "$MY_ROOT/$my_app_systemd_service" |  | ||||||
|   $sudo_cmd chown -R root:root "$MY_ROOT/$my_app_systemd_service" |  | ||||||
|   $sudo_cmd chmod 644 "$MY_ROOT/$my_app_systemd_service" |  | ||||||
| 
 |  | ||||||
|   mkdir -p $(dirname "$my_app_dir/$my_app_systemd_tmpfiles") |  | ||||||
|   dap_dl "$installer_base/$my_app_systemd_tmpfiles" "$my_app_dir/$my_app_systemd_tmpfiles" |  | ||||||
|   $sudo_cmd mv "$my_app_dir/$my_app_systemd_tmpfiles" "$MY_ROOT/$my_app_systemd_tmpfiles" |  | ||||||
|   $sudo_cmd chown -R root:root "$MY_ROOT/$my_app_systemd_tmpfiles" |  | ||||||
|   $sudo_cmd chmod 644 "$MY_ROOT/$my_app_systemd_tmpfiles" |  | ||||||
| 
 |  | ||||||
|   $sudo_cmd systemctl stop "${my_app_name}.service" >/dev/null 2>/dev/null |  | ||||||
|   $sudo_cmd systemctl daemon-reload |  | ||||||
|   $sudo_cmd systemctl start "${my_app_name}.service" |  | ||||||
|   $sudo_cmd systemctl enable "${my_app_name}.service" |  | ||||||
| 
 |  | ||||||
|   echo "$my_app_name started with systemctl, check its status like so" |  | ||||||
|   echo "  $sudo_cmd systemctl status $my_app_name" |  | ||||||
|   echo "  $sudo_cmd journalctl -xe -u goldilocks" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| install_for_launchd() |  | ||||||
| { |  | ||||||
|   echo "" |  | ||||||
|   echo "Installing as launchd service" |  | ||||||
|   echo "" |  | ||||||
|   # See http://www.launchd.info/ |  | ||||||
|   mkdir -p $(dirname "$my_app_dir/$my_app_launchd_service") |  | ||||||
|   dap_dl "$installer_base/$my_app_launchd_service" "$my_app_dir/$my_app_launchd_service" |  | ||||||
|   $sudo_cmd mv "$my_app_dir/$my_app_launchd_service" "$MY_ROOT/$my_app_launchd_service" |  | ||||||
|   $sudo_cmd chown root:wheel "$MY_ROOT/$my_app_launchd_service" |  | ||||||
|   $sudo_cmd chmod 0644 "$MY_ROOT/$my_app_launchd_service" |  | ||||||
|   $sudo_cmd launchctl unload -w "$MY_ROOT/$my_app_launchd_service" >/dev/null 2>/dev/null |  | ||||||
|   $sudo_cmd launchctl load -w "$MY_ROOT/$my_app_launchd_service" |  | ||||||
| 
 |  | ||||||
|   echo "$my_app_name started with launchd" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| install_etc_config() |  | ||||||
| { |  | ||||||
|   $sudo_cmd mkdir -p $(dirname "$MY_ROOT/$my_app_etc_example_config") |  | ||||||
|   mkdir -p $(dirname "$my_app_dir/$my_app_etc_example_config") |  | ||||||
|   dap_dl "$installer_base/$my_app_etc_example_config" "$my_app_dir/$my_app_etc_example_config" |  | ||||||
|   $sudo_cmd mv "$my_app_dir/$my_app_etc_example_config" "$MY_ROOT/$my_app_etc_example_config" |  | ||||||
| 
 |  | ||||||
|   if [ ! -e "$MY_ROOT/$my_app_etc_config" ]; then |  | ||||||
|     $sudo_cmd mkdir -p $(dirname "$MY_ROOT/$my_app_etc_config") |  | ||||||
|     mkdir -p $(dirname "$my_app_dir/$my_app_etc_config") |  | ||||||
|     dap_dl "$installer_base/$my_app_etc_config" "$my_app_dir/$my_app_etc_config" |  | ||||||
|     $sudo_cmd mv "$my_app_dir/$my_app_etc_config" "$MY_ROOT/$my_app_etc_config" |  | ||||||
|   fi |  | ||||||
| 
 |  | ||||||
|   # OS X |  | ||||||
|   $sudo_cmd chown -R _www:_www $(dirname "$MY_ROOT/$my_app_etc_config") || true |  | ||||||
|   # Linux |  | ||||||
|   $sudo_cmd chown -R www-data:www-data $(dirname "$MY_ROOT/$my_app_etc_config") || true |  | ||||||
|   $sudo_cmd chmod 775 $(dirname "$MY_ROOT/$my_app_etc_config") |  | ||||||
|   $sudo_cmd chmod 664 "$MY_ROOT/$my_app_etc_config" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| install_service() |  | ||||||
| { |  | ||||||
|   install_etc_config |  | ||||||
| 
 |  | ||||||
|   installable="" |  | ||||||
|   if [ -d "$MY_ROOT/etc/systemd/system" ]; then |  | ||||||
|     install_for_systemd |  | ||||||
|     installable="true" |  | ||||||
|   fi |  | ||||||
|   if [ -d "/Library/LaunchDaemons" ]; then |  | ||||||
|     install_for_launchd |  | ||||||
|     installable="true" |  | ||||||
|   fi |  | ||||||
|   if [ -z "$installable" ]; then |  | ||||||
|     echo "" |  | ||||||
|     echo "Unknown system service init type. You must install as a system service manually." |  | ||||||
|     echo '(please file a bug with the output of "uname -a")' |  | ||||||
|     echo "" |  | ||||||
|   fi |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ## END SERVICE_INSTALL ## |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| set -e |  | ||||||
| set -u |  | ||||||
| 
 |  | ||||||
| # Install |  | ||||||
| # TODO install to tmp location, then move to /opt |  | ||||||
| export NODE_PATH=/opt/goldilocks/lib/node_modules |  | ||||||
| export NPM_CONFIG_PREFIX=/opt/goldilocks |  | ||||||
| $sudo_cmd mkdir -p /etc/goldilocks |  | ||||||
| $sudo_cmd mkdir -p /var/log/goldilocks |  | ||||||
| $sudo_cmd mkdir -p /srv/www |  | ||||||
| $sudo_cmd mkdir -p /var/www |  | ||||||
| $sudo_cmd mkdir -p /opt/goldilocks/{lib,bin,etc} |  | ||||||
| 
 |  | ||||||
| # Dependencies |  | ||||||
| dap_dl_bash "https://git.daplie.com/Daplie/node-install-script/raw/master/setup-min.sh" |  | ||||||
| 
 |  | ||||||
| # Change to user perms |  | ||||||
| 
 |  | ||||||
| # OS X or Linux |  | ||||||
| $sudo_cmd chown -R $(whoami) /opt/goldilocks/ || true |  | ||||||
| 
 |  | ||||||
| my_npm="$NPM_CONFIG_PREFIX/bin/npm" |  | ||||||
| $my_npm install -g npm@4 |  | ||||||
| $my_npm install -g 'git+https://git@git.daplie.com/Daplie/goldilocks.js.git' |  | ||||||
| # Finish up with submodule |  | ||||||
| pushd /opt/goldilocks/lib/node_modules/goldilocks |  | ||||||
| bash ./update-packages.sh |  | ||||||
| popd |  | ||||||
| 
 |  | ||||||
| # Change to admin perms |  | ||||||
| 
 |  | ||||||
| # OS X |  | ||||||
| $sudo_cmd chown -R _www:_www /var/www /srv/www /opt/goldilocks || true |  | ||||||
| # Linux |  | ||||||
| $sudo_cmd chown -R www-data:www-data /var/www /srv/www /opt/goldilocks || true |  | ||||||
| 
 |  | ||||||
| # make sure the files are all read/write for the owner and group, and then set |  | ||||||
| # the setuid and setgid bits so that any files/directories created inside these |  | ||||||
| # directories have the same owner and group. |  | ||||||
| $sudo_cmd chmod -R ug+rwX /opt/goldilocks |  | ||||||
| find /opt/goldilocks -type d -exec $sudo_cmd chmod ug+s {} \; |  | ||||||
| 
 |  | ||||||
| # Uninstall |  | ||||||
| dap_dl "https://git.daplie.com/Daplie/goldilocks.js/raw/master/uninstall.sh" "./goldilocks-uninstall" |  | ||||||
| $sudo_cmd chmod 755 "./goldilocks-uninstall" |  | ||||||
| # OS X |  | ||||||
| $sudo_cmd chown root:wheel "./goldilocks-uninstall" || true |  | ||||||
| # Linux |  | ||||||
| $sudo_cmd chown root:root "./goldilocks-uninstall" || true |  | ||||||
| $sudo_cmd mv "./goldilocks-uninstall" "/usr/local/bin/uninstall-goldilocks" |  | ||||||
| 
 |  | ||||||
| # Install Service |  | ||||||
| install_service |  | ||||||
							
								
								
									
										20
									
								
								installer/get.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								installer/get.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | set -e | ||||||
|  | set -u | ||||||
|  | 
 | ||||||
|  | my_name=goldilocks | ||||||
|  | # TODO provide an option to supply my_ver and my_tmp | ||||||
|  | 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 | ||||||
|  | 
 | ||||||
|  | echo "Installing to $my_tmp (will be moved after install)" | ||||||
|  | pushd $my_tmp/opt/$my_name/lib/node_modules/$my_name | ||||||
|  |   git checkout $my_ver | ||||||
|  |   source ./installer/install.sh | ||||||
|  | popd | ||||||
|  | 
 | ||||||
|  | echo "Installation successful, now cleaning up $my_tmp ..." | ||||||
|  | rm -rf $my_tmp | ||||||
|  | echo "Done" | ||||||
							
								
								
									
										48
									
								
								installer/http-get.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								installer/http-get.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | ############################### | ||||||
|  | #                             # | ||||||
|  | #         http_get            # | ||||||
|  | # boilerplate for curl / wget # | ||||||
|  | #                             # | ||||||
|  | ############################### | ||||||
|  | 
 | ||||||
|  | # See https://git.daplie.com/Daplie/daplie-snippets/blob/master/bash/http-get.sh | ||||||
|  | 
 | ||||||
|  | _h_http_get="" | ||||||
|  | _h_http_opts="" | ||||||
|  | _h_http_out="" | ||||||
|  | 
 | ||||||
|  | detect_http_get() | ||||||
|  | { | ||||||
|  |   set +e | ||||||
|  |   if type -p curl >/dev/null 2>&1; then | ||||||
|  |     _h_http_get="curl" | ||||||
|  |     _h_http_opts="-fsSL" | ||||||
|  |     _h_http_out="-o" | ||||||
|  |   elif type -p wget >/dev/null 2>&1; then | ||||||
|  |     _h_http_get="wget" | ||||||
|  |     _h_http_opts="--quiet" | ||||||
|  |     _h_http_out="-O" | ||||||
|  |   else | ||||||
|  |     echo "Aborted, could not find curl or wget" | ||||||
|  |     return 7 | ||||||
|  |   fi | ||||||
|  |   set -e | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | http_get() | ||||||
|  | { | ||||||
|  |   $_h_http_get $_h_http_opts $_h_http_out "$2" "$1" | ||||||
|  |   touch "$2" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | http_bash() | ||||||
|  | { | ||||||
|  |   _http_url=$1 | ||||||
|  |   #dap_args=$2 | ||||||
|  |   rm -rf dap-tmp-runner.sh | ||||||
|  |   $_h_http_get $_h_http_opts $_h_http_out dap-tmp-runner.sh "$_http_url"; bash dap-tmp-runner.sh; rm dap-tmp-runner.sh | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | detect_http_get | ||||||
|  | 
 | ||||||
|  | ## END HTTP_GET ## | ||||||
							
								
								
									
										17
									
								
								installer/install-for-launchd.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								installer/install-for-launchd.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | set -u | ||||||
|  | 
 | ||||||
|  | my_app_launchd_service="Library/LaunchDaemons/${my_app_pkg_name}.plist" | ||||||
|  | 
 | ||||||
|  | echo "" | ||||||
|  | echo "Installing as launchd service" | ||||||
|  | echo "" | ||||||
|  | 
 | ||||||
|  | # See http://www.launchd.info/ | ||||||
|  | safe_copy_config "$my_app_dist/$my_app_launchd_service" "$my_root/$my_app_launchd_service" | ||||||
|  | 
 | ||||||
|  | $sudo_cmd chown root:wheel "$my_root/$my_app_launchd_service" | ||||||
|  | 
 | ||||||
|  | $sudo_cmd launchctl unload -w "$my_root/$my_app_launchd_service" >/dev/null 2>/dev/null | ||||||
|  | $sudo_cmd launchctl load -w "$my_root/$my_app_launchd_service" | ||||||
|  | 
 | ||||||
|  | echo "$my_app_name started with launchd" | ||||||
							
								
								
									
										35
									
								
								installer/install-for-systemd.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								installer/install-for-systemd.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | set -u | ||||||
|  | 
 | ||||||
|  | my_app_systemd_service="etc/systemd/system/${my_app_name}.service" | ||||||
|  | my_app_systemd_tmpfiles="etc/tmpfiles.d/${my_app_name}.conf" | ||||||
|  | 
 | ||||||
|  | echo "" | ||||||
|  | echo "Installing as systemd service" | ||||||
|  | echo "" | ||||||
|  | 
 | ||||||
|  | sed "s/MY_USER/$my_user/g" "$my_app_dist/$my_app_systemd_service" > "$my_app_dist/$my_app_systemd_service.2" | ||||||
|  | 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" | ||||||
|  | 
 | ||||||
|  | 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 systemctl stop "${my_app_name}.service" >/dev/null 2>/dev/null || true | ||||||
|  | $sudo_cmd systemctl daemon-reload | ||||||
|  | $sudo_cmd systemctl start "${my_app_name}.service" | ||||||
|  | $sudo_cmd systemctl enable "${my_app_name}.service" | ||||||
|  | 
 | ||||||
|  | echo "" | ||||||
|  | echo "" | ||||||
|  | echo "Fun systemd commands to remember:" | ||||||
|  | echo "  $sudo_cmd systemctl daemon-reload" | ||||||
|  | echo "  $sudo_cmd systemctl restart $my_app_name.service" | ||||||
|  | echo "" | ||||||
|  | echo "$my_app_name started with systemctl, check its status like so:" | ||||||
|  | echo "  $sudo_cmd systemctl status $my_app_name" | ||||||
|  | echo "  $sudo_cmd journalctl -xefu $my_app_name" | ||||||
|  | echo "" | ||||||
|  | echo "" | ||||||
							
								
								
									
										37
									
								
								installer/install-system-service.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								installer/install-system-service.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | |||||||
|  | safe_copy_config() | ||||||
|  | { | ||||||
|  |   src=$1 | ||||||
|  |   dst=$2 | ||||||
|  |   $sudo_cmd mkdir -p $(dirname "$dst") | ||||||
|  |   if [ -f "$dst" ]; then | ||||||
|  |     $sudo_cmd rsync -a "$src" "$dst.latest" | ||||||
|  |     # TODO edit config file with $my_user and $my_group | ||||||
|  |     if [ "$(cat $dst)" == "$(cat $dst.latest)" ]; then | ||||||
|  |       $sudo_cmd rm $dst.latest | ||||||
|  |     else | ||||||
|  |       echo "MANUAL INTERVENTION REQUIRED: check the systemd script update and manually decide what you want to do" | ||||||
|  |       echo "diff $dst $dst.latest" | ||||||
|  |       $sudo_cmd chown -R root:root "$dst.latest" | ||||||
|  |     fi | ||||||
|  |   else | ||||||
|  |     $sudo_cmd rsync -a --ignore-existing "$src" "$dst" | ||||||
|  |   fi | ||||||
|  |   $sudo_cmd chown -R root:root "$dst" | ||||||
|  |   $sudo_cmd chmod 644 "$dst" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | installable="" | ||||||
|  | if [ -d "$my_root/etc/systemd/system" ]; then | ||||||
|  |   source ./installer/install-for-systemd.sh | ||||||
|  |   installable="true" | ||||||
|  | fi | ||||||
|  | if [ -d "/Library/LaunchDaemons" ]; then | ||||||
|  |   source ./installer/install-for-launchd.sh | ||||||
|  |   installable="true" | ||||||
|  | fi | ||||||
|  | if [ -z "$installable" ]; then | ||||||
|  |   echo "" | ||||||
|  |   echo "Unknown system service init type. You must install as a system service manually." | ||||||
|  |   echo '(please file a bug with the output of "uname -a")' | ||||||
|  |   echo "" | ||||||
|  | fi | ||||||
							
								
								
									
										149
									
								
								installer/install.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								installer/install.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,149 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
|  | set -e | ||||||
|  | set -u | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ### IMPORTANT ### | ||||||
|  | ###  VERSION  ### | ||||||
|  | my_name=goldilocks | ||||||
|  | my_app_pkg_name=com.daplie.goldilocks.web | ||||||
|  | my_app_ver="v1.1" | ||||||
|  | my_azp_oauth3_ver="v1.2.3" | ||||||
|  | export NODE_VERSION="v8.9.0" | ||||||
|  | 
 | ||||||
|  | if [ -z "${my_tmp-}" ]; then | ||||||
|  |   my_tmp="$(mktemp -d)" | ||||||
|  |   mkdir -p $my_tmp/opt/$my_name/lib/node_modules/$my_name | ||||||
|  |   echo "Installing to $my_tmp (will be moved after install)" | ||||||
|  |   git clone ./ $my_tmp/opt/$my_name/lib/node_modules/$my_name | ||||||
|  |   pushd $my_tmp/opt/$my_name/lib/node_modules/$my_name | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | ################# | ||||||
|  | export NODE_PATH=$my_tmp/opt/$my_name/lib/node_modules | ||||||
|  | export PATH=$my_tmp/opt/$my_name/bin/:$PATH | ||||||
|  | export NPM_CONFIG_PREFIX=$my_tmp/opt/$my_name | ||||||
|  | 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" | ||||||
|  | 
 | ||||||
|  | # Backwards compat | ||||||
|  | # some scripts still use the old names | ||||||
|  | my_app_dir=$my_tmp | ||||||
|  | my_app_name=$my_name | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | git checkout $my_app_ver | ||||||
|  | 
 | ||||||
|  | mkdir -p "$my_tmp/opt/$my_name"/{lib,bin,etc} | ||||||
|  | ln -s ../lib/node_modules/$my_name/bin/$my_name.js $my_tmp/opt/$my_name/bin/$my_name | ||||||
|  | ln -s ../lib/node_modules/$my_name/bin/$my_name.js $my_tmp/opt/$my_name/bin/$my_name.js | ||||||
|  | mkdir -p "$my_tmp/etc/$my_name" | ||||||
|  | chmod 775 "$my_tmp/etc/$my_name" | ||||||
|  | cat "$my_app_dist/etc/$my_name/$my_name.example.yml" > "$my_tmp/etc/$my_name/$my_name.example.yml" | ||||||
|  | chmod 664 "$my_tmp/etc/$my_name/$my_name.example.yml" | ||||||
|  | mkdir -p $my_tmp/srv/www | ||||||
|  | mkdir -p $my_tmp/var/www | ||||||
|  | mkdir -p $my_tmp/var/log/$my_name | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # | ||||||
|  | # Helpers | ||||||
|  | # | ||||||
|  | source ./installer/sudo-cmd.sh | ||||||
|  | source ./installer/http-get.sh | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # | ||||||
|  | # Dependencies | ||||||
|  | # | ||||||
|  | echo $NODE_VERSION > /tmp/NODEJS_VER | ||||||
|  | http_bash "https://git.coolaj86.com/coolaj86/node-installer.sh/raw/v1.1/install.sh" | ||||||
|  | $my_npm install -g npm@4 | ||||||
|  | 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.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 | ||||||
|  |   popd | ||||||
|  | 
 | ||||||
|  |   mkdir -p jquery.com | ||||||
|  |   ln -s jquery.com com.jquery | ||||||
|  |   pushd jquery.com | ||||||
|  |     http_get 'https://code.jquery.com/jquery-3.1.1.js' jquery-3.1.1.js | ||||||
|  |   popd | ||||||
|  | 
 | ||||||
|  |   mkdir -p google.com | ||||||
|  |   ln -s google.com com.google | ||||||
|  |   pushd google.com | ||||||
|  |     http_get 'https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.min.js' angular.1.6.2.min.js | ||||||
|  |   popd | ||||||
|  | 
 | ||||||
|  |   mkdir -p well-known | ||||||
|  |   ln -s well-known .well-known | ||||||
|  |   pushd well-known | ||||||
|  |     ln -snf ../oauth3.org/well-known/oauth3 ./oauth3 | ||||||
|  |   popd | ||||||
|  |   echo "installed dependencies" | ||||||
|  | popd | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # | ||||||
|  | # System Service | ||||||
|  | # | ||||||
|  | source ./installer/my-root.sh | ||||||
|  | echo "Pre-installation to $my_tmp complete, now installing to $my_root/ ..." | ||||||
|  | set +e | ||||||
|  | if type -p tree >/dev/null 2>/dev/null; then | ||||||
|  |   #tree -I "node_modules|include|share" $my_tmp | ||||||
|  |   tree -L 6 -I "include|share|npm" $my_tmp | ||||||
|  | else | ||||||
|  |   ls $my_tmp | ||||||
|  | fi | ||||||
|  | set -e | ||||||
|  | 
 | ||||||
|  | source ./installer/my-user-my-group.sh | ||||||
|  | echo "User $my_user Group $my_group" | ||||||
|  | 
 | ||||||
|  | $sudo_cmd chown -R $my_user:$my_group $my_tmp/* | ||||||
|  | $sudo_cmd chown root:root $my_tmp/* | ||||||
|  | $sudo_cmd chown root:root $my_tmp | ||||||
|  | $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 | ||||||
|  | $sudo_cmd chown -R $my_user:$my_group $my_root/var/www $my_root/srv/www | ||||||
|  | 
 | ||||||
|  | # make sure the files are all read/write for the owner and group, and then set | ||||||
|  | # the setuid and setgid bits so that any files/directories created inside these | ||||||
|  | # directories have the same owner and group. | ||||||
|  | $sudo_cmd chmod -R ug+rwX $my_root/opt/$my_name | ||||||
|  | find $my_root/opt/$my_name -type d -exec $sudo_cmd chmod ug+s {} \; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | echo "" | ||||||
|  | echo "$my_name installation complete!" | ||||||
|  | echo "" | ||||||
|  | echo "" | ||||||
|  | echo "Update the config at: /etc/$my_name/$my_name.yml" | ||||||
|  | echo "" | ||||||
|  | echo "Unistall: rm -rf /srv/$my_name/ /var/$my_name/ /etc/$my_name/ /opt/$my_name/ /var/log/$my_name/ /etc/tmpfiles.d/$my_name.conf /etc/systemd/system/$my_name.service /etc/ssl/$my_name" | ||||||
							
								
								
									
										8
									
								
								installer/my-root.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								installer/my-root.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | # something or other about android and tmux using PREFIX | ||||||
|  | #: "${PREFIX:=''}" | ||||||
|  | my_root="" | ||||||
|  | if [ -z "${PREFIX-}" ]; then | ||||||
|  |   my_root="" | ||||||
|  | else | ||||||
|  |   my_root="$PREFIX" | ||||||
|  | fi | ||||||
							
								
								
									
										19
									
								
								installer/my-user-my-group.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								installer/my-user-my-group.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | if type -p adduser >/dev/null 2>/dev/null; then | ||||||
|  |   if [ -z "$(cat $my_root/etc/passwd | grep $my_app_name)" ]; then | ||||||
|  |     $sudo_cmd adduser --home $my_root/opt/$my_app_name --gecos '' --disabled-password $my_app_name | ||||||
|  |   fi | ||||||
|  |   my_user=$my_app_name | ||||||
|  |   my_group=$my_app_name | ||||||
|  | elif [ -n "$(cat /etc/passwd | grep www-data:)" ]; then | ||||||
|  |   # Linux (Ubuntu) | ||||||
|  |   my_user=www-data | ||||||
|  |   my_group=www-data | ||||||
|  | elif [ -n "$(cat /etc/passwd | grep _www:)" ]; then | ||||||
|  |   # Mac | ||||||
|  |   my_user=_www | ||||||
|  |   my_group=_www | ||||||
|  | else | ||||||
|  |   # Unsure | ||||||
|  |   my_user=$(whoami) | ||||||
|  |   my_group=$(id -g -n) | ||||||
|  | fi | ||||||
							
								
								
									
										7
									
								
								installer/sudo-cmd.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								installer/sudo-cmd.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | # Not every platform has or needs sudo, gotta save them O(1)s... | ||||||
|  | sudo_cmd="" | ||||||
|  | set +e | ||||||
|  | if type -p sudo >/dev/null 2>/dev/null; then | ||||||
|  |   ((EUID)) && [[ -z "${ANDROID_ROOT-}" ]] && sudo_cmd="sudo" | ||||||
|  | fi | ||||||
|  | set -e | ||||||
| @ -174,6 +174,14 @@ var mdnsSchema = { | |||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | var tunnelSvrSchema = { | ||||||
|  |   type: 'object' | ||||||
|  | , properties: { | ||||||
|  |     servernames: { type: 'array', items: { type: 'string' }} | ||||||
|  |   , secret:      { type: 'string' } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| var ddnsSchema = { | var ddnsSchema = { | ||||||
|   type: 'object' |   type: 'object' | ||||||
| , properties: { | , properties: { | ||||||
| @ -223,6 +231,7 @@ var mainSchema = { | |||||||
|   , ddns:   ddnsSchema |   , ddns:   ddnsSchema | ||||||
|   , socks5: socks5Schema |   , socks5: socks5Schema | ||||||
|   , device: deviceSchema |   , device: deviceSchema | ||||||
|  |   , tunnel_server: tunnelSvrSchema | ||||||
|   } |   } | ||||||
| , additionalProperties: false | , 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'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| module.exports.create = function (deps, conf) { | module.exports.create = function (deps, conf, utils) { | ||||||
|   function dnsType(addr) { |   function dnsType(addr) { | ||||||
|     if (/^\d+\.\d+\.\d+\.\d+$/.test(addr)) { |     if (/^\d+\.\d+\.\d+\.\d+$/.test(addr)) { | ||||||
|       return 'A'; |       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) { |   async function setDeviceAddress(session, addr, domains) { | ||||||
|     var directives = await deps.OAUTH3.discover(session.token.aud); |     var directives = await deps.OAUTH3.discover(session.token.aud); | ||||||
| 
 | 
 | ||||||
| @ -111,7 +55,7 @@ module.exports.create = function (deps, conf) { | |||||||
|       return goodAddrDomains.indexOf(domain) < 0; |       return goodAddrDomains.indexOf(domain) < 0; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     var oldDns = await splitDomains(directives.api, badAddrDomains); |     var oldDns = await utils.splitDomains(directives.api, badAddrDomains); | ||||||
|     var common = { |     var common = { | ||||||
|       api: 'devices.detach' |       api: 'devices.detach' | ||||||
|     , session: session |     , session: session | ||||||
| @ -124,7 +68,7 @@ module.exports.create = function (deps, conf) { | |||||||
|       console.log('removed bad DNS records for ' + badAddrDomains.join(', ')); |       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 = { |     common = { | ||||||
|       api: 'devices.attach' |       api: 'devices.attach' | ||||||
|     , session: session |     , session: session | ||||||
| @ -169,7 +113,7 @@ module.exports.create = function (deps, conf) { | |||||||
|   async function removeDomains(session, domains) { |   async function removeDomains(session, domains) { | ||||||
|     var directives = await deps.OAUTH3.discover(session.token.aud); |     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 = { |     var common = { | ||||||
|       api: 'devices.detach' |       api: 'devices.detach' | ||||||
|     , session: session |     , session: session | ||||||
|  | |||||||
| @ -3,48 +3,21 @@ | |||||||
| module.exports.create = function (deps, conf) { | module.exports.create = function (deps, conf) { | ||||||
|   var dns = deps.PromiseA.promisifyAll(require('dns')); |   var dns = deps.PromiseA.promisifyAll(require('dns')); | ||||||
|   var network = deps.PromiseA.promisifyAll(deps.recase.camelCopy(require('network'))); |   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 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; |   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; |   var tunnelActive = false; | ||||||
|   async function startTunnel(tunnelSession, mod, domainList) { |   async function startTunnel(tunnelSession, mod, domainList) { | ||||||
|     try { |     try { | ||||||
|       var dnsSession = await getSession(mod.tokenId); |       var dnsSession = await utils.getSession(mod.tokenId); | ||||||
|       var tunnelDomain = await deps.tunnelClients.start(tunnelSession || dnsSession, domainList); |       var tunnelDomain = await tunnelClients.start(tunnelSession || dnsSession, domainList); | ||||||
| 
 | 
 | ||||||
|       var addrList; |       var addrList; | ||||||
|       try { |       try { | ||||||
| @ -59,7 +32,9 @@ module.exports.create = function (deps, conf) { | |||||||
|         throw new Error('failed to lookup IP for tunnel domain "' + tunnelDomain + '"'); |         throw new Error('failed to lookup IP for tunnel domain "' + tunnelDomain + '"'); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       if (!mod.disabled) { | ||||||
|         await dnsCtrl.setDeviceAddress(dnsSession, addrList[0], domainList); |         await dnsCtrl.setDeviceAddress(dnsSession, addrList[0], domainList); | ||||||
|  |       } | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       console.log('error starting tunnel for', domainList.join(', ')); |       console.log('error starting tunnel for', domainList.join(', ')); | ||||||
|       console.log(err); |       console.log(err); | ||||||
| @ -73,7 +48,7 @@ module.exports.create = function (deps, conf) { | |||||||
|       tunnelSession = await deps.storage.tokens.get(conf.ddns.tunnel.tokenId); |       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; } |       if (mod.type !== 'dns@oauth3.org') { return null; } | ||||||
| 
 | 
 | ||||||
|       return startTunnel(tunnelSession, mod, domainList); |       return startTunnel(tunnelSession, mod, domainList); | ||||||
| @ -82,14 +57,14 @@ module.exports.create = function (deps, conf) { | |||||||
|     tunnelActive = true; |     tunnelActive = true; | ||||||
|   } |   } | ||||||
|   async function disconnectTunnels() { |   async function disconnectTunnels() { | ||||||
|     deps.tunnelClients.disconnect(); |     tunnelClients.disconnect(); | ||||||
|     tunnelActive = false; |     tunnelActive = false; | ||||||
|     await Promise.resolve(); |     await Promise.resolve(); | ||||||
|   } |   } | ||||||
|   async function checkTunnelTokens() { |   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; } |       if (mod.type !== 'dns@oauth3.org') { return null; } | ||||||
| 
 | 
 | ||||||
|       var domainStr = domainList.slice().sort().join(','); |       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; } |     if (!newTokens.length) { return; } | ||||||
| 
 | 
 | ||||||
| @ -187,10 +162,10 @@ module.exports.create = function (deps, conf) { | |||||||
|     } |     } | ||||||
|     publicAddress = addr; |     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; } |       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); |         return dnsCtrl.setDeviceAddress(session, addr, domainList); | ||||||
|       }).catch(function (err) { |       }).catch(function (err) { | ||||||
|         console.log('error setting DNS records for', domainList.join(', ')); |         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
 |     // 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.
 |     // 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; } |       if (mod.type !== 'dns@oauth3.org') { return; } | ||||||
| 
 | 
 | ||||||
|       prevMods[mod.id] = { mod, domainList }; |       prevMods[mod.id] = { mod, domainList }; | ||||||
|       return true; |       return true; | ||||||
|     }, prevConf); |     }, prevConf); | ||||||
|     iterateAllModules(function (mod, domainList) { |     utils.iterateAllModules(function (mod, domainList) { | ||||||
|       if (mod.type !== 'dns@oauth3.org') { return; } |       if (mod.type !== 'dns@oauth3.org') { return; } | ||||||
| 
 | 
 | ||||||
|       curMods[mod.id] = { mod, domainList }; |       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.
 |     // Then remove DNS records for the domains that we are no longer responsible for.
 | ||||||
|     await Promise.all(Object.values(prevMods).map(function ({mod, domainList}) { |     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; |       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(); |         oldDomains = domainList.slice(); | ||||||
|       } else { |       } else { | ||||||
|         oldDomains = domainList.filter(function (domain) { |         oldDomains = domainList.filter(function (domain) { | ||||||
| @ -249,7 +227,7 @@ module.exports.create = function (deps, conf) { | |||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return getSession(mod.tokenId).then(function (session) { |       return utils.getSession(mod.tokenId).then(function (session) { | ||||||
|         return dnsCtrl.removeDomains(session, oldDomains); |         return dnsCtrl.removeDomains(session, oldDomains); | ||||||
|       }); |       }); | ||||||
|     }).filter(Boolean)); |     }).filter(Boolean)); | ||||||
| @ -259,6 +237,9 @@ module.exports.create = function (deps, conf) { | |||||||
| 
 | 
 | ||||||
|     // And add DNS records for any newly added domains.
 |     // And add DNS records for any newly added domains.
 | ||||||
|     await Promise.all(Object.values(curMods).map(function ({mod, domainList}) { |     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; |       var newDomains; | ||||||
|       if (!prevMods[mod.id] || mod.tokenId !== prevMods[mod.id].mod.tokenId) { |       if (!prevMods[mod.id] || mod.tokenId !== prevMods[mod.id].mod.tokenId) { | ||||||
|         newDomains = domainList.slice(); |         newDomains = domainList.slice(); | ||||||
| @ -274,7 +255,7 @@ module.exports.create = function (deps, conf) { | |||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return getSession(mod.tokenId).then(function (session) { |       return utils.getSession(mod.tokenId).then(function (session) { | ||||||
|         return dnsCtrl.setDeviceAddress(session, publicAddress, newDomains); |         return dnsCtrl.setDeviceAddress(session, publicAddress, newDomains); | ||||||
|       }); |       }); | ||||||
|     }).filter(Boolean)); |     }).filter(Boolean)); | ||||||
| @ -340,5 +321,6 @@ module.exports.create = function (deps, conf) { | |||||||
|   , getDeviceAddresses: dnsCtrl.getDeviceAddresses |   , getDeviceAddresses: dnsCtrl.getDeviceAddresses | ||||||
|   , recheckPubAddr:     recheckPubAddr |   , recheckPubAddr:     recheckPubAddr | ||||||
|   , updateConf:         updateConf |   , updateConf:         updateConf | ||||||
|  |   , challenge | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -6,6 +6,52 @@ module.exports.create = function (deps, config) { | |||||||
|   var activeTunnels = {}; |   var activeTunnels = {}; | ||||||
|   var activeDomains = {}; |   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) { |   function fillData(data) { | ||||||
|     if (typeof data === 'string') { |     if (typeof data === 'string') { | ||||||
|       data = { jwt: data }; |       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.
 |       // get the promise that should tell us more about if it worked or not.
 | ||||||
|       activeTunnels[data.tunnelUrl] = stunnel.connect({ |       activeTunnels[data.tunnelUrl] = stunnel.connect({ | ||||||
|         stunneld: data.tunnelUrl |         stunneld: data.tunnelUrl | ||||||
|       , net: deps.tunnel.net |       , net: customNet | ||||||
|         // NOTE: the ports here aren't that important since we are providing a custom
 |         // 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
 |         // `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
 |         // 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); |  | ||||||
| }; |  | ||||||
							
								
								
									
										80
									
								
								lib/mdns.js
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								lib/mdns.js
									
									
									
									
									
								
							| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| var PromiseA = require('bluebird'); | var PromiseA = require('bluebird'); | ||||||
| var queryName = '_cloud._tcp.local'; | var queryName = '_cloud._tcp.local'; | ||||||
|  | var dnsSuite = require('dns-suite'); | ||||||
| 
 | 
 | ||||||
| function createResponse(name, ownerIds, packet, ttl, mainPort) { | function createResponse(name, ownerIds, packet, ttl, mainPort) { | ||||||
|   var rpacket = { |   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) { | module.exports.create = function (deps, config) { | ||||||
|   var socket = require('dgram').createSocket({ type: 'udp4', reuseAddr: true }); |   var socket; | ||||||
|   var dns = require('dns-suite'); |  | ||||||
|   var nextBroadcast = -1; |   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);
 |     // console.log('Received %d bytes from %s:%d', message.length, rinfo.address, rinfo.port);
 | ||||||
| 
 | 
 | ||||||
|     var packet; |     var packet; | ||||||
|     try { |     try { | ||||||
|       packet = dns.DNSPacket.parse(message); |       packet = dnsSuite.DNSPacket.parse(message); | ||||||
|     } |     } | ||||||
|     catch (er) { |     catch (er) { | ||||||
|       // `dns-suite` actually errors on a lot of the packets floating around in our network,
 |       // `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.
 |     // Only respond to queries.
 | ||||||
|     if (packet.header.qr !== 0) { |     if (packet.header.qr !== 0) {  return; } | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     // Only respond if they were asking for cloud devices.
 |     // Only respond if they were asking for cloud devices.
 | ||||||
|     if (packet.question.length !== 1 || packet.question[0].name !== queryName) { |     if (packet.question.length !== 1)           { return; } | ||||||
|       return; |     if (packet.question[0].name !== queryName)  { return; } | ||||||
|     } |     if (packet.question[0].typeName !== 'PTR')  { return; } | ||||||
|     if (packet.question[0].typeName !== 'PTR' || packet.question[0].className !== 'IN' ) { |     if (packet.question[0].className !== 'IN' ) { return; } | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     var proms = [ |     var proms = [ | ||||||
|       deps.storage.mdnsId.get() |       deps.storage.mdnsId.get() | ||||||
| @ -131,7 +127,7 @@ module.exports.start = function (deps, config, mainPort) { | |||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     PromiseA.all(proms).then(function (results) { |     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(); |       var now = Date.now(); | ||||||
|       if (now > nextBroadcast) { |       if (now > nextBroadcast) { | ||||||
|         socket.send(resp, config.mdns.port, config.mdns.broadcast); |         socket.send(resp, config.mdns.port, config.mdns.broadcast); | ||||||
| @ -140,7 +136,14 @@ module.exports.start = function (deps, config, mainPort) { | |||||||
|         socket.send(resp, rinfo.port, rinfo.address); |         socket.send(resp, rinfo.port, rinfo.address); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   }); |   } | ||||||
|  | 
 | ||||||
|  |   function start() { | ||||||
|  |     socket = require('dgram').createSocket({ type: 'udp4', reuseAddr: true }); | ||||||
|  |     socket.on('message', handlePacket); | ||||||
|  | 
 | ||||||
|  |     return new Promise(function (resolve, reject) { | ||||||
|  |       socket.once('error', reject); | ||||||
| 
 | 
 | ||||||
|       socket.bind(config.mdns.port, function () { |       socket.bind(config.mdns.port, function () { | ||||||
|         var addr = this.address(); |         var addr = this.address(); | ||||||
| @ -153,5 +156,48 @@ module.exports.start = function (deps, config, mainPort) { | |||||||
|         // much more difficult for someone to use us as part of a DDoS attack by
 |         // much more difficult for someone to use us as part of a DDoS attack by
 | ||||||
|         // spoofing the UDP address a request came from.
 |         // spoofing the UDP address a request came from.
 | ||||||
|         socket.setTTL(1); |         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) { | ||||||
|       if (stat._closing) { |       if (stat._closing) { | ||||||
|         module.exports.destroyTcpListener(port); |         stat.server.destroy(); | ||||||
|       } |       } else { | ||||||
|       else if (handler !== stat.handler) { |         // 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
 | ||||||
|         // we'll replace the current listener
 |         // if the handlers are the same). Until there is reason to do otherwise we are
 | ||||||
|  |         // opting for the replacement.
 | ||||||
|         stat.handler = handler; |         stat.handler = handler; | ||||||
|         resolve(); |         resolve(); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       else { |  | ||||||
|         // this exact listener is already open
 |  | ||||||
|         resolve(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     var enableDestroy = require('server-destroy'); |     var enableDestroy = require('server-destroy'); | ||||||
| @ -34,7 +30,7 @@ module.exports.addTcpListener = function (port, handler) { | |||||||
|     stat = serversMap[port] = { |     stat = serversMap[port] = { | ||||||
|       server: server |       server: server | ||||||
|     , handler: handler |     , handler: handler | ||||||
|     , _closing: null |     , _closing: false | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     // Add .destroy so we can close all open connections. Better if added before listen
 |     // 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) { |   return new PromiseA(function (resolve) { | ||||||
|     var stat = serversMap[port]; |     var stat = serversMap[port]; | ||||||
|     if (!stat) { |     if (!stat) { | ||||||
|       resolve(); |       resolve(); | ||||||
|       return; |       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(); |     stat.server.close(); | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| @ -84,7 +90,9 @@ module.exports.destroyTcpListener = function (port) { | |||||||
|   } |   } | ||||||
| }; | }; | ||||||
| module.exports.listTcpListeners = function () { | 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) { |   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) { |       start(config.socks5.port).catch(function (err) { | ||||||
|         console.error('failed to start Socks5 proxy', err); |         console.error('failed to start Socks5 proxy', err); | ||||||
|       }); |       }); | ||||||
|  |       configEnabled = true; | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|  |   process.nextTick(updateConf); | ||||||
| 
 | 
 | ||||||
|   return { |   return { | ||||||
|     curState: curState |     curState | ||||||
|   , start: start |   , start | ||||||
|   , stop: stop |   , stop | ||||||
|  |   , updateConf | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| module.exports.create = function (deps, conf, greenlockMiddleware) { | module.exports.create = function (deps, conf, tcpMods) { | ||||||
|   var PromiseA = require('bluebird'); |   var PromiseA = require('bluebird'); | ||||||
|   var statAsync = PromiseA.promisify(require('fs').stat); |   var statAsync = PromiseA.promisify(require('fs').stat); | ||||||
|   var domainMatches = require('../domain-utils').match; |   var domainMatches = require('../domain-utils').match; | ||||||
| @ -162,8 +162,8 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { | |||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (deps.tunnelServer.isClientDomain(separatePort(headers.host).host)) { |     if (deps.stunneld.isClientDomain(separatePort(headers.host).host)) { | ||||||
|       deps.tunnelServer.handleClientConn(conn); |       deps.stunneld.handleClientConn(conn); | ||||||
|       process.nextTick(function () { |       process.nextTick(function () { | ||||||
|         conn.unshift(opts.firstChunk); |         conn.unshift(opts.firstChunk); | ||||||
|         conn.resume(); |         conn.resume(); | ||||||
| @ -172,7 +172,7 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!acmeServer) { |     if (!acmeServer) { | ||||||
|       acmeServer = require('http').createServer(greenlockMiddleware); |       acmeServer = require('http').createServer(tcpMods.tls.middleware); | ||||||
|     } |     } | ||||||
|     return emitConnection(acmeServer, conn, opts); |     return emitConnection(acmeServer, conn, opts); | ||||||
|   } |   } | ||||||
| @ -214,8 +214,8 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { | |||||||
|       return emitConnection(adminServer, conn, opts); |       return emitConnection(adminServer, conn, opts); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (deps.tunnelServer.isAdminDomain(host)) { |     if (deps.stunneld.isAdminDomain(host)) { | ||||||
|       deps.tunnelServer.handleAdminConn(conn); |       deps.stunneld.handleAdminConn(conn); | ||||||
|       process.nextTick(function () { |       process.nextTick(function () { | ||||||
|         conn.unshift(opts.firstChunk); |         conn.unshift(opts.firstChunk); | ||||||
|         conn.resume(); |         conn.resume(); | ||||||
| @ -241,7 +241,7 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { | |||||||
|       res.statusCode = 502; |       res.statusCode = 502; | ||||||
|       res.setHeader('Connection', 'close'); |       res.setHeader('Connection', 'close'); | ||||||
|       res.setHeader('Content-Type', 'text/html'); |       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) { |     proxyServer = http.createServer(function (req, res) { | ||||||
| @ -292,7 +292,7 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { | |||||||
|     newConnOpts.remoteAddress = opts.address || conn.remoteAddress; |     newConnOpts.remoteAddress = opts.address || conn.remoteAddress; | ||||||
|     newConnOpts.remotePort    = opts.port    || conn.remotePort; |     newConnOpts.remotePort    = opts.port    || conn.remotePort; | ||||||
| 
 | 
 | ||||||
|     deps.proxy(conn, newConnOpts, opts.firstChunk); |     tcpMods.proxy(conn, newConnOpts, opts.firstChunk); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function checkProxy(mod, conn, opts, headers) { |   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.sendBadGateway = sendBadGateway; | ||||||
| 
 | 
 | ||||||
| module.exports.create = function (deps, config) { | module.exports.create = function (deps, config) { | ||||||
|   return function proxy(conn, newConnOpts, firstChunk, decrypt) { |   function proxy(conn, newConnOpts, firstChunk, decrypt) { | ||||||
|     var connected = false; |     var connected = false; | ||||||
|     newConnOpts.allowHalfOpen = true; |     newConnOpts.allowHalfOpen = true; | ||||||
|     var newConn = deps.net.createConnection(newConnOpts, function () { |     var newConn = deps.net.createConnection(newConnOpts, function () { | ||||||
| @ -73,5 +73,9 @@ module.exports.create = function (deps, config) { | |||||||
|     newConn.on('close', function () { |     newConn.on('close', function () { | ||||||
|       conn.destroy(); |       conn.destroy(); | ||||||
|     }); |     }); | ||||||
|   }; |   } | ||||||
|  | 
 | ||||||
|  |   proxy.getRespBody = getRespBody; | ||||||
|  |   proxy.sendBadGateway = sendBadGateway; | ||||||
|  |   return proxy; | ||||||
| }; | }; | ||||||
| @ -1,6 +1,6 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| module.exports.create = function (deps, config, netHandler) { | module.exports.create = function (deps, config, tcpMods) { | ||||||
|   var path = require('path'); |   var path = require('path'); | ||||||
|   var tls = require('tls'); |   var tls = require('tls'); | ||||||
|   var parseSni = require('sni'); |   var parseSni = require('sni'); | ||||||
| @ -86,8 +86,7 @@ module.exports.create = function (deps, config, netHandler) { | |||||||
|   , challenges: { |   , challenges: { | ||||||
|       'http-01': require('le-challenge-fs').create({ debug: config.debug }) |       'http-01': require('le-challenge-fs').create({ debug: config.debug }) | ||||||
|     , 'tls-sni-01': require('le-challenge-sni').create({ debug: config.debug }) |     , 'tls-sni-01': require('le-challenge-sni').create({ debug: config.debug }) | ||||||
|       // TODO dns-01
 |     , 'dns-01': deps.ddns.challenge | ||||||
|       //, 'dns-01': require('le-challenge-ddns').create({ debug: config.debug })
 |  | ||||||
|     } |     } | ||||||
|   , challengeType: 'http-01' |   , challengeType: 'http-01' | ||||||
| 
 | 
 | ||||||
| @ -208,7 +207,7 @@ module.exports.create = function (deps, config, netHandler) { | |||||||
|   var terminateServer = tls.createServer(terminatorOpts, function (socket) { |   var terminateServer = tls.createServer(terminatorOpts, function (socket) { | ||||||
|     console.log('(post-terminated) tls connection, addr:', extractSocketProp(socket, 'remoteAddress')); |     console.log('(post-terminated) tls connection, addr:', extractSocketProp(socket, 'remoteAddress')); | ||||||
| 
 | 
 | ||||||
|     netHandler(socket, { |     tcpMods.tcpHandler(socket, { | ||||||
|       servername: socket.servername |       servername: socket.servername | ||||||
|     , encrypted: true |     , encrypted: true | ||||||
|       // remoteAddress... ugh... https://github.com/nodejs/node/issues/8854
 |       // 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.remoteAddress = opts.address || extractSocketProp(socket, 'remoteAddress'); | ||||||
|     newConnOpts.remotePort    = opts.port    || extractSocketProp(socket, 'remotePort'); |     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
 |       // 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.
 |       // the socket so the proxy module can send a 502 HTTP response.
 | ||||||
|       var tlsOpts = localhostCerts.mergeTlsOptions('localhost.daplie.me', {isServer: true}); |       var tlsOpts = localhostCerts.mergeTlsOptions('localhost.daplie.me', {isServer: true}); | ||||||
| @ -291,8 +290,8 @@ module.exports.create = function (deps, config, netHandler) { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (deps.tunnelServer.isClientDomain(opts.servername)) { |     if (deps.stunneld.isClientDomain(opts.servername)) { | ||||||
|       deps.tunnelServer.handleClientConn(socket); |       deps.stunneld.handleClientConn(socket); | ||||||
|       if (!opts.hyperPeek) { |       if (!opts.hyperPeek) { | ||||||
|         process.nextTick(function () { |         process.nextTick(function () { | ||||||
|           socket.unshift(opts.firstChunk); |           socket.unshift(opts.firstChunk); | ||||||
| @ -1,26 +1,10 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| module.exports.create = function (deps, config) { | function httpsTunnel(servername, conn) { | ||||||
|   if (!config.tunnelServer || !Array.isArray(config.tunnelServer.servernames) || !config.tunnelServer.secret) { |  | ||||||
|     return { |  | ||||||
|       isAdminDomain:  function () { return false; } |  | ||||||
|     , isClientDomain: function () { return false; } |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   var tunnelOpts = Object.assign({}, config.tunnelServer); |  | ||||||
|   // This function 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); |   console.error('tunnel server received encrypted connection to', servername); | ||||||
|   conn.end(); |   conn.end(); | ||||||
|   }; | } | ||||||
|   tunnelOpts.httpsInvalid = tunnelOpts.httpsTunnel; | function handleHttp(servername, conn) { | ||||||
|   // This 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); |   console.error('tunnel server received un-encrypted connection to', servername); | ||||||
|   conn.end([ |   conn.end([ | ||||||
|     'HTTP/1.1 404 Not Found' |     'HTTP/1.1 404 Not Found' | ||||||
| @ -31,31 +15,117 @@ module.exports.create = function (deps, config) { | |||||||
|   , '' |   , '' | ||||||
|   , 'Not Found' |   , 'Not Found' | ||||||
|   ].join('\r\n')); |   ].join('\r\n')); | ||||||
|   }; | } | ||||||
|   tunnelOpts.handleInsecureHttp = tunnelOpts.handleHttp; | function rejectNonWebsocket(req, res) { | ||||||
| 
 |  | ||||||
|   var tunnelServer = require('stunneld').create(tunnelOpts); |  | ||||||
| 
 |  | ||||||
|   var httpServer = require('http').createServer(function (req, res) { |  | ||||||
|   // status code 426 = Upgrade Required
 |   // status code 426 = Upgrade Required
 | ||||||
|   res.statusCode = 426; |   res.statusCode = 426; | ||||||
|   res.setHeader('Content-Type', 'application/json'); |   res.setHeader('Content-Type', 'application/json'); | ||||||
|     res.end(JSON.stringify({error: { |   res.send({error: { message: 'Only websockets accepted for tunnel server' }}); | ||||||
|       message: 'Only websockets accepted for tunnel server' | } | ||||||
|     }})); | 
 | ||||||
|   }); | var defaultConfig = { | ||||||
|   var wsServer = new (require('ws').Server)({ server: httpServer }); |   servernames: [] | ||||||
|   wsServer.on('connection', tunnelServer.ws); | , 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.
 | ||||||
|  |   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.
 | ||||||
|  | , handleHttp:         handleHttp | ||||||
|  | , handleInsecureHttp: handleHttp | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | module.exports.create = function (deps, config) { | ||||||
|  |   var equal = require('deep-equal'); | ||||||
|  |   var enableDestroy = require('server-destroy'); | ||||||
|  |   var currentOpts = Object.assign({}, defaultConfig); | ||||||
|  | 
 | ||||||
|  |   var httpServer, wsServer, stunneld; | ||||||
|  |   function start() { | ||||||
|  |     if (httpServer || wsServer || stunneld) { | ||||||
|  |       throw new Error('trying to start already started tunnel server'); | ||||||
|  |     } | ||||||
|  |     httpServer = require('http').createServer(rejectNonWebsocket); | ||||||
|  |     enableDestroy(httpServer); | ||||||
|  | 
 | ||||||
|  |     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 { |   return { | ||||||
|     isAdminDomain: function (domain) { |     isAdminDomain: function (domain) { | ||||||
|       return config.tunnelServer.servernames.indexOf(domain) !== -1; |       return currentOpts.servernames.indexOf(domain) !== -1; | ||||||
|     } |     } | ||||||
|   , handleAdminConn: function (conn) { |   , 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 |   , isClientDomain: function (domain) { | ||||||
|   , handleClientConn: tunnelServer.tcp |       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 = { |   modules = { | ||||||
|     storage:  require('./storage').create(deps, conf) |     storage:  require('./storage').create(deps, conf) | ||||||
|   , proxy:    require('./proxy-conn').create(deps, conf) |  | ||||||
|   , socks5:   require('./socks5-server').create(deps, conf) |   , socks5:   require('./socks5-server').create(deps, conf) | ||||||
|   , ddns:     require('./ddns').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); |   Object.assign(deps, modules); | ||||||
| 
 | 
 | ||||||
|   require('./goldilocks.js').create(deps, conf); |  | ||||||
|   process.removeListener('message', create); |   process.removeListener('message', create); | ||||||
|   process.on('message', update); |   process.on('message', update); | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										2316
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2316
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "goldilocks", |   "name": "goldilocks", | ||||||
|   "version": "1.1.3", |   "version": "1.1.5", | ||||||
|   "description": "The node.js webserver that's just right, Greenlock (HTTPS/TLS/SSL via ACME/Let's Encrypt) and tunneling (RVPN) included.", |   "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", |   "main": "bin/goldilocks.js", | ||||||
|   "repository": { |   "repository": { | ||||||
| @ -52,7 +52,6 @@ | |||||||
|     "js-yaml": "^3.8.3", |     "js-yaml": "^3.8.3", | ||||||
|     "jsonschema": "^1.2.0", |     "jsonschema": "^1.2.0", | ||||||
|     "jsonwebtoken": "^7.4.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": "git+https://git.daplie.com/Daplie/le-challenge-webroot.git#master", | ||||||
|     "le-challenge-sni": "^2.0.1", |     "le-challenge-sni": "^2.0.1", | ||||||
|     "le-store-certbot": "git+https://git.daplie.com/Daplie/le-store-certbot.git#master", |     "le-store-certbot": "git+https://git.daplie.com/Daplie/le-store-certbot.git#master", | ||||||
|  | |||||||
							
								
								
									
										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 |  | ||||||
							
								
								
									
										48
									
								
								uninstall.sh
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								uninstall.sh
									
									
									
									
									
								
							| @ -1,48 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
| 
 |  | ||||||
| # something or other about android and tmux using PREFIX |  | ||||||
| #: "${PREFIX:=''}" |  | ||||||
| MY_ROOT="" |  | ||||||
| if [ -z "${PREFIX-}" ]; then |  | ||||||
|   MY_ROOT="" |  | ||||||
| else |  | ||||||
|   MY_ROOT="$PREFIX" |  | ||||||
| fi |  | ||||||
| # Not every platform has or needs sudo, gotta save them O(1)s... |  | ||||||
| sudo_cmd="" |  | ||||||
| ((EUID)) && [[ -z "$ANDROID_ROOT" ]] && sudo_cmd="sudo" |  | ||||||
| 
 |  | ||||||
| # you don't want any oopsies when an rm -rf is involved... |  | ||||||
| set -e |  | ||||||
| set -u |  | ||||||
| 
 |  | ||||||
| my_app_name=goldilocks |  | ||||||
| my_app_pkg_name=com.daplie.goldilocks.web |  | ||||||
| 
 |  | ||||||
| my_app_etc_config="etc/${my_app_name}/${my_app_name}.yml" |  | ||||||
| my_app_systemd_service="etc/systemd/system/${my_app_name}.service" |  | ||||||
| my_app_systemd_tmpfiles="etc/tmpfiles.d/${my_app_name}.conf" |  | ||||||
| my_app_launchd_service="Library/LaunchDaemons/${my_app_pkg_name}.plist" |  | ||||||
| my_app_upstart_service="etc/init.d/${my_app_name}.conf" |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| $sudo_cmd rm -f /usr/local/bin/$my_app_name |  | ||||||
| $sudo_cmd rm -f /usr/local/bin/uninstall-$my_app_name |  | ||||||
| $sudo_cmd rm -rf /usr/local/lib/node_modules/$my_app_name |  | ||||||
| 
 |  | ||||||
| $sudo_cmd rm -f "$MY_ROOT/$my_app_etc_config" |  | ||||||
| $sudo_cmd rmdir -p $(dirname "$MY_ROOT/$my_app_etc_config") 2>/dev/null || true |  | ||||||
| $sudo_cmd rm -f "$MY_ROOT/$my_app_systemd_service" |  | ||||||
| $sudo_cmd rm -f "$MY_ROOT/$my_app_systemd_tmpfiles" |  | ||||||
| $sudo_cmd rm -f "$MY_ROOT/$my_app_launchd_service" |  | ||||||
| $sudo_cmd rm -f "$MY_ROOT/$my_app_upstart_service" |  | ||||||
| 
 |  | ||||||
| $sudo_cmd rm -rf /opt/$my_app_name |  | ||||||
| $sudo_cmd rm -rf /var/log/$my_app_name |  | ||||||
| 
 |  | ||||||
| # TODO flag for --purge |  | ||||||
| #rm -rf /etc/goldilocks |  | ||||||
| 
 |  | ||||||
| # TODO trap uninstall function |  | ||||||
| 
 |  | ||||||
| echo "uninstall complete: $my_app_name" |  | ||||||
| @ -1,31 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
| 
 |  | ||||||
| set -e |  | ||||||
| set -u |  | ||||||
| 
 |  | ||||||
| pushd $(dirname ${0})/packages/assets |  | ||||||
| 
 |  | ||||||
| OAUTH3_GIT_URL="https://git.daplie.com/Oauth3/oauth3.js.git" |  | ||||||
| git clone ${OAUTH3_GIT_URL} org.oauth3 || true |  | ||||||
| pushd org.oauth3 |  | ||||||
| git remote set-url origin ${OAUTH3_GIT_URL} |  | ||||||
| git checkout master |  | ||||||
| git pull |  | ||||||
| popd |  | ||||||
| 
 |  | ||||||
| mkdir -p com.jquery |  | ||||||
| pushd com.jquery |  | ||||||
| curl -o jquery-3.1.1.js 'https://code.jquery.com/jquery-3.1.1.js' |  | ||||||
| popd |  | ||||||
| 
 |  | ||||||
| mkdir -p com.google |  | ||||||
| pushd com.google |  | ||||||
| curl -o  angular.1.6.2.min.js 'https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.min.js' |  | ||||||
| popd |  | ||||||
| 
 |  | ||||||
| mkdir -p well-known |  | ||||||
| pushd well-known |  | ||||||
| ln -snf ../org.oauth3/well-known/oauth3 ./oauth3 |  | ||||||
| popd |  | ||||||
| 
 |  | ||||||
| popd |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user