leproxy.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. #!/usr/bin/env php
  2. <?php
  3. /**
  4. * LeProxy is the HTTP/SOCKS proxy server for everybody!
  5. *
  6. * LeProxy should be run from the command line. Assuming this file is
  7. * named `leproxy.php`, try running `$ php leproxy.php --help`.
  8. *
  9. * @link https://leproxy.org/ LeProxy project homepage
  10. * @license https://leproxy.org/#license MIT license
  11. * @copyright 2017 Christian Lück
  12. * @version dev
  13. */
  14. namespace LeProxy\LeProxy;
  15. use Clue\Commander\Router;
  16. use Clue\Commander\NoRouteFoundException;
  17. use Clue\Commander\Tokens\Tokenizer;
  18. use React\EventLoop\Factory;
  19. use React\Dns\Config\HostsFile;
  20. if (PHP_VERSION_ID < 50400 || PHP_SAPI !== 'cli') {
  21. echo 'LeProxy HTTP/SOCKS proxy requires running ' . (PHP_SAPI !== 'cli' ? ('via command line (not ' . PHP_SAPI . ')') : ('on PHP 5.4+ (is ' . PHP_VERSION . ')')) . PHP_EOL;
  22. exit(1);
  23. }
  24. // get current version from git or default to "unknown" otherwise
  25. // this line will be replaced with the static const in the release file.
  26. define('VERSION', ltrim(exec('git describe --always --dirty 2>/dev/null || echo unknown'), 'v'));
  27. require __DIR__ . '/vendor/autoload.php';
  28. // parse options from command line arguments (argv)
  29. $tokenizer = new Tokenizer();
  30. $tokenizer->addFilter('block', function (&$value) {
  31. $value = ConnectorFactory::coerceBlockUri($value);
  32. return true;
  33. });
  34. $tokenizer->addFilter('proxy', function (&$value) {
  35. $value = ConnectorFactory::coerceProxyUri($value);
  36. return true;
  37. });
  38. $tokenizer->addFilter('hosts', function (&$value) {
  39. $value = HostsFile::loadFromPathBlocking($value)->getHostsForIp('0.0.0.0');
  40. return true;
  41. });
  42. $commander = new Router($tokenizer);
  43. $commander->add('--version', function () {
  44. exit('LeProxy development version ' . VERSION . PHP_EOL);
  45. });
  46. $commander->add('-h | --help', function () {
  47. exit('LeProxy HTTP/SOCKS proxy
  48. Usage:
  49. $ php leproxy.php [<listenAddress>] [--allow-unprotected] [--block=<destination>...] [--block-hosts=<path>...] [--proxy=<upstreamProxy>...] [--no-log]
  50. $ php leproxy.php --version
  51. $ php leproxy.php --help
  52. Arguments:
  53. <listenAddress>
  54. The socket address to listen on.
  55. The address consists of a full URI which may contain a username and
  56. password, host and port.
  57. By default, LeProxy will listen on the public address 0.0.0.0:8080.
  58. LeProxy will report an error if it fails to listen on the given address,
  59. you may try another address or use port `0` to pick a random free port.
  60. If this address does not contain a username and password, LeProxy will
  61. run in protected mode and only forward requests from the local host,
  62. see also `--allow-unprotected`.
  63. --allow-unprotected
  64. If no username and password has been given, then LeProxy runs in
  65. protected mode by default, so that it only forwards requests from the
  66. local host and can not be abused as an open proxy.
  67. If you have ensured only legit users can access your system, you can
  68. pass the `--allow-unprotected` flag to forward requests from all hosts.
  69. This option should be used with care, you have been warned.
  70. --block=<destination>
  71. Blocks forwarding connections to the given destination address.
  72. Any number of destination addresses can be given.
  73. Each destination address can be in the form `host` or `host:port` and
  74. `host` may contain the `*` wildcard to match anything.
  75. Subdomains for each host will automatically be blocked.
  76. --block-hosts=<path>
  77. Loads the hosts file from the given file path and blocks all of the
  78. hostnames (and subdomains) that match the IP `0.0.0.0`.
  79. Any number of hosts files can be given, all hosts will be blocked.
  80. --proxy=<upstreamProxy>
  81. An upstream proxy server where each connection request will be
  82. forwarded to (proxy chaining).
  83. Any number of upstream proxies can be given.
  84. Each address consists of full URI which may contain a scheme, username
  85. and password, host and port. Default scheme is `http://`, default port
  86. is `8080` for all schemes.
  87. --no-log
  88. By default, LeProxy logs all connection attempts to STDOUT for
  89. debugging purposes. This can be avoided by passing this argument.
  90. --version
  91. Prints the current version of LeProxy and exits.
  92. --help, -h
  93. Shows this help and exits.
  94. Examples:
  95. $ php leproxy.php
  96. Runs LeProxy on public default address 0.0.0.0:8080 (protected mode)
  97. $ php leproy.php 127.0.0.1:1080
  98. Runs LeProxy on custom address 127.0.0.1:1080 (protected mode, local only)
  99. $ php leproxy.php user:pass@0.0.0.0:8080
  100. Runs LeProxy on public default addresses and require authentication
  101. $ php leproxy.php --block=youtube.com --block=*:80
  102. Runs LeProxy on default address and blocks access to youtube.com and
  103. port 80 on all hosts (standard plaintext HTTP port).
  104. $ php leproxy.php --proxy=http://user:pass@127.1.1.1:8080
  105. Runs LeProxy so that all connection requests will be forwarded through
  106. an upstream proxy server that requires authentication.
  107. ');
  108. });
  109. $commander->add('[--allow-unprotected] [--block=<block:block>...] [--block-hosts=<file:hosts>...] [--proxy=<proxy:proxy>...] [--no-log] [<listen>]', function ($args) {
  110. // validate listening URI or assume default URI
  111. $args['listen'] = ConnectorFactory::coerceListenUri(isset($args['listen']) ? $args['listen'] : '');
  112. $args['allow-unprotected'] = isset($args['allow-unprotected']);
  113. if ($args['allow-unprotected'] && strpos($args['listen'], '@') !== false) {
  114. throw new \InvalidArgumentException('Unprotected mode can not be used with authentication required');
  115. }
  116. if (isset($args['block-hosts'])) {
  117. if (!isset($args['block'])) {
  118. $args['block'] = array();
  119. }
  120. foreach ($args['block-hosts'] as $hosts) {
  121. $args['block'] += $hosts;
  122. }
  123. }
  124. // filter duplicate block entries and subdomains
  125. if (isset($args['block'])) {
  126. $args['block'] = ConnectorFactory::filterRootDomains($args['block']);
  127. }
  128. return $args;
  129. });
  130. try {
  131. $args = $commander->handleArgv();
  132. } catch (\Exception $e) {
  133. $message = '';
  134. if (!$e instanceof NoRouteFoundException) {
  135. $message = ' (' . $e->getMessage() . ')';
  136. }
  137. fwrite(STDERR, 'Usage Error: Invalid command arguments given, see --help' . $message . PHP_EOL);
  138. // sysexits.h: #define EX_USAGE 64 /* command line usage error */
  139. exit(64);
  140. }
  141. $loop = Factory::create();
  142. // set next proxy server chain -> p1 -> p2 -> p3 -> destination
  143. $connector = ConnectorFactory::createConnectorChain(isset($args['proxy']) ? $args['proxy'] : array(), $loop);
  144. if (isset($args['block'])) {
  145. $connector = ConnectorFactory::createBlockingConnector($args['block'], $connector);
  146. }
  147. // log all connection attempts to STDOUT (unless `--no-log` has been given)
  148. if (!isset($args['no-log'])) {
  149. $connector = new LoggingConnector($connector, new Logger());
  150. }
  151. // create proxy server and start listening on given address
  152. $proxy = new LeProxyServer($loop, $connector);
  153. try {
  154. $socket = $proxy->listen($args['listen'], $args['allow-unprotected']);
  155. } catch (\RuntimeException $e) {
  156. fwrite(STDERR, 'Program error: Unable to start listening, maybe try another port? (' . $e->getMessage() . ')'. PHP_EOL);
  157. // sysexits.h: #define EX_OSERR 71 /* system error (e.g., can't fork) */
  158. exit(71);
  159. }
  160. $addr = str_replace('tcp://', 'http://', $socket->getAddress());
  161. echo 'LeProxy HTTP/SOCKS proxy now listening on ' . $addr . ' (';
  162. if (strpos($args['listen'], '@') !== false) {
  163. echo 'authentication required';
  164. } elseif ($args['allow-unprotected']) {
  165. echo 'unprotected mode, open proxy';
  166. } else {
  167. echo 'protected mode, local access only';
  168. }
  169. echo ')' . PHP_EOL;
  170. if (isset($args['proxy'])) {
  171. echo 'Forwarding via: ' . implode(' -> ', $args['proxy']) . PHP_EOL;
  172. }
  173. if (isset($args['block'])) {
  174. echo 'Blocking a total of ' . count($args['block']) . ' destination(s)' . PHP_EOL;
  175. }
  176. $loop->run();