[Python-checkins] python/dist/src/Lib smtplib.py,1.46.4.4,1.46.4.5

loewis@users.sourceforge.net loewis@users.sourceforge.net
Sun, 06 Oct 2002 10:51:13 -0700


Update of /cvsroot/python/python/dist/src/Lib
In directory usw-pr-cvs1:/tmp/cvs-serv507

Modified Files:
      Tag: release22-maint
	smtplib.py 
Log Message:
Patch #572031: AUTH method LOGIN for smtplib.


Index: smtplib.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Lib/smtplib.py,v
retrieving revision 1.46.4.4
retrieving revision 1.46.4.5
diff -C2 -d -r1.46.4.4 -r1.46.4.5
*** smtplib.py	6 Oct 2002 03:37:00 -0000	1.46.4.4
--- smtplib.py	6 Oct 2002 17:51:10 -0000	1.46.4.5
***************
*** 57,60 ****
--- 57,65 ----
  CRLF="\r\n"
  
+ OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
+ 
+ def encode_base64(s, eol=None):
+     return "".join(base64.encodestring(s).split("\n"))
+ 
  # Exception classes used by this module.
  class SMTPException(Exception):
***************
*** 392,400 ****
          del resp[0]
          for each in resp:
              m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*)',each)
              if m:
                  feature=m.group("feature").lower()
                  params=m.string[m.end("feature"):].strip()
!                 self.esmtp_features[feature]=params
          return (code,msg)
  
--- 397,426 ----
          del resp[0]
          for each in resp:
+             # To be able to communicate with as many SMTP servers as possible,
+             # we have to take the old-style auth advertisement into account,
+             # because:
+             # 1) Else our SMTP feature parser gets confused.
+             # 2) There are some servers that only advertise the auth methods we
+             #    support using the old style.
+             auth_match = OLDSTYLE_AUTH.match(each)
+             if auth_match:
+                 # This doesn't remove duplicates, but that's no problem
+                 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
+                         + " " + auth_match.groups(0)[0]
+                 continue
+  
+             # RFC 1869 requires a space between ehlo keyword and parameters.
+             # It's actually stricter, in that only spaces are allowed between
+             # parameters, but were not going to check for that here.  Note
+             # that the space isn't present if there are no parameters.
              m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*)',each)
              if m:
                  feature=m.group("feature").lower()
                  params=m.string[m.end("feature"):].strip()
!                 if feature == "auth":
!                     self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
!                             + " " + params
!                 else:
!                     self.esmtp_features[feature]=params
          return (code,msg)
  
***************
*** 495,506 ****
              challenge = base64.decodestring(challenge)
              response = user + " " + hmac.HMAC(password, challenge).hexdigest()
!             return base64.encodestring(response)[:-1]
  
          def encode_plain(user, password):
!             return base64.encodestring("%s\0%s\0%s" %
!                                        (user, user, password))[:-1]
  
          AUTH_PLAIN = "PLAIN"
          AUTH_CRAM_MD5 = "CRAM-MD5"
  
          if self.helo_resp is None and self.ehlo_resp is None:
--- 521,533 ----
              challenge = base64.decodestring(challenge)
              response = user + " " + hmac.HMAC(password, challenge).hexdigest()
!             return encode_base64(response, eol="")
  
          def encode_plain(user, password):
!             return encode_base64("%s\0%s\0%s" % (user, user, password), eol="")
!  
  
          AUTH_PLAIN = "PLAIN"
          AUTH_CRAM_MD5 = "CRAM-MD5"
+         AUTH_LOGIN = "LOGIN"
  
          if self.helo_resp is None and self.ehlo_resp is None:
***************
*** 519,524 ****
          # less preferred methods. Except for the purpose of testing the weaker
          # ones, we prefer stronger methods like CRAM-MD5:
!         preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN]
!         #preferred_auths = [AUTH_PLAIN, AUTH_CRAM_MD5]
  
          # Determine the authentication method we'll use
--- 546,550 ----
          # less preferred methods. Except for the purpose of testing the weaker
          # ones, we prefer stronger methods like CRAM-MD5:
!         preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]
  
          # Determine the authentication method we'll use
***************
*** 528,532 ****
                  authmethod = method
                  break
-         if self.debuglevel > 0: print "AuthMethod:", authmethod
  
          if authmethod == AUTH_CRAM_MD5:
--- 554,557 ----
***************
*** 539,542 ****
--- 564,573 ----
              (code, resp) = self.docmd("AUTH",
                  AUTH_PLAIN + " " + encode_plain(user, password))
+         elif authmethod == AUTH_LOGIN:
+             (code, resp) = self.docmd("AUTH",
+                 "%s %s" % (AUTH_LOGIN, encode_base64(user, eol="")))
+             if code != 334:
+                 raise SMTPAuthenticationError(code, resp)
+             (code, resp) = self.docmd(encode_base64(password, eol=""))
          elif authmethod == None:
              raise SMTPException("No suitable authentication method found.")