14

プロキシ経由でメールを送信したいと考えています。

私の現在の実装は次のとおりです。

認証を使用してsmtpサーバーに接続します。ログインに成功したら、メールを送信します。正常に動作しますが、電子メール ヘッダーを見ると、ホスト名が表示されます。代わりにプロキシを介してトンネリングしたいと思います。

どんな助けでも大歓迎です。

4

8 に答える 8

5

昨日も同様の問題がありました。これは、問題を解決するために書いたコードです。プロキシ経由ですべての smtp メソッドを使用できるようにします。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#       smtprox.py
#       Shouts to suidrewt
#
# ############################################# #
# This module allows Proxy support in MailFux.  #
# Shouts to Betrayed for telling me about       #
# http CONNECT                                  #
# ############################################# #

import smtplib
import socket

def recvline(sock):
    stop = 0
    line = ''
    while True:
        i = sock.recv(1)
        if i == '\n': stop = 1
        line += i
        if stop == 1:
            break
    return line

class ProxSMTP( smtplib.SMTP ):

    def __init__(self, host='', port=0, p_address='',p_port=0, local_hostname=None,
             timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
        """Initialize a new instance.

        If specified, `host' is the name of the remote host to which to
        connect.  If specified, `port' specifies the port to which to connect.
        By default, smtplib.SMTP_PORT is used.  An SMTPConnectError is raised
        if the specified `host' doesn't respond correctly.  If specified,
        `local_hostname` is used as the FQDN of the local host.  By default,
        the local hostname is found using socket.getfqdn().

        """
        self.p_address = p_address
        self.p_port = p_port

        self.timeout = timeout
        self.esmtp_features = {}
        self.default_port = smtplib.SMTP_PORT
        if host:
            (code, msg) = self.connect(host, port)
            if code != 220:
                raise SMTPConnectError(code, msg)
        if local_hostname is not None:
            self.local_hostname = local_hostname
        else:
            # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
            # if that can't be calculated, that we should use a domain literal
            # instead (essentially an encoded IP address like [A.B.C.D]).
            fqdn = socket.getfqdn()
            if '.' in fqdn:
                self.local_hostname = fqdn
            else:
                # We can't find an fqdn hostname, so use a domain literal
                addr = '127.0.0.1'
                try:
                    addr = socket.gethostbyname(socket.gethostname())
                except socket.gaierror:
                    pass
                self.local_hostname = '[%s]' % addr
        smtplib.SMTP.__init__(self)

    def _get_socket(self, port, host, timeout):
        # This makes it simpler for SMTP_SSL to use the SMTP connect code
        # and just alter the socket connection bit.
        if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
        new_socket = socket.create_connection((self.p_address,self.p_port), timeout)
        new_socket.sendall("CONNECT {0}:{1} HTTP/1.1\r\n\r\n".format(port,host))
        for x in xrange(2): recvline(new_socket)
        return new_socket
于 2011-06-19T02:43:18.247 に答える
2

mkerrig と Denis Cornehl が別の回答のコメントで指摘したように、smtplib から変更された SMTP クラスを使用した PySocks create_connection は、すべてのソケットにモンキーパッチを適用する必要なく機能します。

私はまだこの実装が嫌いです (他のバージョンの python または smtplib で何が壊れるかを知っている人はいます) が、これは今のところ機能します (3.8.1)。インターネット上の他の場所で機能する他のソリューションを見つけることができなかったので、ここに私の試みがあります:

  1. smtplib.SMTP クラスから init および _get_socket 関数をコピーします。
  2. init を変更して、proxy_addr と proxy_port を追加します
  3. _get_socket を変更して、socks.create_connection() (対ソケット) を返すようにします。
  4. SMTPConnectError を smtplib.SMTPConnectError に変更して動作するようにします

my_proxy_smtplib.py:

import socket
import smtplib

import socks


class ProxySMTP(smtplib.SMTP):
    def __init__(self, host='', port=0, local_hostname=None,
                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
                 source_address=None, proxy_addr=None, proxy_port=None):
        """Initialize a new instance.

        If specified, `host' is the name of the remote host to which to
        connect.  If specified, `port' specifies the port to which to connect.
        By default, smtplib.SMTP_PORT is used.  If a host is specified the
        connect method is called, and if it returns anything other than a
        success code an SMTPConnectError is raised.  If specified,
        `local_hostname` is used as the FQDN of the local host in the HELO/EHLO
        command.  Otherwise, the local hostname is found using
        socket.getfqdn(). The `source_address` parameter takes a 2-tuple (host,
        port) for the socket to bind to as its source address before
        connecting. If the host is '' and port is 0, the OS default behavior
        will be used.

        """
        self._host = host
        self.timeout = timeout
        self.esmtp_features = {}
        self.command_encoding = 'ascii'
        self.source_address = source_address
        self.proxy_addr = proxy_addr
        self.proxy_port = proxy_port

        if host:
            (code, msg) = self.connect(host, port)
            if code != 220:
                self.close()
                raise smtplib.SMTPConnectError(code, msg)
        if local_hostname is not None:
            self.local_hostname = local_hostname
        else:
            # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
            # if that can't be calculated, that we should use a domain literal
            # instead (essentially an encoded IP address like [A.B.C.D]).
            fqdn = socket.getfqdn()
            if '.' in fqdn:
                self.local_hostname = fqdn
            else:
                # We can't find an fqdn hostname, so use a domain literal
                addr = '127.0.0.1'
                try:
                    addr = socket.gethostbyname(socket.gethostname())
                except socket.gaierror:
                    pass
                self.local_hostname = '[%s]' % addr

    def _get_socket(self, host, port, timeout):
        # This makes it simpler for SMTP_SSL to use the SMTP connect code
        # and just alter the socket connection bit.
        if self.debuglevel > 0:
            self._print_debug('connect: to', (host, port), self.source_address)
        return socks.create_connection((host, port),
                                       proxy_type=socks.PROXY_TYPE_SOCKS5,
                                       timeout=timeout,
                                       proxy_addr=self.proxy_addr,
                                       proxy_port=self.proxy_port)

そして使用するには:

from my_proxy_smtplib import ProxySMTP

email_server = ProxySMTP('smtp.gmail.com', 587,
                         proxy_addr='192.168.0.1',
                         proxy_port=3487)
email_server.starttls()
email_server.login(user_email, user_pass)
email_server.sendmail(user_email, recipient_list, msg.as_string())
email_server.quit()
于 2021-07-01T19:18:51.303 に答える
0

smtplibパッチのみを適用するはるかに単純なアプローチ:

proxy_url = urlparse('http://user:pass@10.0.0.1:8080')

def _smtplib_get_socket(self, host, port, timeout):
    # Patched SMTP._get_socket
    return socks.create_connection(
        (host, port),
        timeout,
        self.source_address,
        proxy_type=socks.HTTP,
        proxy_addr=proxy_url.hostname,
        proxy_port=int(proxy_url.port),
        proxy_username=proxy_url.username,
        proxy_password=proxy_url.password,
    )

# We do this instead of wrapmodule due to
# https://github.com/Anorov/PySocks/issues/158
smtplib.SMTP._get_socket = _smtplib_get_socket
于 2022-01-08T17:56:38.440 に答える