Edit file File name : imapbackend.py Content :# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4; encoding:utf8 -*- # # Copyright 2002 Ben Escoto <ben@emerose.org> # Copyright 2007 Kenneth Loafman <kenneth@loafman.com> # Copyright 2008 Ian Barton <ian@manor-farm.org> # # This file is part of duplicity. # # Duplicity is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # # Duplicity is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with duplicity; if not, write to the Free Software Foundation, # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from future import standard_library standard_library.install_aliases() from builtins import input import email import email.encoders import email.mime.multipart import getpass import imaplib import os import re import socket import sys import time from email.parser import Parser try: from email.policy import default # pylint: disable=import-error except: pass # TODO: should probably change use of socket.sslerror instead of doing this if sys.version_info.major >= 3: import ssl socket.sslerror = ssl.SSLError from duplicity import config from duplicity import log from duplicity.errors import * # pylint: disable=unused-wildcard-import import duplicity.backend class ImapBackend(duplicity.backend.Backend): def __init__(self, parsed_url): duplicity.backend.Backend.__init__(self, parsed_url) log.Debug(u"I'm %s (scheme %s) connecting to %s as %s" % (self.__class__.__name__, parsed_url.scheme, parsed_url.hostname, parsed_url.username)) # Store url for reconnection on error self.url = parsed_url # Set the username if (parsed_url.username is None): username = eval(input(u'Enter account userid: ')) else: username = parsed_url.username # Set the password if (not parsed_url.password): if u'IMAP_PASSWORD' in os.environ: password = os.environ.get(u'IMAP_PASSWORD') else: password = getpass.getpass(u"Enter account password: ") else: password = parsed_url.password self.username = username self.password = password self.resetConnection() def resetConnection(self): parsed_url = self.url try: imap_server = os.environ[u'IMAP_SERVER'] except KeyError: imap_server = parsed_url.hostname # Try to close the connection cleanly try: self.conn.close() # pylint:disable=access-member-before-definition except Exception: pass if (parsed_url.scheme == u"imap"): cl = imaplib.IMAP4 self.conn = cl(imap_server, 143) elif (parsed_url.scheme == u"imaps"): cl = imaplib.IMAP4_SSL self.conn = cl(imap_server, 993) log.Debug(u"Type of imap class: %s" % (cl.__name__)) self.remote_dir = re.sub(r'^/', r'', parsed_url.path, 1) # Login if (not(config.imap_full_address)): self.conn.login(self.username, self.password) self.conn.select(config.imap_mailbox) log.Info(u"IMAP connected") else: self.conn.login(self.username + u"@" + parsed_url.hostname, self.password) self.conn.select(config.imap_mailbox) log.Info(u"IMAP connected") def prepareBody(self, f, rname): mp = email.mime.multipart.MIMEMultipart() # I am going to use the remote_dir as the From address so that # multiple archives can be stored in an IMAP account and can be # accessed separately mp[u"From"] = self.remote_dir mp[u"Subject"] = rname.decode() a = email.mime.multipart.MIMEBase(u"application", u"binary") a.set_payload(f.read()) email.encoders.encode_base64(a) mp.attach(a) return mp.as_string() def _put(self, source_path, remote_filename): f = source_path.open(u"rb") allowedTimeout = config.timeout if (allowedTimeout == 0): # Allow a total timeout of 1 day allowedTimeout = 2880 while allowedTimeout > 0: try: self.conn.select(remote_filename) body = self.prepareBody(f, remote_filename) # If we don't select the IMAP folder before # append, the message goes into the INBOX. self.conn.select(config.imap_mailbox) self.conn.append(config.imap_mailbox, None, None, body.encode()) break except (imaplib.IMAP4.abort, socket.error, socket.sslerror): allowedTimeout -= 1 log.Info(u"Error saving '%s', retrying in 30s " % remote_filename) time.sleep(30) while allowedTimeout > 0: try: self.resetConnection() break except (imaplib.IMAP4.abort, socket.error, socket.sslerror): allowedTimeout -= 1 log.Info(u"Error reconnecting, retrying in 30s ") time.sleep(30) log.Info(u"IMAP mail with '%s' subject stored" % remote_filename) def _get(self, remote_filename, local_path): allowedTimeout = config.timeout if (allowedTimeout == 0): # Allow a total timeout of 1 day allowedTimeout = 2880 while allowedTimeout > 0: try: self.conn.select(config.imap_mailbox) (result, flist) = self.conn.search(None, u'Subject', remote_filename) if result != u"OK": raise Exception(flist[0]) # check if there is any result if flist[0] == u'': raise Exception(u"no mail with subject %s") (result, flist) = self.conn.fetch(flist[0], u"(RFC822)") if result != u"OK": raise Exception(flist[0]) rawbody = flist[0][1] p = Parser() m = p.parsestr(rawbody.decode()) mp = m.get_payload(0) body = mp.get_payload(decode=True) break except (imaplib.IMAP4.abort, socket.error, socket.sslerror): allowedTimeout -= 1 log.Info(u"Error loading '%s', retrying in 30s " % remote_filename) time.sleep(30) while allowedTimeout > 0: try: self.resetConnection() break except (imaplib.IMAP4.abort, socket.error, socket.sslerror): allowedTimeout -= 1 log.Info(u"Error reconnecting, retrying in 30s ") time.sleep(30) tfile = local_path.open(u"wb") tfile.write(body) tfile.close() local_path.setdata() log.Info(u"IMAP mail with '%s' subject fetched" % remote_filename) def _list(self): ret = [] (result, flist) = self.conn.select(config.imap_mailbox) if result != u"OK": raise BackendException(flist[0]) # Going to find all the archives which have remote_dir in the From # address # Search returns an error if you haven't selected an IMAP folder. (result, flist) = self.conn.search(None, u'FROM', self.remote_dir) if result != u"OK": raise Exception(flist[0]) if flist[0] == b'': return ret nums = flist[0].strip().split(b" ") set = b"%s:%s" % (nums[0], nums[-1]) # pylint: disable=redefined-builtin (result, flist) = self.conn.fetch(set, u"(BODY[HEADER])") if result != u"OK": raise Exception(flist[0]) for msg in flist: if (len(msg) == 1): continue if sys.version_info.major >= 3: headers = Parser(policy=default).parsestr(msg[1].decode(u"unicode-escape")) # noqa # pylint: disable=unsubscriptable-object else: headers = Parser().parsestr(msg[1].decode(u"unicode-escape")) # pylint: disable=unsubscriptable-object subj = headers[u"subject"] header_from = headers[u"from"] # Catch messages with empty headers which cause an exception. if (not (header_from is None)): if (re.compile(u"^" + self.remote_dir + u"$").match(header_from)): ret.append(subj) log.Info(u"IMAP flist: %s %s" % (subj, header_from)) return ret def imapf(self, fun, *args): (ret, flist) = fun(*args) if ret != u"OK": raise Exception(flist[0]) return flist def delete_single_mail(self, i): self.imapf(self.conn.store, i, u"+FLAGS", u'\\DELETED') def expunge(self): flist = self.imapf(self.conn.expunge) def _delete_list(self, filename_list): for filename in filename_list: flist = self.imapf(self.conn.search, None, u"(SUBJECT %s)" % filename) flist = flist[0].split() if len(flist) > 0 and flist[0] != u"": self.delete_single_mail(flist[0]) log.Notice(u"marked %s to be deleted" % filename) self.expunge() log.Notice(u"IMAP expunged %s files" % len(filename_list)) def _close(self): self.conn.select(config.imap_mailbox) self.conn.close() self.conn.logout() duplicity.backend.register_backend(u"imap", ImapBackend) duplicity.backend.register_backend(u"imaps", ImapBackend) duplicity.backend.uses_netloc.extend([u'imap', u'imaps']) Save