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