使用Python通过Exchange进行集成Windows身份验证(NTLM)的SMTP

13 投票
3 回答
16242 浏览
提问于 2025-04-15 23:12

我想用当前登录的Windows用户的凭证来验证与Exchange服务器的SMTP连接,使用的是NTLM认证。

我知道有一个叫做 python-ntlm 的模块,还有两个 补丁 可以让SMTP支持NTLM认证,不过我想用当前用户的安全令牌,而不想输入用户名和密码。

这个问题和 用Python和urllib2进行Windows认证 的问题非常相似。

3 个回答

3

在使用Python 2.7.x的时候,发送NTLM类型3消息会因为指定了空的命令而失败:

code, response = smtp.docmd("", ntlm_message)

这样做虽然能把正确的响应发回服务器,但因为docmd()调用了putcmd(),所以会在消息前面多加一个空格。

在smtplib.py文件中:

def putcmd(self, cmd, args=""):
    """Send a command to the server."""
    if args == "":
        str = '%s%s' % (cmd, CRLF)
    else:
        str = '%s %s%s' % (cmd, args, CRLF)
    self.send(str)

# ...

def docmd(self, cmd, args=""):
    """Send a command, and return its response code."""
    self.putcmd(cmd, args)
    return self.getreply()

这就导致程序走到了else条件下,结果发送了str(' ' + ntlm_message + CRLF),这会导致出现(501, '参数或参数中的语法错误')的错误。

所以解决办法就是直接把NTLM消息作为命令发送。

code, response = smtp.docmd(ntlm_message)

针对上面的问题已经提交了修复方案,不过谁知道什么时候会被审核或接受呢。

6

这个回答很棒,不过我想更新一下关于Python 3的内容。

def asbase64(msg):
    # encoding the message then convert to string
    return base64.b64encode(msg).decode("utf-8")
17

虽然下面的解决方案只用了Python的Win32扩展(里面的sspi示例代码非常有帮助),但问题中提到的python-ntlm的IMAP和SMTP补丁也提供了很好的参考。

from smtplib import SMTPException, SMTPAuthenticationError
import string
import base64
import sspi

# NTLM Guide -- http://curl.haxx.se/rfc/ntlm.html

SMTP_EHLO_OKAY = 250
SMTP_AUTH_CHALLENGE = 334
SMTP_AUTH_OKAY = 235

def asbase64(msg):
    # encoding the message then convert to string
    return base64.b64encode(msg).decode("utf-8")

def connect_to_exchange_as_current_user(smtp):
    """Example:
    >>> import smtplib
    >>> smtp = smtplib.SMTP("my.smtp.server")
    >>> connect_to_exchange_as_current_user(smtp)
    """

    # Send the SMTP EHLO command
    code, response = smtp.ehlo()
    if code != SMTP_EHLO_OKAY:
        raise SMTPException("Server did not respond as expected to EHLO command")

    sspiclient = sspi.ClientAuth('NTLM')

    # Generate the NTLM Type 1 message
    sec_buffer=None
    err, sec_buffer = sspiclient.authorize(sec_buffer)
    ntlm_message = asbase64(sec_buffer[0].Buffer)

    # Send the NTLM Type 1 message -- Authentication Request
    code, response = smtp.docmd("AUTH", "NTLM " + ntlm_message)

    # Verify the NTLM Type 2 response -- Challenge Message
    if code != SMTP_AUTH_CHALLENGE:
        raise SMTPException("Server did not respond as expected to NTLM negotiate message")

    # Generate the NTLM Type 3 message
    err, sec_buffer = sspiclient.authorize(base64.decodebytes(response))
    ntlm_message = asbase64(sec_buffer[0].Buffer)

    # Send the NTLM Type 3 message -- Response Message
    code, response = smtp.docmd(ntlm_message)
    if code != SMTP_AUTH_OKAY:
        raise SMTPAuthenticationError(code, response)

撰写回答