Package duplicity :: Package backends :: Module imapbackend
[hide private]
[frames] | no frames]

Source Code for Module duplicity.backends.imapbackend

  1  # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- 
  2  # 
  3  # Copyright 2002 Ben Escoto <ben@emerose.org> 
  4  # Copyright 2007 Kenneth Loafman <kenneth@loafman.com> 
  5  # Copyright 2008 Ian Barton <ian@manor-farm.org> 
  6  # 
  7  # This file is part of duplicity. 
  8  # 
  9  # Duplicity is free software; you can redistribute it and/or modify it 
 10  # under the terms of the GNU General Public License as published by the 
 11  # Free Software Foundation; either version 2 of the License, or (at your 
 12  # option) any later version. 
 13  # 
 14  # Duplicity is distributed in the hope that it will be useful, but 
 15  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 16  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 17  # General Public License for more details. 
 18  # 
 19  # You should have received a copy of the GNU General Public License 
 20  # along with duplicity; if not, write to the Free Software Foundation, 
 21  # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 22   
 23  import imaplib 
 24  import re 
 25  import os 
 26  import time 
 27  import socket 
 28  import StringIO 
 29  import rfc822 
 30  import getpass 
 31  import email 
 32   
 33  import duplicity.backend 
 34  from duplicity import globals 
 35  from duplicity import log 
 36  from duplicity.errors import * #@UnusedWildImport 
 37   
 38   
