Skip to content

Commit 36a8e8d

Browse files
committed
Show full address details in debug mode; unify proxy parameters
1 parent 73f7d8a commit 36a8e8d

File tree

2 files changed

+43
-45
lines changed

2 files changed

+43
-45
lines changed

emailproxy.config

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ documentation = Local servers are specified as demonstrated below where, for exa
2626
behalf (i.e., do not enable STARTTLS in your client). IMAP STARTTLS and POP STARTTLS are not currently supported.
2727

2828
- The `local_address` property can be used to set an IP address or hostname for the proxy to listen on. Both IPv4
29-
and IPv6 are supported. If not specified, this value is set to `::` (i.e., dual-stack IPv4 and IPv6 `localhost`).
29+
and IPv6 are supported. If not specified, this value is set to `::` (i.e., dual-stack IPv4/IPv6 on all interfaces).
3030
When a hostname is set the proxy will first resolve this to an IP address, preferring IPv6 over IPv4 if both are
3131
available. When running in an IPv6 environment with dual-stack support, the proxy will attempt to listen on both
3232
IPv4 and IPv6 hosts simultaneously. Note that tools such as `netstat` do not always accurately show dual-stack mode;

emailproxy.py

+42-44
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
__author__ = 'Simon Robinson'
77
__copyright__ = 'Copyright (c) 2023 Simon Robinson'
88
__license__ = 'Apache 2.0'
9-
__version__ = '2023-10-06' # ISO 8601 (YYYY-MM-DD)
9+
__version__ = '2023-10-19' # ISO 8601 (YYYY-MM-DD)
1010

1111
import abc
1212
import argparse
@@ -1019,8 +1019,8 @@ def decode_credentials(str_data):
10191019

10201020

10211021
class SSLAsyncoreDispatcher(asyncore.dispatcher_with_send):
1022-
def __init__(self, connection=None, socket_map=None):
1023-
asyncore.dispatcher_with_send.__init__(self, sock=connection, map=socket_map)
1022+
def __init__(self, connection_socket=None, socket_map=None):
1023+
asyncore.dispatcher_with_send.__init__(self, sock=connection_socket, map=socket_map)
10241024
self.ssl_handshake_errors = (ssl.SSLWantReadError, ssl.SSLWantWriteError,
10251025
ssl.SSLEOFError, ssl.SSLZeroReturnError)
10261026
self.ssl_connection, self.ssl_handshake_attempts, self.ssl_handshake_completed = self._reset()
@@ -1145,17 +1145,17 @@ class OAuth2ClientConnection(SSLAsyncoreDispatcher):
11451145
"""The base client-side connection that is subclassed to handle IMAP/POP/SMTP client interaction (note that there
11461146
is some protocol-specific code in here, but it is not essential, and only used to avoid logging credentials)"""
11471147

1148-
def __init__(self, proxy_type, connection, socket_map, connection_info, server_connection, proxy_parent,
1149-
custom_configuration):
1150-
SSLAsyncoreDispatcher.__init__(self, connection, socket_map)
1148+
def __init__(self, proxy_type, connection_socket, socket_map, proxy_parent, custom_configuration):
1149+
SSLAsyncoreDispatcher.__init__(self, connection_socket=connection_socket, socket_map=socket_map)
11511150
self.receive_buffer = b''
11521151
self.proxy_type = proxy_type
1153-
self.connection_info = connection_info
1154-
self.server_connection = server_connection
1155-
self.local_address = proxy_parent.local_address
1156-
self.server_address = server_connection.server_address
1152+
self.server_connection = None
11571153
self.proxy_parent = proxy_parent
1154+
self.local_address = proxy_parent.local_address
1155+
self.server_address = proxy_parent.server_address
11581156
self.custom_configuration = custom_configuration
1157+
self.debug_address_string = '%s-{%s}-%s' % tuple(map(Log.format_host_port, (
1158+
connection_socket.getpeername(), connection_socket.getsockname(), self.server_address)))
11591159

11601160
self.censor_next_log = False # try to avoid logging credentials
11611161
self.authenticated = False
@@ -1164,11 +1164,11 @@ def __init__(self, proxy_type, connection, socket_map, connection_info, server_c
11641164
bool(custom_configuration['local_certificate_path'] and custom_configuration['local_key_path']))
11651165

11661166
def info_string(self):
1167-
debug_string = '; %s->%s' % (Log.format_host_port(self.connection_info), Log.format_host_port(
1168-
self.server_address)) if Log.get_level() == logging.DEBUG else ''
1167+
debug_string = self.debug_address_string if Log.get_level() == logging.DEBUG else \
1168+
Log.format_host_port(self.local_address)
11691169
account = '; %s' % self.server_connection.authenticated_username if \
11701170
self.server_connection and self.server_connection.authenticated_username else ''
1171-
return '%s (%s%s%s)' % (self.proxy_type, Log.format_host_port(self.local_address), debug_string, account)
1171+
return '%s (%s%s)' % (self.proxy_type, debug_string, account)
11721172

