add proxy support powered by PySocks

Read proxy option in imapserver, instantiate a class in imaplibutil
using a self-defined keyword and a socket instance, and use this socket
instance to substitute the default socket instance used in imaplib2.

Signed-off-by: 夏恺(Xia Kai) <xiaket@gmail.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
This commit is contained in:
夏恺(Xia Kai) 2015-02-15 22:16:20 +08:00 committed by Nicolas Sebrecht
parent a1383da9b3
commit d36209a305
3 changed files with 104 additions and 18 deletions

View File

@ -380,6 +380,15 @@ remoterepository = RemoteExample
#filterheaders = X-Some-Weird-Header #filterheaders = X-Some-Weird-Header
# This option stands in the [Account Test] section.
#
# Use proxy connection for this account. Usefull to bypass the GFW in China.
# To specify a proxy connection, join proxy type, host and port with colons.
# Available proxy types are SOCKS5, SOCKS4, HTTP.
# You also need to install PySocks through pip.
#
#proxy = SOCKS5:localhost:9999
[Repository LocalExample] [Repository LocalExample]
# Each repository requires a "type" declaration. The types supported for # Each repository requires a "type" declaration. The types supported for

View File

@ -21,6 +21,7 @@ import subprocess
from sys import exc_info from sys import exc_info
import threading import threading
from hashlib import sha1 from hashlib import sha1
import socket
from offlineimap.ui import getglobalui from offlineimap.ui import getglobalui
from offlineimap import OfflineImapError from offlineimap import OfflineImapError
@ -69,6 +70,37 @@ class UsefulIMAPMixIn(object):
def _mesg(self, s, tn=None, secs=None): def _mesg(self, s, tn=None, secs=None):
new_mesg(self, s, tn, secs) new_mesg(self, s, tn, secs)
# Overrides private function from IMAP4 (@imaplib2)
def open_socket(self):
"""open_socket()
Open socket choosing first address family available."""
msg = (-1, 'could not open socket')
for res in socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
# use socket of our own, possiblly socksified socket.
s = self.socket(af, socktype, proto)
except socket.error, msg:
continue
try:
for i in (0, 1):
try:
s.connect(sa)
break
except socket.error, msg:
if len(msg.args) < 2 or msg.args[0] != errno.EINTR:
raise
else:
raise socket.error(msg)
except socket.error, msg:
s.close()
continue
break
else:
raise socket.error(msg)
return s
class IMAP4_Tunnel(UsefulIMAPMixIn, IMAP4): class IMAP4_Tunnel(UsefulIMAPMixIn, IMAP4):
"""IMAP4 client class over a tunnel """IMAP4 client class over a tunnel
@ -79,6 +111,9 @@ class IMAP4_Tunnel(UsefulIMAPMixIn, IMAP4):
The result will be in PREAUTH stage.""" The result will be in PREAUTH stage."""
def __init__(self, tunnelcmd, **kwargs): def __init__(self, tunnelcmd, **kwargs):
if "use_socket" in kwargs:
self.socket = kwargs['use_socket']
del kwargs['use_socket']
IMAP4.__init__(self, tunnelcmd, **kwargs) IMAP4.__init__(self, tunnelcmd, **kwargs)
def open(self, host, port): def open(self, host, port):
@ -141,6 +176,9 @@ class WrappedIMAP4_SSL(UsefulIMAPMixIn, IMAP4_SSL):
"""Improved version of imaplib.IMAP4_SSL overriding select().""" """Improved version of imaplib.IMAP4_SSL overriding select()."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if "use_socket" in kwargs:
self.socket = kwargs['use_socket']
del kwargs['use_socket']
self._fingerprint = kwargs.get('fingerprint', None) self._fingerprint = kwargs.get('fingerprint', None)
if type(self._fingerprint) != type([]): if type(self._fingerprint) != type([]):
self._fingerprint = [self._fingerprint] self._fingerprint = [self._fingerprint]
@ -171,7 +209,11 @@ class WrappedIMAP4_SSL(UsefulIMAPMixIn, IMAP4_SSL):
class WrappedIMAP4(UsefulIMAPMixIn, IMAP4): class WrappedIMAP4(UsefulIMAPMixIn, IMAP4):
"""Improved version of imaplib.IMAP4 overriding select().""" """Improved version of imaplib.IMAP4 overriding select()."""
pass def __init__(self, *args, **kwargs):
if "use_socket" in kwargs:
self.socket = kwargs['use_socket']
del kwargs['use_socket']
IMAP4.__init__(self, *args, **kwargs)
def Internaldate2epoch(resp): def Internaldate2epoch(resp):

View File

@ -15,10 +15,7 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from offlineimap import imaplibutil, imaputil, threadutil, OfflineImapError
from offlineimap.ui import getglobalui
from threading import Lock, BoundedSemaphore, Thread, Event, currentThread from threading import Lock, BoundedSemaphore, Thread, Event, currentThread
import offlineimap.accounts
import hmac import hmac
import socket import socket
import base64 import base64
@ -28,6 +25,11 @@ from sys import exc_info
from socket import gaierror from socket import gaierror
from ssl import SSLError, cert_time_to_seconds from ssl import SSLError, cert_time_to_seconds
from offlineimap import imaplibutil, imaputil, threadutil, OfflineImapError
import offlineimap.accounts
from offlineimap.ui import getglobalui
try: try:
# do we have a recent pykerberos? # do we have a recent pykerberos?
have_gss = False have_gss = False
@ -101,6 +103,31 @@ class IMAPServer:
self.gss_vc = None self.gss_vc = None
self.gssapi = False self.gssapi = False
# In order to support proxy connection, we have to override the
# default socket instance with our own socksified socket instance.
# We add this option to bypass the GFW in China.
_account_section = 'Account ' + self.repos.account.name
if not self.config.has_option(_account_section, 'proxy'):
self.proxied_socket = socket.socket
else:
proxy = self.config.get(_account_section, 'proxy')
# Powered by PySocks.
try:
import socks
proxy_type, host, port = proxy.split(":")
port = int(port)
socks.setdefaultproxy(getattr(socks, proxy_type), host, port)
self.proxied_socket = socks.socksocket
except ImportError:
self.ui.warn("PySocks not installed, ignoring proxy option.")
self.proxied_socket = socket.socket
except (AttributeError, ValueError) as e:
self.ui.warn("Bad proxy option %s for account %s: %s "
"Ignoring proxy option."%
(proxy, self.repos.account.name, e))
self.proxied_socket = socket.socket
def __getpassword(self): def __getpassword(self):
"""Returns the server password or None""" """Returns the server password or None"""
if self.goodpassword != None: # use cached good one first if self.goodpassword != None: # use cached good one first
@ -391,25 +418,33 @@ class IMAPServer:
# Generate a new connection. # Generate a new connection.
if self.tunnel: if self.tunnel:
self.ui.connecting('tunnel', self.tunnel) self.ui.connecting('tunnel', self.tunnel)
imapobj = imaplibutil.IMAP4_Tunnel(self.tunnel, imapobj = imaplibutil.IMAP4_Tunnel(
timeout=socket.getdefaulttimeout()) self.tunnel,
timeout=socket.getdefaulttimeout(),
use_socket=self.proxied_socket,
)
success = 1 success = 1
elif self.usessl: elif self.usessl:
self.ui.connecting(self.hostname, self.port) self.ui.connecting(self.hostname, self.port)
imapobj = imaplibutil.WrappedIMAP4_SSL(self.hostname, imapobj = imaplibutil.WrappedIMAP4_SSL(
self.port, self.hostname,
self.sslclientkey, self.port,
self.sslclientcert, self.sslclientkey,
self.sslcacertfile, self.sslclientcert,
self.__verifycert, self.sslcacertfile,
self.sslversion, self.__verifycert,
timeout=socket.getdefaulttimeout(), self.sslversion,
fingerprint=self.fingerprint timeout=socket.getdefaulttimeout(),
) fingerprint=self.fingerprint,
use_socket=self.proxied_socket,
)
else: else:
self.ui.connecting(self.hostname, self.port) self.ui.connecting(self.hostname, self.port)
imapobj = imaplibutil.WrappedIMAP4(self.hostname, self.port, imapobj = imaplibutil.WrappedIMAP4(
timeout=socket.getdefaulttimeout()) self.hostname, self.port,
timeout=socket.getdefaulttimeout(),
use_socket=self.proxied_socket,
)
if not self.preauth_tunnel: if not self.preauth_tunnel:
try: try: