通过代理使用imaplib读取邮件

 

python通过代理访问网络的简单直接方法:

在程序开头插入以下代码

import socket
import socks
socks.set_default_proxy(socks.SOCKS5, "代理服务器IP", 代理服务器端口)
socket.socket = socks.socksocket

其原理是改重写了socket.socket这个类,使任何通过socket.socket访问网络的对象都通过socks.socksocket来访问网络,

这里要注意,使用socks时要根据代理类型设置参数,类型支持3种:socks.SOCKS5 / socks.SOCKS4 / socks.HTTP

例如以下代码显示实际IP。

from urllib import request

response = request.urlopen('http://httpbin.org/ip')
str1 = response.read().decode()
print(str1)

以下代码则显示代理IP:(request对象是通过socket.socket访问网络的)

from urllib import request

import socket
import socks
socks.set_default_proxy(socks.SOCKS5, "222.129.38.21", 57114)
socket.socket = socks.socksocket

response = request.urlopen('http://httpbin.org/ip')
str1 = response.read().decode()
print(str1)

但我不想全局使用代理,只想imaplib使用代理。那就要找到imaplib中创建网络连接的那段代码,并重写即可。

IMAP4_SSL中创建网络链接的源代码如下:

class IMAP4_SSL(IMAP4):

    def _create_socket(self):

        host = None if not self.host else self.host
        sys.audit("imaplib.open", self, self.host, self.port)
        sock = socket.create_connection((host, self.port))        
        return self.ssl_context.wrap_socket(sock,
                                            server_hostname=self.host)

 创建一个IMAP4_SSL的子类,并重写其_create_socket方法:

class SocksIMAP4SSL(IMAP4_SSL):

    def _create_socket(self):

        p_addr = '222.129.38.21'
        p_port = 57114

        sock = socks.create_connection((self.host, self.port), proxy_type=PROXY_TYPE_HTTP, proxy_addr=p_addr, proxy_port=p_port)
        return self.ssl_context.wrap_socket(sock, server_hostname=self.host)


#
# 关于 return self.ssl_context.wrap_socket(sock, server_hostname=self.host) 见到一些例子加入 ssl.HAS_SNI 判断,这里需要吗?暂时未清楚。如下:
#       server_hostname = self.host if ssl.HAS_SNI else None
#       return self.ssl_context.wrap_socket(sock, server_hostname=server_hostname)

 

-----------------------------------------

知道以上原理之前,我的摸索过程是这样的:在网上找了一轮代码,试过可行的是这段:

https://stackoverflow.com/questions/51543067/python-imap-proxy-connection (CSDN上也有人贴出 https://blog.csdn.net/alakers/article/details/103891300

import ssl, time

from socks import create_connection
from socks import PROXY_TYPE_SOCKS4
from socks import PROXY_TYPE_SOCKS5
from socks import PROXY_TYPE_HTTP

from imaplib import IMAP4
from imaplib import IMAP4_PORT
from imaplib import IMAP4_SSL_PORT
from filter import get_user_pass

__author__ = "sstevan"
__license__ = "GPLv3"
__version__ = "0.1"


class SocksIMAP4(IMAP4):
    """
    IMAP service trough SOCKS proxy. PySocks module required.
    """

    PROXY_TYPES = {"socks4": PROXY_TYPE_SOCKS4,
                   "socks5": PROXY_TYPE_SOCKS5,
                   "http": PROXY_TYPE_HTTP}

    def __init__(self, host, port=IMAP4_PORT, proxy_addr=None, proxy_port=None,
                 rdns=True, username=None, password=None, proxy_type="socks5"):

        self.proxy_addr = proxy_addr
        self.proxy_port = proxy_port
        self.rdns = rdns
        self.username = username
        self.password = password
        self.proxy_type = SocksIMAP4.PROXY_TYPES[proxy_type.lower()]

        IMAP4.__init__(self, host, port)

    def _create_socket(self):
        return create_connection((self.host, self.port), proxy_type=self.proxy_type, proxy_addr=self.proxy_addr,
                                 proxy_port=self.proxy_port, proxy_rdns=self.rdns, proxy_username=self.username,
                                 proxy_password=self.password)


class SocksIMAP4SSL(SocksIMAP4):

    def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None, proxy_addr=None,
                 proxy_port=None, rdns=True, username=None, password=None, proxy_type="socks5"):

        if ssl_context is not None and keyfile is not None:
                raise ValueError("ssl_context and keyfile arguments are mutually "
                                 "exclusive")
        if ssl_context is not None and certfile is not None:
            raise ValueError("ssl_context and certfile arguments are mutually "
                             "exclusive")

        self.keyfile = keyfile
        self.certfile = certfile
        if ssl_context is None:
            ssl_context = ssl._create_stdlib_context(certfile=certfile,
                                                     keyfile=keyfile)
        self.ssl_context = ssl_context

        SocksIMAP4.__init__(self, host, port, proxy_addr=proxy_addr, proxy_port=proxy_port,
                            rdns=rdns, username=username, password=password, proxy_type=proxy_type)

    def _create_socket(self):
        sock = SocksIMAP4._create_socket(self)
        server_hostname = self.host if ssl.HAS_SNI else None
        return self.ssl_context.wrap_socket(sock, server_hostname=server_hostname)

    def open(self, host='', port=IMAP4_PORT):
        SocksIMAP4.open(self, host, port)

