Skip to content

Commit 706c643

Browse files
committed
Add proxy authorization (close #73)
1 parent 44268e1 commit 706c643

File tree

1 file changed

+68
-0
lines changed

1 file changed

+68
-0
lines changed

src/main.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import argparse
1111
import asyncio
12+
import base64
1213
import json
1314
import logging
1415
import os
@@ -56,6 +57,8 @@ def __init__(self):
5657
self.host = "127.0.0.1"
5758
self.port = 8881
5859
self.out_host = None
60+
self.username = None
61+
self.password = None
5962
self.blacklist_file = "blacklist.txt"
6063
self.fragment_method = "random"
6164
self.domain_matching = "strict"
@@ -526,6 +529,7 @@ def __init__(
526529
self.statistics = statistics
527530
self.logger = logger
528531
self.out_host = self.config.out_host
532+
self.auth_enabled = config.username is not None and config.password is not None
529533
self.active_connections: Dict[Tuple, ConnectionInfo] = {}
530534
self.connections_lock = asyncio.Lock()
531535
self.tasks: List[asyncio.Task] = []
@@ -560,6 +564,9 @@ async def handle_connection(
560564
self.statistics.update_traffic(0, len(http_data))
561565
conn_info.traffic_out += len(http_data)
562566

567+
if not await self._check_proxy_authorization(http_data, writer):
568+
return
569+
563570
if method == b"CONNECT":
564571
await self._handle_https_connection(
565572
reader, writer, host, port, conn_key, conn_info
@@ -596,6 +603,57 @@ def _parse_http_request(self, http_data: bytes) -> Tuple[bytes, bytes, int]:
596603

597604
return method, host, port
598605

606+
async def _check_proxy_authorization(
607+
self, http_data: bytes, writer: asyncio.StreamWriter
608+
) -> bool:
609+
"""Check proxy authorization"""
610+
611+
if not self.auth_enabled:
612+
return True
613+
614+
headers = http_data.split(b"\r\n")
615+
auth_header = None
616+
for line in headers:
617+
if line.lower().startswith(b"proxy-authorization:"):
618+
auth_header = line
619+
break
620+
621+
if auth_header is None:
622+
await self._send_407_response(writer)
623+
return False
624+
625+
parts = auth_header.split(b" ", 2)
626+
if len(parts) != 3 or parts[1].lower() != b"basic":
627+
await self._send_407_response(writer)
628+
return False
629+
630+
try:
631+
decoded = base64.b64decode(parts[2].strip()).decode("utf-8")
632+
username, password = decoded.split(":", 1)
633+
except Exception:
634+
await self._send_407_response(writer)
635+
return False
636+
637+
if username != self.config.username or password != self.config.password:
638+
await self._send_407_response(writer)
639+
return False
640+
641+
return True
642+
643+
async def _send_407_response(self, writer: asyncio.StreamWriter):
644+
"""Send 407 Proxy Authentication Required response"""
645+
646+
response = (
647+
"HTTP/1.1 407 Proxy Authentication Required\r\n"
648+
'Proxy-Authenticate: Basic realm="NoDPI Proxy"\r\n'
649+
"Content-Length: 0\r\n"
650+
"Connection: close\r\n\r\n"
651+
)
652+
writer.write(response.encode())
653+
await writer.drain()
654+
writer.close()
655+
await writer.wait_closed()
656+
599657
async def _handle_https_connection(
600658
self,
601659
reader: asyncio.StreamReader,
@@ -1147,6 +1205,8 @@ def load_from_args(args) -> ProxyConfig:
11471205
config.host = args.host
11481206
config.port = args.port
11491207
config.out_host = args.out_host
1208+
config.username = args.auth_username
1209+
config.password = args.auth_password
11501210
config.blacklist_file = args.blacklist
11511211
config.fragment_method = args.fragment_method
11521212
config.domain_matching = args.domain_matching
@@ -1326,6 +1386,14 @@ def parse_args():
13261386
choices=["loose", "strict"],
13271387
help="Domain matching mode (strict by default)",
13281388
)
1389+
1390+
parser.add_argument(
1391+
"--auth-username", required=False, help="Username for proxy authentication"
1392+
)
1393+
parser.add_argument(
1394+
"--auth-password", required=False, help="Password for proxy authentication"
1395+
)
1396+
13291397
parser.add_argument(
13301398
"--log-access", required=False, help="Path to the access control log"
13311399
)

0 commit comments

Comments
 (0)