Skip to content
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
Cannot retrieve contributors at this time
executable file 213 lines (177 sloc) 8.13 KB
#!/usr/bin/env php
* LeProxy is the HTTP/SOCKS proxy server for everybody!
* LeProxy should be run from the command line. Assuming this file is
* named `leproxy.php`, try running `$ php leproxy.php --help`.
* @link LeProxy project homepage
* @license MIT license
* @copyright 2017 Christian Lück
* @version dev
namespace LeProxy\LeProxy;
use Clue\Commander\Router;
use Clue\Commander\NoRouteFoundException;
use Clue\Commander\Tokens\Tokenizer;
use React\EventLoop\Factory;
use React\Dns\Config\HostsFile;
if (PHP_VERSION_ID < 50400 || PHP_SAPI !== 'cli') {
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;
// get current version from git or default to "unknown" otherwise
// this line will be replaced with the static const in the release file.
define('VERSION', ltrim(exec('git describe --always --dirty 2>/dev/null || echo unknown'), 'v'));
// autoload for local project development or project installed as dependency
(@include __DIR__ . '/vendor/autoload.php') || require __DIR__ . '/../../autoload.php';
// parse options from command line arguments (argv)
$tokenizer = new Tokenizer();
$tokenizer->addFilter('block', function (&$value) {
$value = ConnectorFactory::coerceBlockUri($value);
return true;
$tokenizer->addFilter('proxy', function (&$value) {
$value = ConnectorFactory::coerceProxyUri($value);
return true;
$tokenizer->addFilter('hosts', function (&$value) {
$value = HostsFile::loadFromPathBlocking($value)->getHostsForIp('');
return true;
$commander = new Router($tokenizer);
$commander->add('--version', function () {
exit('LeProxy development version ' . VERSION . PHP_EOL);
$commander->add('-h | --help', function () {
exit('LeProxy HTTP/SOCKS proxy
$ php leproxy.php [<listenAddress>] [--allow-unprotected] [--block=<destination>...] [--block-hosts=<path>...] [--proxy=<upstreamProxy>...] [--no-log]
$ php leproxy.php --version
$ php leproxy.php --help
The socket address to listen on.
The address consists of a full URI which may contain a username and
password, host and port (or Unix domain socket path).
By default, LeProxy will listen on the public address
LeProxy will report an error if it fails to listen on the given address,
you may try another address or use port `0` to pick a random free port.
If this address does not contain a username and password, LeProxy will
run in protected mode and only forward requests from the local host,
see also `--allow-unprotected`.
If no username and password has been given, then LeProxy runs in
protected mode by default, so that it only forwards requests from the
local host and can not be abused as an open proxy.
If you have ensured only legit users can access your system, you can
pass the `--allow-unprotected` flag to forward requests from all hosts.
This option should be used with care, you have been warned.
Blocks forwarding connections to the given destination address.
Any number of destination addresses can be given.
Each destination address can be in the form `host` or `host:port` and
`host` may contain the `*` wildcard to match anything.
Subdomains for each host will automatically be blocked.
Loads the hosts file from the given file path and blocks all of the
hostnames (and subdomains) that match the IP ``.
Any number of hosts files can be given, all hosts will be blocked.
An upstream proxy server where each connection request will be
forwarded to (proxy chaining).
Any number of upstream proxies can be given.
Each address consists of full URI which may contain a scheme, username
and password, host and port (or Unix domain socket path). Default scheme
is `http://`, default port is `8080` for all schemes.
By default, LeProxy logs all connection attempts to STDOUT for
debugging purposes. This can be avoided by passing this argument.
Prints the current version of LeProxy and exits.
--help, -h
Shows this help and exits.
$ php leproxy.php
Runs LeProxy on public default address (protected mode)
$ php leproy.php
Runs LeProxy on custom address (protected mode, local only)
$ php leproxy.php user:pass@
Runs LeProxy on public default addresses and require authentication
$ php leproxy.php --block=*:80
Runs LeProxy on default address and blocks access to and
port 80 on all hosts (standard plaintext HTTP port).
$ php leproxy.php --proxy=http://user:pass@
Runs LeProxy so that all connection requests will be forwarded through
an upstream proxy server that requires authentication.
$commander->add('[--allow-unprotected] [--block=<block:block>...] [--block-hosts=<file:hosts>...] [--proxy=<proxy:proxy>...] [--no-log] [<listen>]', function ($args) {
// validate listening URI or assume default URI
$args['listen'] = ConnectorFactory::coerceListenUri(isset($args['listen']) ? $args['listen'] : '');
$args['allow-unprotected'] = isset($args['allow-unprotected']);
if ($args['allow-unprotected'] && strpos($args['listen'], '@') !== false) {
throw new \InvalidArgumentException('Unprotected mode can not be used with authentication required');
if (isset($args['block-hosts'])) {
if (!isset($args['block'])) {
$args['block'] = array();
foreach ($args['block-hosts'] as $hosts) {
$args['block'] += $hosts;
// filter duplicate block entries and subdomains
if (isset($args['block'])) {
$args['block'] = ConnectorFactory::filterRootDomains($args['block']);
return $args;
try {
$args = $commander->handleArgv();
} catch (\Exception $e) {
$message = '';
if (!$e instanceof NoRouteFoundException) {
$message = ' (' . $e->getMessage() . ')';
fwrite(STDERR, 'Usage Error: Invalid command arguments given, see --help' . $message . PHP_EOL);
// sysexits.h: #define EX_USAGE 64 /* command line usage error */
$loop = Factory::create();
// set next proxy server chain -> p1 -> p2 -> p3 -> destination
$connector = ConnectorFactory::createConnectorChain(isset($args['proxy']) ? $args['proxy'] : array(), $loop);
if (isset($args['block'])) {
$connector = ConnectorFactory::createBlockingConnector($args['block'], $connector);
// log all connection attempts to STDOUT (unless `--no-log` has been given)
if (!isset($args['no-log'])) {
$connector = new LoggingConnector($connector, new Logger());
// create proxy server and start listening on given address
$proxy = new LeProxyServer($loop, $connector);
try {
$socket = $proxy->listen($args['listen'], $args['allow-unprotected']);
} catch (\RuntimeException $e) {
fwrite(STDERR, 'Program error: Unable to start listening, maybe try another port? (' . $e->getMessage() . ')'. PHP_EOL);
// sysexits.h: #define EX_OSERR 71 /* system error (e.g., can't fork) */
$addr = str_replace(array('tcp://', 'unix://'), array('http://', 'http+unix://'), $socket->getAddress());
echo 'LeProxy HTTP/SOCKS proxy now listening on ' . $addr . ' (';
if (strpos($args['listen'], '@') !== false) {
echo 'authentication required';
} elseif ($args['allow-unprotected']) {
echo 'unprotected mode, open proxy';
} else {
echo 'protected mode, local access only';
echo ')' . PHP_EOL;
if (isset($args['proxy'])) {
echo 'Forwarding via: ' . implode(' -> ', $args['proxy']) . PHP_EOL;
if (isset($args['block'])) {
echo 'Blocking a total of ' . count($args['block']) . ' destination(s)' . PHP_EOL;