2014-03-09 20:13:08 +00:00
|
|
|
import socket
|
2014-03-09 20:51:24 +00:00
|
|
|
from .. import version, protocol
|
|
|
|
from libmproxy.proxy.primitives import Log
|
|
|
|
from .primitives import ProxyServerError
|
|
|
|
from .connection import ClientConnection, ServerConnection
|
|
|
|
from .primitives import ProxyError, ConnectionTypeChange, AddressPriority
|
|
|
|
from netlib import tcp
|
2014-03-09 20:13:08 +00:00
|
|
|
|
|
|
|
|
2014-03-09 20:51:24 +00:00
|
|
|
class DummyServer:
|
|
|
|
bound = False
|
2014-01-09 04:34:29 +00:00
|
|
|
|
2014-03-09 20:51:24 +00:00
|
|
|
def __init__(self, config):
|
|
|
|
self.config = config
|
2014-01-09 04:34:29 +00:00
|
|
|
|
2014-03-09 20:51:24 +00:00
|
|
|
def start_slave(self, *args):
|
|
|
|
pass
|
2014-03-05 04:25:12 +00:00
|
|
|
|
2014-03-09 20:51:24 +00:00
|
|
|
def shutdown(self):
|
|
|
|
pass
|
2013-01-04 01:19:32 +00:00
|
|
|
|
2014-02-04 04:02:17 +00:00
|
|
|
|
2014-03-09 20:51:24 +00:00
|
|
|
class ProxyServer(tcp.TCPServer):
|
|
|
|
allow_reuse_address = True
|
|
|
|
bound = True
|
|
|
|
def __init__(self, config, port, host='', server_version=version.NAMEVERSION):
|
|
|
|
"""
|
|
|
|
Raises ProxyServerError if there's a startup problem.
|
|
|
|
"""
|
|
|
|
self.config = config
|
|
|
|
self.server_version = server_version
|
|
|
|
try:
|
|
|
|
tcp.TCPServer.__init__(self, (host, port))
|
|
|
|
except socket.error, v:
|
|
|
|
raise ProxyServerError('Error starting proxy server: ' + v.strerror)
|
|
|
|
self.channel = None
|
2013-03-24 20:20:26 +00:00
|
|
|
|
2014-03-09 20:51:24 +00:00
|
|
|
def start_slave(self, klass, channel):
|
|
|
|
slave = klass(channel, self)
|
|
|
|
slave.start()
|
2014-02-07 06:08:59 +00:00
|
|
|
|
2014-03-09 20:51:24 +00:00
|
|
|
def set_channel(self, channel):
|
|
|
|
self.channel = channel
|
2013-02-24 04:35:24 +00:00
|
|
|
|
2014-03-09 20:51:24 +00:00
|
|
|
def handle_client_connection(self, conn, client_address):
|
|
|
|
h = ConnectionHandler(self.config, conn, client_address, self, self.channel, self.server_version)
|
|
|
|
h.handle()
|
|
|
|
h.finish()
|
2014-01-30 04:00:13 +00:00
|
|
|
|
2014-02-05 19:26:47 +00:00
|
|
|
|
2014-01-05 00:03:55 +00:00
|
|
|
class ConnectionHandler:
|
|
|
|
def __init__(self, config, client_connection, client_address, server, channel, server_version):
|
2012-06-10 04:02:48 +00:00
|
|
|
self.config = config
|
2014-01-29 01:49:11 +00:00
|
|
|
self.client_conn = ClientConnection(client_connection, client_address, server)
|
2014-01-09 04:34:29 +00:00
|
|
|
self.server_conn = None
|
2014-01-05 00:03:55 +00:00
|
|
|
self.channel, self.server_version = channel, server_version
|
2013-12-12 09:00:23 +00:00
|
|
|
|
2014-01-09 04:34:29 +00:00
|
|
|
self.close = False
|
2014-01-05 00:03:55 +00:00
|
|
|
self.conntype = None
|
|
|
|
self.sni = None
|
2013-12-12 09:00:23 +00:00
|
|
|
|
2010-02-16 04:09:07 +00:00
|
|
|
def handle(self):
|
2014-01-13 01:25:58 +00:00
|
|
|
self.log("clientconnect")
|
2014-01-07 01:29:10 +00:00
|
|
|
self.channel.ask("clientconnect", self)
|
2014-01-05 00:03:55 +00:00
|
|
|
|
2014-01-29 01:49:11 +00:00
|
|
|
self.determine_conntype()
|
|
|
|
|
2014-01-09 04:34:29 +00:00
|
|
|
try:
|
2014-01-30 17:56:23 +00:00
|
|
|
try:
|
|
|
|
# Can we already identify the target server and connect to it?
|
2014-03-10 04:11:51 +00:00
|
|
|
if self.config.get_upstream_server:
|
|
|
|
upstream_info = self.config.get_upstream_server(self.client_conn.connection)
|
|
|
|
self.set_server_address(upstream_info[2:], AddressPriority.FROM_SETTINGS)
|
|
|
|
client_ssl, server_ssl = upstream_info[:2]
|
|
|
|
if client_ssl or server_ssl:
|
|
|
|
self.establish_server_connection()
|
|
|
|
self.establish_ssl(client=client_ssl, server=server_ssl)
|
2014-01-30 17:56:23 +00:00
|
|
|
|
|
|
|
while not self.close:
|
|
|
|
try:
|
|
|
|
protocol.handle_messages(self.conntype, self)
|
2014-03-09 20:13:08 +00:00
|
|
|
except ConnectionTypeChange:
|
2014-01-31 00:44:55 +00:00
|
|
|
self.log("Connection Type Changed: %s" % self.conntype)
|
2014-01-30 17:56:23 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
# FIXME: Do we want to persist errors?
|
|
|
|
except (ProxyError, tcp.NetLibError), e:
|
|
|
|
protocol.handle_error(self.conntype, self, e)
|
2014-01-29 01:49:11 +00:00
|
|
|
except Exception, e:
|
|
|
|
self.log(e.__class__)
|
2014-01-30 04:00:13 +00:00
|
|
|
import traceback
|
|
|
|
self.log(traceback.format_exc())
|
2014-01-09 04:34:29 +00:00
|
|
|
self.log(str(e))
|
2012-06-30 12:15:03 +00:00
|
|
|
|
2014-01-29 01:49:11 +00:00
|
|
|
self.del_server_connection()
|
2014-01-13 01:25:58 +00:00
|
|
|
self.log("clientdisconnect")
|
2014-01-07 01:29:10 +00:00
|
|
|
self.channel.tell("clientdisconnect", self)
|
2010-11-12 15:01:17 +00:00
|
|
|
|
2014-02-04 01:56:59 +00:00
|
|
|
def del_server_connection(self):
|
|
|
|
"""
|
|
|
|
Deletes an existing server connection.
|
|
|
|
"""
|
|
|
|
if self.server_conn and self.server_conn.connection:
|
|
|
|
self.server_conn.finish()
|
|
|
|
self.log("serverdisconnect", ["%s:%s" % (self.server_conn.address.host, self.server_conn.address.port)])
|
|
|
|
self.channel.tell("serverdisconnect", self)
|
|
|
|
self.server_conn = None
|
|
|
|
self.sni = None
|
2014-01-09 04:34:29 +00:00
|
|
|
|
|
|
|
def determine_conntype(self):
|
2014-01-05 00:03:55 +00:00
|
|
|
#TODO: Add ruleset to select correct protocol depending on mode/target port etc.
|
|
|
|
self.conntype = "http"
|
|
|
|
|
2014-02-04 01:56:59 +00:00
|
|
|
def set_server_address(self, address, priority):
|
2014-01-05 00:03:55 +00:00
|
|
|
"""
|
2014-02-06 21:16:26 +00:00
|
|
|
Sets a new server address with the given priority.
|
|
|
|
Does not re-establish either connection or SSL handshake.
|
2014-01-05 00:03:55 +00:00
|
|
|
"""
|
2014-02-04 01:56:59 +00:00
|
|
|
address = tcp.Address.wrap(address)
|
|
|
|
|
2014-02-06 21:16:26 +00:00
|
|
|
if self.server_conn:
|
|
|
|
if self.server_conn.priority > priority:
|
|
|
|
self.log("Attempt to change server address, "
|
|
|
|
"but priority is too low (is: %s, got: %s)" % (self.server_conn.priority, priority))
|
|
|
|
return
|
|
|
|
if self.server_conn.address == address:
|
|
|
|
self.server_conn.priority = priority # Possibly increase priority
|
|
|
|
return
|
2014-02-04 01:56:59 +00:00
|
|
|
|
|
|
|
self.del_server_connection()
|
2014-02-06 21:16:26 +00:00
|
|
|
|
|
|
|
self.log("Set new server address: %s:%s" % (address.host, address.port))
|
|
|
|
self.server_conn = ServerConnection(address, priority)
|
2014-02-04 01:56:59 +00:00
|
|
|
|
|
|
|
def establish_server_connection(self):
|
|
|
|
"""
|
|
|
|
Establishes a new server connection.
|
|
|
|
If there is already an existing server connection, the function returns immediately.
|
|
|
|
"""
|
|
|
|
if self.server_conn.connection:
|
|
|
|
return
|
|
|
|
self.log("serverconnect", ["%s:%s" % self.server_conn.address()[:2]])
|
|
|
|
self.channel.tell("serverconnect", self)
|
2014-01-18 14:35:37 +00:00
|
|
|
try:
|
|
|
|
self.server_conn.connect()
|
|
|
|
except tcp.NetLibError, v:
|
|
|
|
raise ProxyError(502, v)
|
2014-01-05 00:03:55 +00:00
|
|
|
|
2014-01-18 16:15:33 +00:00
|
|
|
def establish_ssl(self, client=False, server=False):
|
2014-01-10 00:38:28 +00:00
|
|
|
"""
|
|
|
|
Establishes SSL on the existing connection(s) to the server or the client,
|
|
|
|
as specified by the parameters. If the target server is on the pass-through list,
|
2014-02-04 01:56:59 +00:00
|
|
|
the conntype attribute will be changed and the SSL connection won't be wrapped.
|
2014-01-10 00:38:28 +00:00
|
|
|
A protocol handler must raise a ConnTypeChanged exception if it detects that this is happening
|
|
|
|
"""
|
2014-01-05 00:03:55 +00:00
|
|
|
# TODO: Implement SSL pass-through handling and change conntype
|
2014-01-31 03:45:39 +00:00
|
|
|
passthrough = [
|
2014-02-06 22:05:53 +00:00
|
|
|
# "echo.websocket.org",
|
|
|
|
# "174.129.224.73" # echo.websocket.org, transparent mode
|
2014-01-31 03:45:39 +00:00
|
|
|
]
|
2014-01-31 02:01:51 +00:00
|
|
|
if self.server_conn.address.host in passthrough or self.sni in passthrough:
|
2014-01-09 16:56:42 +00:00
|
|
|
self.conntype = "tcp"
|
2014-01-31 02:01:51 +00:00
|
|
|
return
|
|
|
|
|
2014-02-04 01:56:59 +00:00
|
|
|
# Logging
|
2014-01-31 02:01:51 +00:00
|
|
|
if client or server:
|
|
|
|
subs = []
|
|
|
|
if client:
|
|
|
|
subs.append("with client")
|
|
|
|
if server:
|
|
|
|
subs.append("with server (sni: %s)" % self.sni)
|
|
|
|
self.log("Establish SSL", subs)
|
2014-01-09 16:56:42 +00:00
|
|
|
|
|
|
|
if server:
|
|
|
|
if self.server_conn.ssl_established:
|
|
|
|
raise ProxyError(502, "SSL to Server already established.")
|
2014-02-06 21:16:26 +00:00
|
|
|
self.establish_server_connection() # make sure there is a server connection.
|
2014-01-09 04:34:29 +00:00
|
|
|
self.server_conn.establish_ssl(self.config.clientcerts, self.sni)
|
2014-01-09 16:56:42 +00:00
|
|
|
if client:
|
|
|
|
if self.client_conn.ssl_established:
|
|
|
|
raise ProxyError(502, "SSL to Client already established.")
|
2014-03-05 04:25:12 +00:00
|
|
|
cert, key = self.find_cert()
|
2014-03-02 04:35:41 +00:00
|
|
|
self.client_conn.convert_to_ssl(
|
2014-03-09 20:51:24 +00:00
|
|
|
cert, key,
|
2014-03-02 04:35:41 +00:00
|
|
|
handle_sni = self.handle_sni,
|
|
|
|
cipher_list = self.config.ciphers
|
|
|
|
)
|
2014-01-05 00:03:55 +00:00
|
|
|
|
2014-01-18 21:57:28 +00:00
|
|
|
def server_reconnect(self, no_ssl=False):
|
2014-02-04 01:56:59 +00:00
|
|
|
address = self.server_conn.address
|
|
|
|
had_ssl = self.server_conn.ssl_established
|
2014-02-06 21:16:26 +00:00
|
|
|
priority = self.server_conn.priority
|
2014-02-04 01:56:59 +00:00
|
|
|
sni = self.sni
|
2014-01-31 02:01:51 +00:00
|
|
|
self.log("(server reconnect follows)")
|
2014-02-04 01:56:59 +00:00
|
|
|
self.del_server_connection()
|
|
|
|
self.set_server_address(address, priority)
|
|
|
|
self.establish_server_connection()
|
2014-01-18 21:57:28 +00:00
|
|
|
if had_ssl and not no_ssl:
|
2014-01-18 16:15:33 +00:00
|
|
|
self.sni = sni
|
|
|
|
self.establish_ssl(server=True)
|
|
|
|
|
2014-02-04 01:56:59 +00:00
|
|
|
def finish(self):
|
|
|
|
self.client_conn.finish()
|
|
|
|
|
2014-01-05 00:03:55 +00:00
|
|
|
def log(self, msg, subs=()):
|
2012-06-30 12:15:03 +00:00
|
|
|
msg = [
|
2014-01-28 16:28:20 +00:00
|
|
|
"%s:%s: %s" % (self.client_conn.address.host, self.client_conn.address.port, msg)
|
2012-06-30 12:15:03 +00:00
|
|
|
]
|
|
|
|
for i in subs:
|
2014-01-18 16:15:33 +00:00
|
|
|
msg.append(" -> " + i)
|
2012-06-30 12:15:03 +00:00
|
|
|
msg = "\n".join(msg)
|
2014-01-09 04:34:29 +00:00
|
|
|
self.channel.tell("log", Log(msg))
|
2012-06-30 12:15:03 +00:00
|
|
|
|
2014-01-07 01:29:10 +00:00
|
|
|
def find_cert(self):
|
2014-03-05 04:25:12 +00:00
|
|
|
host = self.server_conn.address.host
|
|
|
|
sans = []
|
|
|
|
if not self.config.no_upstream_cert or not self.server_conn.ssl_established:
|
|
|
|
upstream_cert = self.server_conn.cert
|
|
|
|
if upstream_cert.cn:
|
|
|
|
host = upstream_cert.cn.decode("utf8").encode("idna")
|
|
|
|
sans = upstream_cert.altnames
|
|
|
|
|
|
|
|
ret = self.config.certstore.get_cert(host, sans)
|
|
|
|
if not ret:
|
|
|
|
raise ProxyError(502, "Unable to generate dummy cert.")
|
|
|
|
return ret
|
2011-02-08 17:00:59 +00:00
|
|
|
|
2014-01-07 01:29:10 +00:00
|
|
|
def handle_sni(self, connection):
|
2012-06-26 11:51:38 +00:00
|
|
|
"""
|
2014-01-07 01:29:10 +00:00
|
|
|
This callback gets called during the SSL handshake with the client.
|
|
|
|
The client has just sent the Sever Name Indication (SNI). We now connect upstream to
|
|
|
|
figure out which certificate needs to be served.
|
2012-06-26 11:51:38 +00:00
|
|
|
"""
|
2011-01-27 01:19:48 +00:00
|
|
|
try:
|
2014-01-07 01:29:10 +00:00
|
|
|
sn = connection.get_servername()
|
|
|
|
if sn and sn != self.sni:
|
|
|
|
self.sni = sn.decode("utf8").encode("idna")
|
2014-02-04 01:56:59 +00:00
|
|
|
self.log("SNI received: %s" % self.sni)
|
2014-01-29 01:49:11 +00:00
|
|
|
self.server_reconnect() # reconnect to upstream server with SNI
|
2014-01-07 01:29:10 +00:00
|
|
|
# Now, change client context to reflect changed certificate:
|
|
|
|
new_context = SSL.Context(SSL.TLSv1_METHOD)
|
2014-03-05 04:25:12 +00:00
|
|
|
cert, key = self.find_cert()
|
|
|
|
new_context.use_privatekey_file(key)
|
|
|
|
new_context.use_certificate(cert.X509)
|
2014-01-07 01:29:10 +00:00
|
|
|
connection.set_context(new_context)
|
|
|
|
# An unhandled exception in this method will core dump PyOpenSSL, so
|
|
|
|
# make dang sure it doesn't happen.
|
2014-02-04 01:56:59 +00:00
|
|
|
except Exception, e: # pragma: no cover
|
2014-03-09 20:51:24 +00:00
|
|
|
pass
|