diff --git a/composer.json b/composer.json index 1509585..b6ef87d 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "clue/commander": "^1.3", "clue/connection-manager-extra": "^1.1", "clue/http-proxy-react": "^1.4", - "clue/socks-react": "^0.8.7", + "clue/socks-react": "^1.0", "react/http": "^0.8.3", "react/http-client": "^0.5.9", "react/socket": "^1.1" diff --git a/composer.lock b/composer.lock index 33b4329..5c42fc9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eb34b950c92e712e28708096c3db26f3", + "content-hash": "40d6f3f2ba0850279ad6115d2b5d2b31", "packages": [ { "name": "clue/commander", @@ -169,23 +169,22 @@ }, { "name": "clue/socks-react", - "version": "v0.8.7", + "version": "v1.0.0", "source": { "type": "git", - "url": "https://github.com/clue/php-socks-react.git", - "reference": "0fcd6f2f506918ff003f1b995c6e78443f26e8ea" + "url": "https://github.com/clue/reactphp-socks.git", + "reference": "f60ae627807b4b04de0c5beca2344f6df666b802" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/clue/php-socks-react/zipball/0fcd6f2f506918ff003f1b995c6e78443f26e8ea", - "reference": "0fcd6f2f506918ff003f1b995c6e78443f26e8ea", + "url": "https://api.github.com/repos/clue/reactphp-socks/zipball/f60ae627807b4b04de0c5beca2344f6df666b802", + "reference": "f60ae627807b4b04de0c5beca2344f6df666b802", "shasum": "" }, "require": { - "evenement/evenement": "~3.0|~1.0|~2.0", "php": ">=5.3", "react/promise": "^2.1 || ^1.2", - "react/socket": "^1.0 || ^0.8.6" + "react/socket": "^1.1" }, "require-dev": { "clue/block-react": "^1.1", @@ -209,18 +208,19 @@ "email": "christian@lueck.tv" } ], - "description": "Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of ReactPHP", - "homepage": "https://github.com/clue/php-socks-react", + "description": "Async SOCKS proxy connector client and server implementation, tunnel any TCP/IP-based protocol through a SOCKS5 or SOCKS4(a) proxy server, built on top of ReactPHP.", + "homepage": "https://github.com/clue/reactphp-socks", "keywords": [ "async", - "proxy", + "proxy server", "reactphp", "socks client", - "socks protocol", "socks server", + "socks4a", + "socks5", "tcp tunnel" ], - "time": "2017-12-17T14:47:58+00:00" + "time": "2018-11-20T11:16:19+00:00" }, { "name": "evenement/evenement", diff --git a/src/ConnectorFactory.php b/src/ConnectorFactory.php index 88fe2fa..092d393 100644 --- a/src/ConnectorFactory.php +++ b/src/ConnectorFactory.php @@ -59,7 +59,12 @@ public static function coerceProxyUri($uri) throw new \InvalidArgumentException('Upstream proxy "' . $uri . '" can not be parsed as a valid URI'); } - if (!in_array($parts['scheme'], array('http', 'socks', 'socks5', 'socks4', 'socks4a'))) { + // deprecated, for BC only: map explicit SOCKS4a to SOCKS4, should be removed in the future + if ($parts['scheme'] === 'socks4a') { + $parts['scheme'] = 'socks4'; + } + + if (!in_array($parts['scheme'], array('http', 'socks', 'socks5', 'socks4'))) { throw new \InvalidArgumentException('Upstream proxy scheme "' . $parts['scheme'] . '://" not supported'); } @@ -82,6 +87,9 @@ public static function coerceProxyUri($uri) $parts += array('user' => '', 'pass' => ''); $parts['host'] = $parts['user'] . ':' . $parts['pass'] . '@' . $parts['host']; + } elseif ($parts['scheme'] === 'socks') { + // deprecated, for BC only: map unspecified SOCKS to SOCKS4(a), should be SOCKS5 in the future + $parts['scheme'] = 'socks4'; } return $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port']; diff --git a/src/LeProxyServer.php b/src/LeProxyServer.php index a602c15..0a7b71b 100644 --- a/src/LeProxyServer.php +++ b/src/LeProxyServer.php @@ -60,32 +60,37 @@ public function listen($listen, $allowUnprotected) $socket = new Socket($parts['host'] . ':' . $parts['port'], $this->loop); } - // start new proxy server which uses the given connector for forwarding/chaining - $unification = new ProtocolDetector($socket); - $http = new HttpProxyServer($this->loop, $unification->http, $this->connector); - $socks = new SocksServer( - $this->loop, - $unification->socks, - new SocksErrorConnector( - $this->connector, - !isset($parts['user']) && !isset($parts['pass']) && !$allowUnprotected - ) - ); - // require authentication if listening URI contains username/password + $auth = null; if (isset($parts['user']) || isset($parts['pass'])) { $auth = array( rawurldecode($parts['user']) => isset($parts['pass']) ? rawurldecode($parts['pass']) : '' ); + } + // start new proxy server which uses the given connector for forwarding/chaining + $unification = new ProtocolDetector($socket); + + // HTTP server with authentication required or protected mode by default + $http = new HttpProxyServer($this->loop, $unification->http, $this->connector); + if ($auth !== null) { $http->setAuthArray($auth); - $socks->setAuthArray($auth); } elseif (!$allowUnprotected) { // no authentication required, so only allow local HTTP requests (protected mode) - // SOCKS works slightly differently and simply rejects every non-local connection attempt, see SocksErrorConnector $http->allowUnprotected = false; } + // SOCKS server works slightly differently and simply rejects every non-local connection attempt via SocksErrorConnector + $socks = new SocksServer( + $this->loop, + new SocksErrorConnector( + $this->connector, + !isset($parts['user']) && !isset($parts['pass']) && !$allowUnprotected + ), + $auth + ); + $socks->listen($unification->socks); + return $socket; } } diff --git a/tests/ConnectorFactoryTest.php b/tests/ConnectorFactoryTest.php index ed66f7c..3b4279b 100644 --- a/tests/ConnectorFactoryTest.php +++ b/tests/ConnectorFactoryTest.php @@ -9,10 +9,10 @@ public function testCoerceProxyUri() $uris = array( 'host' => 'http://host:8080', 'host:1234' => 'http://host:1234', - 'socks://host' => 'socks://host:8080', + 'socks://host' => 'socks4://host:8080', 'socks://user:pass@host' => 'socks5://user:pass@host:8080', 'user@host' => 'http://user:@host:8080', - 'socks4a://10.20.30.40:5060' => 'socks4a://10.20.30.40:5060', + 'socks4a://10.20.30.40:5060' => 'socks4://10.20.30.40:5060', './proxy.sock' => 'http+unix://./proxy.sock', '/tmp/proxy.sock' => 'http+unix:///tmp/proxy.sock',