Skip to content

Commit ef46726

Browse files
committed
Add support for AUTH PLAIN to POP
1 parent 9c8c4b2 commit ef46726

File tree

1 file changed

+26
-5
lines changed

1 file changed

+26
-5
lines changed

emailproxy.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -812,8 +812,9 @@ class POPOAuth2ClientConnection(OAuth2ClientConnection):
812812

813813
class AUTH(enum.Enum):
814814
PENDING = 1
815-
AWAITING_PASS = 2
816-
AWAITING_AUTH = 3
815+
PLAIN_AWAITING_CREDENTIALS = 2
816+
AWAITING_PASS = 3
817+
AWAITING_AUTH = 4
817818
CREDENTIALS_SENT = 5
818819

819820
def __init__(self, connection, socket_map, connection_info, server_connection, proxy_parent, custom_configuration):
@@ -825,21 +826,39 @@ def process_data(self, byte_data, censor_server_log=False):
825826
str_data = byte_data.decode('utf-8', 'replace').rstrip('\r\n')
826827
str_data_lower = str_data.lower()
827828

828-
if self.authentication_state is self.AUTH.PENDING and str_data_lower.startswith('user'):
829+
if self.authentication_state is self.AUTH.PENDING and str_data_lower.startswith('auth plain'):
830+
if len(str_data) > 11: # 11 = len('AUTH PLAIN ') - this method can have the login details either inline...
831+
(self.server_connection.username, self.server_connection.password) = OAuth2Helper.decode_credentials(
832+
str_data[11:])
833+
self.send_authentication_request()
834+
else: # ...or requested separately
835+
self.authentication_state = self.AUTH.PLAIN_AWAITING_CREDENTIALS
836+
self.censor_next_log = True
837+
self.send(b'+OK\r\n') # request details (note: space after response code is mandatory)
838+
839+
elif self.authentication_state is self.AUTH.PLAIN_AWAITING_CREDENTIALS:
840+
(self.server_connection.username, self.server_connection.password) = OAuth2Helper.decode_credentials(
841+
str_data)
842+
self.send_authentication_request()
843+
844+
elif self.authentication_state is self.AUTH.PENDING and str_data_lower.startswith('user'):
829845
self.server_connection.username = str_data[5:] # 5 = len('USER ')
830846
self.authentication_state = self.AUTH.AWAITING_PASS
831847
self.censor_next_log = True
832848
self.send(b'+OK\r\n') # request password
833849

834850
elif self.authentication_state is self.AUTH.AWAITING_PASS and str_data_lower.startswith('pass'):
835851
self.server_connection.password = str_data[5:] # 5 = len('PASS ')
836-
self.authentication_state = self.AUTH.AWAITING_AUTH
837-
super().process_data(b'AUTH XOAUTH2\r\n')
852+
self.send_authentication_request()
838853

839854
# some other command that we don't handle - pass directly to server
840855
else:
841856
super().process_data(byte_data)
842857

858+
def send_authentication_request(self):
859+
self.authentication_state = self.AUTH.AWAITING_AUTH
860+
super().process_data(b'AUTH XOAUTH2\r\n')
861+
843862

844863
class OAuth2ServerConnection(asyncore.dispatcher_with_send):
845864
"""The base server-side connection, setting up STARTTLS if requested, subclassed for IMAP/SMTP server interaction"""
@@ -1086,6 +1105,8 @@ def process_data(self, byte_data):
10861105
class POPOAuth2ServerConnection(OAuth2ServerConnection):
10871106
"""The POP server side - submit credentials, then watch for +OK and ignore subsequent data"""
10881107

1108+
# POP3: https://tools.ietf.org/html/rfc1734
1109+
# POP3 SASL: https://tools.ietf.org/html/rfc5034
10891110
def __init__(self, socket_map, server_address, connection_info, proxy_parent, custom_configuration):
10901111
super().__init__('POP', socket_map, server_address, connection_info, proxy_parent, custom_configuration)
10911112
self.username = None

0 commit comments

Comments
 (0)