6
6
__author__ = 'Simon Robinson'
7
7
__copyright__ = 'Copyright (c) 2023 Simon Robinson'
8
8
__license__ = 'Apache 2.0'
9
- __version__ = '2023-10-06 ' # ISO 8601 (YYYY-MM-DD)
9
+ __version__ = '2023-10-19 ' # ISO 8601 (YYYY-MM-DD)
10
10
11
11
import abc
12
12
import argparse
@@ -1019,8 +1019,8 @@ def decode_credentials(str_data):
1019
1019
1020
1020
1021
1021
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 )
1024
1024
self .ssl_handshake_errors = (ssl .SSLWantReadError , ssl .SSLWantWriteError ,
1025
1025
ssl .SSLEOFError , ssl .SSLZeroReturnError )
1026
1026
self .ssl_connection , self .ssl_handshake_attempts , self .ssl_handshake_completed = self ._reset ()
@@ -1145,17 +1145,17 @@ class OAuth2ClientConnection(SSLAsyncoreDispatcher):
1145
1145
"""The base client-side connection that is subclassed to handle IMAP/POP/SMTP client interaction (note that there
1146
1146
is some protocol-specific code in here, but it is not essential, and only used to avoid logging credentials)"""
1147
1147
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 )
1151
1150
self .receive_buffer = b''
1152
1151
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
1157
1153
self .proxy_parent = proxy_parent
1154
+ self .local_address = proxy_parent .local_address
1155
+ self .server_address = proxy_parent .server_address
1158
1156
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 )))
1159
1159
1160
1160
self .censor_next_log = False # try to avoid logging credentials
1161
1161
self .authenticated = False
@@ -1164,11 +1164,11 @@ def __init__(self, proxy_type, connection, socket_map, connection_info, server_c
1164
1164
bool (custom_configuration ['local_certificate_path' ] and custom_configuration ['local_key_path' ]))
1165
1165
1166
1166
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 )
1169
1169
account = '; %s' % self .server_connection .authenticated_username if \
1170
1170
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 )
1172
1172
1173
1173
def handle_read (self ):
1174
1174
byte_data = self .recv (RECEIVE_BUFFER_SIZE )
@@ -1264,9 +1264,8 @@ def close(self):
1264
1264
class IMAPOAuth2ClientConnection (OAuth2ClientConnection ):
1265
1265
"""The client side of the connection - intercept LOGIN/AUTHENTICATE commands and replace with OAuth 2.0 SASL"""
1266
1266
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 )
1270
1269
self .authentication_tag = None
1271
1270
self .authentication_command = None
1272
1271
self .awaiting_credentials = False
@@ -1394,9 +1393,8 @@ class STATE(enum.Enum):
1394
1393
XOAUTH2_AWAITING_CONFIRMATION = 5
1395
1394
XOAUTH2_CREDENTIALS_SENT = 6
1396
1395
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 )
1400
1398
self .connection_state = self .STATE .PENDING
1401
1399
1402
1400
def process_data (self , byte_data , censor_server_log = False ):
@@ -1473,9 +1471,8 @@ class STATE(enum.Enum):
1473
1471
XOAUTH2_AWAITING_CONFIRMATION = 6
1474
1472
XOAUTH2_CREDENTIALS_SENT = 7
1475
1473
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 )
1479
1476
self .connection_state = self .STATE .PENDING
1480
1477
1481
1478
def process_data (self , byte_data , censor_server_log = False ):
@@ -1550,16 +1547,17 @@ def send_authentication_request(self):
1550
1547
class OAuth2ServerConnection (SSLAsyncoreDispatcher ):
1551
1548
"""The base server-side connection that is subclassed to handle IMAP/POP/SMTP server interaction"""
1552
1549
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 ):
1554
1551
SSLAsyncoreDispatcher .__init__ (self , socket_map = socket_map ) # note: establish connection later due to STARTTLS
1555
1552
self .receive_buffer = b''
1556
1553
self .proxy_type = proxy_type
1557
- self .connection_info = connection_info
1558
1554
self .client_connection = None
1559
- self .local_address = proxy_parent .local_address
1560
- self .server_address = server_address
1561
1555
self .proxy_parent = proxy_parent
1556
+ self .local_address = proxy_parent .local_address
1557
+ self .server_address = proxy_parent .server_address
1562
1558
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 )))
1563
1561
1564
1562
self .authenticated_username = None # used only for showing last activity in the menu
1565
1563
self .last_activity = 0
@@ -1574,10 +1572,10 @@ def create_socket(self, socket_family=socket.AF_UNSPEC, socket_type=socket.SOCK_
1574
1572
return
1575
1573
1576
1574
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 )
1579
1577
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 )
1581
1579
1582
1580
def handle_connect (self ):
1583
1581
Log .debug (self .info_string (), '--> [ Client connected ]' )
@@ -1693,8 +1691,8 @@ class IMAPOAuth2ServerConnection(OAuth2ServerConnection):
1693
1691
1694
1692
# IMAP: https://tools.ietf.org/html/rfc3501
1695
1693
# 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 )
1698
1696
1699
1697
def process_data (self , byte_data ):
1700
1698
# 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):
1735
1733
# POP3 CAPA: https://tools.ietf.org/html/rfc2449
1736
1734
# POP3 AUTH: https://tools.ietf.org/html/rfc1734
1737
1735
# 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 )
1740
1738
self .capa = []
1741
1739
self .username = None
1742
1740
self .password = None
@@ -1820,8 +1818,8 @@ class STARTTLS(enum.Enum):
1820
1818
NEGOTIATING = 2
1821
1819
COMPLETE = 3
1822
1820
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 )
1825
1823
self .ehlo = None
1826
1824
if self .custom_configuration ['starttls' ]:
1827
1825
self .starttls_state = self .STARTTLS .PENDING
@@ -1929,35 +1927,35 @@ def handle_accept(self):
1929
1927
else :
1930
1928
Log .debug ('Ignoring incoming connection to' , self .info_string (), '- no connection information' )
1931
1929
1932
- def handle_accepted (self , connection , address ):
1930
+ def handle_accepted (self , connection_socket , address ):
1933
1931
if MAX_CONNECTIONS <= 0 or len (self .client_connections ) < MAX_CONNECTIONS :
1934
1932
new_server_connection = None
1935
1933
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 ()))
1937
1936
socket_map = {}
1938
1937
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 )
1941
1939
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 )
1944
1941
new_server_connection .client_connection = new_client_connection
1942
+ new_client_connection .server_connection = new_server_connection
1945
1943
self .client_connections .append (new_client_connection )
1946
1944
1947
1945
threading .Thread (target = OAuth2Proxy .run_server , args = (new_client_connection , socket_map ),
1948
1946
name = 'EmailOAuth2Proxy-connection-%d' % address [1 ], daemon = True ).start ()
1949
1947
1950
1948
except Exception :
1951
- connection .close ()
1949
+ connection_socket .close ()
1952
1950
if new_server_connection :
1953
1951
new_server_connection .close ()
1954
1952
raise
1955
1953
else :
1956
1954
error_text = '%s rejecting new connection above MAX_CONNECTIONS limit of %d' % (
1957
1955
self .info_string (), MAX_CONNECTIONS )
1958
1956
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 ()
1961
1959
1962
1960
@staticmethod
1963
1961
def run_server (client , socket_map ):
0 commit comments