39 -class ImapBackend(duplicity.backend.Backend):
40 - def __init__(self, parsed_url):
41 duplicity.backend.Backend.__init__(self, parsed_url) 42 43 log.Debug("I'm %s (scheme %s) connecting to %s as %s" % 44 (self.__class__.__name__, parsed_url.scheme, parsed_url.hostname, parsed_url.username)) 45 46 # Store url for reconnection on error 47 self._url = parsed_url 48 49 # Set the username 50 if ( parsed_url.username is None ): 51 username = raw_input('Enter account userid: ') 52 else: 53 username = parsed_url.username 54 55 # Set the password 56 if ( not parsed_url.password ): 57 if os.environ.has_key('IMAP_PASSWORD'): 58 password = os.environ.get('IMAP_PASSWORD') 59 else: 60 password = getpass.getpass("Enter account password: ") 61 else: 62 password = parsed_url.password 63 64 self._username = username 65 self._password = password 66 self._resetConnection()
67
68 - def _resetConnection(self):
69 parsed_url = self._url 70 try: 71 imap_server = os.environ['IMAP_SERVER'] 72 except KeyError: 73 imap_server = parsed_url.hostname 74 75 # Try to close the connection cleanly 76 try: 77 self._conn.close() 78 except Exception: 79 pass 80 81 if (parsed_url.scheme == "imap"): 82 cl = imaplib.IMAP4 83 self._conn = cl(imap_server, 143) 84 elif (parsed_url.scheme == "imaps"): 85 cl = imaplib.IMAP4_SSL 86 self._conn = cl(imap_server, 993) 87 88 log.Debug("Type of imap class: %s" % (cl.__name__)) 89 self.remote_dir = re.sub(r'^/', r'', parsed_url.path, 1) 90 91 # Login 92 if (not(globals.imap_full_address)): 93 self._conn.login(self._username, self._password) 94 self._conn.select(globals.imap_mailbox) 95 log.Info("IMAP connected") 96 else: 97 self._conn.login(self._username + "@" + parsed_url.hostname, self._password) 98 self._conn.select(globals.imap_mailbox) 99 log.Info("IMAP connected")
100 101
102 - def _prepareBody(self,f,rname):
103 mp = email.MIMEMultipart.MIMEMultipart() 104 105 # I am going to use the remote_dir as the From address so that 106 # multiple archives can be stored in an IMAP account and can be 107 # accessed separately 108 mp["From"]=self.remote_dir 109 mp["Subject"]=rname 110 111 a = email.MIMEBase.MIMEBase("application","binary") 112 a.set_payload(f.read()) 113 114 email.Encoders.encode_base64(a) 115 116 mp.attach(a) 117 118 return mp.as_string()
119
120 - def put(self, source_path, remote_filename = None):
121 if not remote_filename: 122 remote_filename = source_path.get_filename() 123 f=source_path.open("rb") 124 allowedTimeout = globals.timeout 125 if (allowedTimeout == 0): 126 # Allow a total timeout of 1 day 127 allowedTimeout = 2880 128 while allowedTimeout > 0: 129 try: 130 self._conn.select(remote_filename) 131 body=self._prepareBody(f,remote_filename) 132 # If we don't select the IMAP folder before 133 # append, the message goes into the INBOX. 134 self._conn.select(globals.imap_mailbox) 135 self._conn.append(globals.imap_mailbox, None, None, body) 136 break 137 except (imaplib.IMAP4.abort, socket.error, socket.sslerror): 138 allowedTimeout -= 1 139 log.Info("Error saving '%s', retrying in 30s " % remote_filename) 140 time.sleep(30) 141 while allowedTimeout > 0: 142 try: 143 self._resetConnection() 144 break 145 except (imaplib.IMAP4.abort, socket.error, socket.sslerror): 146 allowedTimeout -= 1 147 log.Info("Error reconnecting, retrying in 30s ") 148 time.sleep(30) 149 150 log.Info("IMAP mail with '%s' subject stored" % remote_filename)
151
152 - def get(self, remote_filename, local_path):
153 allowedTimeout = globals.timeout 154 if (allowedTimeout == 0): 155 # Allow a total timeout of 1 day 156 allowedTimeout = 2880 157 while allowedTimeout > 0: 158 try: 159 self._conn.select(globals.imap_mailbox) 160 (result,list) = self._conn.search(None, 'Subject', remote_filename) 161 if result != "OK": 162 raise Exception(list[0]) 163 164 #check if there is any result 165 if list[0] == '': 166 raise Exception("no mail with subject %s") 167 168 (result,list) = self._conn.fetch(list[0],"(RFC822)") 169 170 if result != "OK": 171 raise Exception(list[0]) 172 rawbody=list[0][1] 173 174 p = email.Parser.Parser() 175 176 m = p.parsestr(rawbody) 177 178 mp = m.get_payload(0) 179 180 body = mp.get_payload(decode=True) 181 break 182 except (imaplib.IMAP4.abort, socket.error, socket.sslerror): 183 allowedTimeout -= 1 184 log.Info("Error loading '%s', retrying in 30s " % remote_filename) 185 time.sleep(30) 186 while allowedTimeout > 0: 187 try: 188 self._resetConnection() 189 break 190 except (imaplib.IMAP4.abort, socket.error, socket.sslerror): 191 allowedTimeout -= 1 192 log.Info("Error reconnecting, retrying in 30s ") 193 time.sleep(30) 194 195 tfile = local_path.open("wb") 196 tfile.write(body) 197 local_path.setdata() 198 log.Info("IMAP mail with '%s' subject fetched" % remote_filename)
199
200 - def list(self):
201 ret = [] 202 (result,list) = self._conn.select(globals.imap_mailbox) 203 if result != "OK": 204 raise BackendException(list[0]) 205 206 # Going to find all the archives which have remote_dir in the From 207 # address 208 209 # Search returns an error if you haven't selected an IMAP folder. 210 (result,list) = self._conn.search(None, 'FROM', self.remote_dir) 211 if result!="OK": 212 raise Exception(list[0]) 213 if list[0]=='': 214 return ret 215 nums=list[0].split(" ") 216 set="%s:%s"%(nums[0],nums[-1]) 217 (result,list) = self._conn.fetch(set,"(BODY[HEADER])") 218 if result!="OK": 219 raise Exception(list[0]) 220 221 for msg in list: 222 if (len(msg)==1):continue 223 io = StringIO.StringIO(msg[1]) 224 m = rfc822.Message(io) 225 subj = m.getheader("subject") 226 header_from = m.getheader("from") 227 228 # Catch messages with empty headers which cause an exception. 229 if (not (header_from == None)): 230 if (re.compile("^" + self.remote_dir + "$").match(header_from)): 231 ret.append(subj) 232 log.Info("IMAP LIST: %s %s" % (subj,header_from)) 233 return ret
234
235 - def _imapf(self,fun,*args):
236 (ret,list)=fun(*args) 237 if ret != "OK": 238 raise Exception(list[0]) 239 return list
240
241 - def _delete_single_mail(self,i):
242 self._imapf(self._conn.store,i,"+FLAGS",'\\DELETED')
243
244 - def _expunge(self):
245 list=self._imapf(self._conn.expunge)
246
247 - def delete(self, filename_list):
248 assert len(filename_list) > 0 249 for filename in filename_list: 250 list = self._imapf(self._conn.search,None,"(SUBJECT %s)"%filename) 251 list = list[0].split() 252 if len(list)==0 or list[0]=="":raise Exception("no such mail with subject '%s'"%filename) 253 self._delete_single_mail(list[0]) 254 log.Notice("marked %s to be deleted" % filename) 255 self._expunge() 256 log.Notice("IMAP expunged %s files" % len(list))
257
258 - def close(self):
259 self._conn.select(globals.imap_mailbox) 260 self._conn.close() 261 self._conn.logout()
262 263 duplicity.backend.register_backend("imap", ImapBackend); 264 duplicity.backend.register_backend("imaps", ImapBackend); 265