11731173
def handle_read(self):
11741174
byte_data = self.recv(RECEIVE_BUFFER_SIZE)
@@ -1264,9 +1264,8 @@ def close(self):
12641264
class IMAPOAuth2ClientConnection(OAuth2ClientConnection):
12651265
"""The client side of the connection - intercept LOGIN/AUTHENTICATE commands and replace with OAuth 2.0 SASL"""
12661266

1267-
def __init__(self, connection, socket_map, connection_info, server_connection, proxy_parent, custom_configuration):
1268-
super().__init__('IMAP', connection, socket_map, connection_info, server_connection, proxy_parent,
1269-
custom_configuration)
1267+
def __init__(self, connection_socket, socket_map, proxy_parent, custom_configuration):
1268+
super().__init__('IMAP', connection_socket, socket_map, proxy_parent, custom_configuration)
12701269
self.authentication_tag = None
12711270
self.authentication_command = None
12721271
self.awaiting_credentials = False
@@ -1394,9 +1393,8 @@ class STATE(enum.Enum):
13941393
XOAUTH2_AWAITING_CONFIRMATION = 5
13951394
XOAUTH2_CREDENTIALS_SENT = 6
13961395

1397-
def __init__(self, connection, socket_map, connection_info, server_connection, proxy_parent, custom_configuration):
1398-
super().__init__('POP', connection, socket_map, connection_info, server_connection, proxy_parent,
1399-
custom_configuration)
1396+
def __init__(self, connection_socket, socket_map, proxy_parent, custom_configuration):
1397+
super().__init__('POP', connection_socket, socket_map, proxy_parent, custom_configuration)
14001398
self.connection_state = self.STATE.PENDING
14011399

14021400
def process_data(self, byte_data, censor_server_log=False):
@@ -1473,9 +1471,8 @@ class STATE(enum.Enum):
14731471
XOAUTH2_AWAITING_CONFIRMATION = 6
14741472
XOAUTH2_CREDENTIALS_SENT = 7
14751473

1476-
def __init__(self, connection, socket_map, connection_info, server_connection, proxy_parent, custom_configuration):
1477-
super().__init__('SMTP', connection, socket_map, connection_info, server_connection, proxy_parent,
1478-
custom_configuration)
1474+
def __init__(self, connection_socket, socket_map, proxy_parent, custom_configuration):
1475+
super().__init__('SMTP', connection_socket, socket_map, proxy_parent, custom_configuration)
14791476
self.connection_state = self.STATE.PENDING
14801477

14811478
def process_data(self, byte_data, censor_server_log=False):
@@ -1550,16 +1547,17 @@ def send_authentication_request(self):
15501547
class OAuth2ServerConnection(SSLAsyncoreDispatcher):
15511548
"""The base server-side connection that is subclassed to handle IMAP/POP/SMTP server interaction"""
15521549

1553-
def __init__(self, proxy_type, socket_map, server_address, connection_info, proxy_parent, custom_configuration):
1550+
def __init__(self, proxy_type, connection_socket, socket_map, proxy_parent, custom_configuration):
15541551
SSLAsyncoreDispatcher.__init__(self, socket_map=socket_map) # note: establish connection later due to STARTTLS
15551552
self.receive_buffer = b''
15561553
self.proxy_type = proxy_type
1557-
self.connection_info = connection_info
15581554
self.client_connection = None
1559-
self.local_address = proxy_parent.local_address
1560-
self.server_address = server_address
15611555
self.proxy_parent = proxy_parent
1556+
self.local_address = proxy_parent.local_address
1557+
self.server_address = proxy_parent.server_address
15621558
self.custom_configuration = custom_configuration
1559+
self.debug_address_string = '%s-{%s}-%s' % tuple(map(Log.format_host_port, (
1560+
connection_socket.getpeername(), connection_socket.getsockname(), self.server_address)))
15631561

