Authentication command - requires response processing. 'mechanism' specifies which authentication mechanism is to be used - the valid values are those listed in the 'auth' element of 'esmtp_features'. 'authobject' must be a callable object taking a single argument:
(self, mechanism, authobject, *, initial_response_ok=True)
| 621 | raise SMTPHeloError(code, resp) |
| 622 | |
| 623 | def auth(self, mechanism, authobject, *, initial_response_ok=True): |
| 624 | """Authentication command - requires response processing. |
| 625 | |
| 626 | 'mechanism' specifies which authentication mechanism is to |
| 627 | be used - the valid values are those listed in the 'auth' |
| 628 | element of 'esmtp_features'. |
| 629 | |
| 630 | 'authobject' must be a callable object taking a single argument: |
| 631 | |
| 632 | data = authobject(challenge) |
| 633 | |
| 634 | It will be called to process the server's challenge response; the |
| 635 | challenge argument it is passed will be a bytes. It should return |
| 636 | an ASCII string that will be base64 encoded and sent to the server. |
| 637 | |
| 638 | Keyword arguments: |
| 639 | - initial_response_ok: Allow sending the RFC 4954 initial-response |
| 640 | to the AUTH command, if the authentication methods supports it. |
| 641 | """ |
| 642 | # RFC 4954 allows auth methods to provide an initial response. Not all |
| 643 | # methods support it. By definition, if they return something other |
| 644 | # than None when challenge is None, then they do. See issue #15014. |
| 645 | mechanism = mechanism.upper() |
| 646 | initial_response = (authobject() if initial_response_ok else None) |
| 647 | if initial_response is not None: |
| 648 | response = encode_base64(initial_response.encode('ascii'), eol='') |
| 649 | (code, resp) = self.docmd("AUTH", mechanism + " " + response) |
| 650 | self._auth_challenge_count = 1 |
| 651 | else: |
| 652 | (code, resp) = self.docmd("AUTH", mechanism) |
| 653 | self._auth_challenge_count = 0 |
| 654 | # If server responds with a challenge, send the response. |
| 655 | while code == 334: |
| 656 | self._auth_challenge_count += 1 |
| 657 | challenge = base64.decodebytes(resp) |
| 658 | response = encode_base64( |
| 659 | authobject(challenge).encode('ascii'), eol='') |
| 660 | (code, resp) = self.docmd(response) |
| 661 | # If server keeps sending challenges, something is wrong. |
| 662 | if self._auth_challenge_count > _MAXCHALLENGE: |
| 663 | raise SMTPException( |
| 664 | "Server AUTH mechanism infinite loop. Last response: " |
| 665 | + repr((code, resp)) |
| 666 | ) |
| 667 | if code in (235, 503): |
| 668 | return (code, resp) |
| 669 | raise SMTPAuthenticationError(code, resp) |
| 670 | |
| 671 | def auth_cram_md5(self, challenge=None): |
| 672 | """ Authobject to use with CRAM-MD5 authentication. Requires self.user |