def connect_proxy(imap_server, imap_port, proxy_addr, proxy_port, proxy_type, email, password):
    mailbox = SocksIMAP4SSL(host=imap_server, port=imap_port,
                            proxy_addr=proxy_addr, proxy_port=proxy_port, proxy_type=proxy_type)
    try:
        mailbox.login(email, password)
        print("We are here")
        print("OK ",)
    except Exception as e:
        print(e)
        return False
    print(mailbox.state)
    mailbox.logout()
    return True


if __name__ == "__main__":
    imap_server = "imap.rambler.ru"
    imap_port = 993

    proxy_addr = "188.120.224.172"
    proxy_port = 59923
    proxy_type = "socks5"
    email, password = get_user_pass("pm@mail11.rambler.ru:11")
    if email is not None:
        resp = connect_proxy(imap_server, imap_port, proxy_addr, proxy_port, proxy_type, email, password)
        #resp = connect(email, password, "smtp.rambler.ru")
    time.sleep(1)

但感觉这段有点把问题搞复杂了(我仅用到IMAP4_SSL,非SSL的不需要)。有没有直接对IMAP4_SSL设置代理的呢?以下这段似乎更合适:https://stackoverflow.com/questions/3386724/how-can-i-fetch-emails-via-pop-or-imap-through-a-proxy/3387230#3387230  (下面称其为B方案)

import ssl

class SocksIMAP4SSL(IMAP4_SSL):
    def open(self, host, port=IMAP4_SSL_PORT):
        self.host = host
        self.port = port
        #actual privoxy default setting, but as said, you may want to parameterize it
        self.sock = create_connection((host, port), PROXY_TYPE_HTTP, "127.0.0.1", 8118)
        self.sslobj = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
        self.file = self.sslobj.makefile('rb')

但调试了很久都不成功。

 

既然B方案是 IMAP4_SSL的子类,只修改了其中的open方法,那么追踪一下原生IMAP4_SSL的open方法的源代码,看看A方案、B方案、原生代码三者之间差别吧,

查看IMAP4_SSL的源代码及查看官方文档得知,IMAP4_SSL是IMAP4的子类,(文档链接)This is a subclass derived from IMAP4 that connects over an SSL encrypted socket。

在官方源代码上发现,A方案实际上是把IMAP4_SSL的原生源代码拷贝了出来,怪不得A方案没有调用IMAP4_SSL啦。

进一步对比跟踪代码,看到A方案也仅仅是修改了 IMAP4 的 _create_socket() 方法 [ 在open()中调用了_create_socket() ]。而_create_socket()中使用了  socket.create_connection,发现A方案跟IMAP4原生代码最核心的区别就是socket.create_connection()传入的参数不一样。

同样是调用socket.create_connection(),原生IMAP4只传入了目标地(host+port)这个参数,A方案传入的参数加了代理ip、代理端口两个参数,

原生:

class IMAP4:

    def _create_socket(self):
        host = None if not self.host else self.host
        sys.audit("imaplib.open", self, self.host, self.port)
        return socket.create_connection((host, self.port))

    def open(self, host='', port=IMAP4_PORT):
        """Setup connection to remote server on "host:port"
            (default: localhost:standard IMAP4 port).
        This connection will be used by the routines:
            read, readline, send, shutdown.
        """
        self.host = host
        self.port = port
        self.sock = self._create_socket()
        self.file = self.sock.makefile('rb')



class IMAP4_SSL(IMAP4):   

    def _create_socket(self):
        sock = IMAP4._create_socket(self)
        return self.ssl_context.wrap_socket(sock,
                                            server_hostname=self.host)

    def open(self, host='', port=IMAP4_SSL_PORT):
        IMAP4.open(self, host, port) 

        #注意,父类的self.port默认值是IMAP4_PORT(143),这里调用一下父类,self.port默认值就变成IMAP4_SSL_PORT了(993),所以重写open()这段不能删除

A方案:

class SocksIMAP4(IMAP4):

    def _create_socket(self):
        return create_connection((self.host, self.port), proxy_type=self.proxy_type, proxy_addr=self.proxy_addr,
                                 proxy_port=self.proxy_port, proxy_rdns=self.rdns, proxy_username=self.username,
                                 proxy_password=self.password)






class SocksIMAP4SSL(SocksIMAP4):

    def _create_socket(self):
        sock = SocksIMAP4._create_socket(self)
        server_hostname = self.host if ssl.HAS_SNI else None
        return self.ssl_context.wrap_socket(sock, server_hostname=server_hostname)

    def open(self, host='', port=IMAP4_PORT):
        SocksIMAP4.open(self, host, port)

 

那原生的socket.create_connection()  是怎么用的呢,有些神马玩法呢?

官方文档如下:https://docs.python.org/zh-cn/3.9/library/socket.html?highlight=create_connection#socket.create_connection

这里有一些示例 :Python socket.create_connection() Examples

https://www.programcreek.com/python/example/4161/socket.create_connection   

 

发现官方的 socket.create_connection 并不支持那么多参数啊,再仔细跟踪,IMAP4使用的是socket.create_connection,而A方案使用的是另外一个类,socks.create_connection

一个是socket,一个是socks,一个字母的差别!!

恩,再查一下socks怎么玩吧

socks的说明文档:http://socksipy.sourceforge.net/readme.txt

 

最后发现三者差异为:

#方案A用 和IMAP4_SSL都是用 ssl._create_stdlib_context(certfile=certfile,keyfile=keyfile).wrap_socket(sock, server_hostname=server_hostname) ,方案B用 ssl.wrap_socket(self.sock, self.keyfile, self.certfile)

他们相同吗?不知道,不想继续折腾了,放弃调试方案B,参考方案A直接重写IMAP4_SSL的_create_socket(),运行登录成功!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值