15641562
self.authenticated_username = None # used only for showing last activity in the menu
15651563
self.last_activity = 0
@@ -1574,10 +1572,10 @@ def create_socket(self, socket_family=socket.AF_UNSPEC, socket_type=socket.SOCK_
15741572
return
15751573

15761574
def info_string(self):
1577-
debug_string = '; %s->%s' % (Log.format_host_port(self.connection_info), Log.format_host_port(
1578-
self.server_address)) if Log.get_level() == logging.DEBUG else ''
1575+
debug_string = self.debug_address_string if Log.get_level() == logging.DEBUG else \
1576+
Log.format_host_port(self.local_address)
15791577
account = '; %s' % self.authenticated_username if self.authenticated_username else ''
1580-
return '%s (%s%s%s)' % (self.proxy_type, Log.format_host_port(self.local_address), debug_string, account)
1578+
return '%s (%s%s)' % (self.proxy_type, debug_string, account)
15811579

15821580
def handle_connect(self):
15831581
Log.debug(self.info_string(), '--> [ Client connected ]')
@@ -1693,8 +1691,8 @@ class IMAPOAuth2ServerConnection(OAuth2ServerConnection):
16931691

16941692
# IMAP: https://tools.ietf.org/html/rfc3501
16951693
# IMAP SASL-IR: https://tools.ietf.org/html/rfc4959
1696-
def __init__(self, socket_map, server_address, connection_info, proxy_parent, custom_configuration):
1697-
super().__init__('IMAP', socket_map, server_address, connection_info, proxy_parent, custom_configuration)
1694+
def __init__(self, connection_socket, socket_map, proxy_parent, custom_configuration):
1695+
super().__init__('IMAP', connection_socket, socket_map, proxy_parent, custom_configuration)
16981696

16991697
def process_data(self, byte_data):
17001698
# note: there is no reason why IMAP STARTTLS (https://tools.ietf.org/html/rfc2595) couldn't be supported here
@@ -1735,8 +1733,8 @@ class POPOAuth2ServerConnection(OAuth2ServerConnection):
17351733
# POP3 CAPA: https://tools.ietf.org/html/rfc2449
17361734
# POP3 AUTH: https://tools.ietf.org/html/rfc1734
17371735
# POP3 SASL: https://tools.ietf.org/html/rfc5034
1738-
def __init__(self, socket_map, server_address, connection_info, proxy_parent, custom_configuration):
1739-
super().__init__('POP', socket_map, server_address, connection_info, proxy_parent, custom_configuration)
1736+
def __init__(self, connection_socket, socket_map, proxy_parent, custom_configuration):
1737+
super().__init__('POP', connection_socket, socket_map, proxy_parent, custom_configuration)
17401738
self.capa = []
17411739
self.username = None
17421740
self.password = None
@@ -1820,8 +1818,8 @@ class STARTTLS(enum.Enum):
18201818
NEGOTIATING = 2
18211819
COMPLETE = 3
18221820

1823-
def __init__(self, socket_map, server_address, connection_info, proxy_parent, custom_configuration):
1824-
super().__init__('SMTP', socket_map, server_address, connection_info, proxy_parent, custom_configuration)
1821+
def __init__(self, connection_socket, socket_map, proxy_parent, custom_configuration):
1822+
super().__init__('SMTP', connection_socket, socket_map, proxy_parent, custom_configuration)
18251823
self.ehlo = None
18261824
if self.custom_configuration['starttls']:
18271825
self.starttls_state = self.STARTTLS.PENDING
@@ -1929,35 +1927,35 @@ def handle_accept(self):
19291927
else:
19301928
Log.debug('Ignoring incoming connection to', self.info_string(), '- no connection information')
19311929

1932-
def handle_accepted(self, connection, address):
1930+
def handle_accepted(self, connection_socket, address):
19331931
if MAX_CONNECTIONS <= 0 or len(self.client_connections) < MAX_CONNECTIONS:
19341932
new_server_connection = None
19351933
try:
1936-
Log.debug('Accepting new connection to', self.info_string(), 'via', connection.getpeername())
1934+
Log.debug('Accepting new connection to', self.info_string(), 'from',
1935+
Log.format_host_port(connection_socket.getpeername()))
19371936
socket_map = {}
19381937
server_class = globals()['%sOAuth2ServerConnection' % self.proxy_type]
1939-
new_server_connection = server_class(socket_map, self.server_address, address, self,
1940-
self.custom_configuration)
1938+
new_server_connection = server_class(connection_socket, socket_map, self, self.custom_configuration)
19411939
client_class = globals()['%sOAuth2ClientConnection' % self.proxy_type]
1942-
new_client_connection = client_class(connection, socket_map, address, new_server_connection, self,
1943-
self.custom_configuration)
1940+
new_client_connection = client_class(connection_socket, socket_map, self, self.custom_configuration)
19441941
new_server_connection.client_connection = new_client_connection
1942+
new_client_connection.server_connection = new_server_connection
19451943
self.client_connections.append(new_client_connection)
19461944

19471945
threading.Thread(target=OAuth2Proxy.run_server, args=(new_client_connection, socket_map),
19481946
name='EmailOAuth2Proxy-connection-%d' % address[1], daemon=True).start()
19491947

19501948
except Exception:
1951-
connection.close()
1949+
connection_socket.close()
19521950
if new_server_connection:
19531951
new_server_connection.close()
19541952
raise
19551953
else:
19561954
error_text = '%s rejecting new connection above MAX_CONNECTIONS limit of %d' % (
19571955
self.info_string(), MAX_CONNECTIONS)
19581956
Log.error(error_text)
1959-
connection.send(b'%s\r\n' % self.bye_message(error_text).encode('utf-8'))
1960-
connection.close()
1957+
connection_socket.send(b'%s\r\n' % self.bye_message(error_text).encode('utf-8'))
1958+
connection_socket.close()
19611959

19621960
@staticmethod
19631961
def run_server(client, socket_map):

0 commit comments

Comments
 (0)