""" This file is part of alot. Alot 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 3 of the License, or (at your option) any later version. Alot 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 notmuch. If not, see . Copyright (C) 2011 Patrick Totzke """ import mailbox import shlex import subprocess import logging import time import email from urlparse import urlparse class Account: """ Datastructure that represents an email account. It manages this account's settings, can send and store mails to maildirs (drafts/send) """ address = None """this accounts main email address""" aliases = [] """list of alternative addresses""" realname = None """real name used to format from-headers""" gpg_key = None """gpg fingerprint. CURRENTLY IGNORED""" signature = None """path to a signature file to append to outgoing mails.""" signature_filename = None """filename of signature file in attachment""" def __init__(self, address=None, aliases=None, realname=None, gpg_key=None, signature=None, signature_filename=None, sent_box=None, draft_box=None): self.address = address self.aliases = [] if aliases: self.aliases = aliases.split(';') self.realname = realname self.gpg_key = gpg_key self.signature = signature self.signature_filename = signature_filename self.sent_box = None if sent_box: mburl = urlparse(sent_box) if mburl.scheme == 'mbox': self.sent_box = mailbox.mbox(mburl.path) elif mburl.scheme == 'maildir': self.sent_box = mailbox.Maildir(mburl.path) elif mburl.scheme == 'mh': self.sent_box = mailbox.MH(mburl.path) elif mburl.scheme == 'babyl': self.sent_box = mailbox.Babyl(mburl.path) elif mburl.scheme == 'mmdf': self.sent_box = mailbox.MMDF(mburl.path) self.draft_box = None if draft_box: mburl = urlparse(draft_box) if mburl.scheme == 'mbox': self.draft_box = mailbox.mbox(mburl.path) elif mburl.scheme == 'maildir': self.draft_box = mailbox.Maildir(mburl.path) elif mburl.scheme == 'mh': self.draft_box = mailbox.MH(mburl.path) elif mburl.scheme == 'babyl': self.draft_box = mailbox.Babyl(mburl.path) elif mburl.scheme == 'mmdf': self.draft_box = mailbox.MMDF(mburl.path) def store_mail(self, mbx, mail): """stores given mail in mailbox. if mailbox is maildir, set the S-flag. :param mbx: mailbox to use :type mbx: `mailbox.Mailbox` :param mail: the mail to store :type mail: `email.message.Message` or string """ mbx.lock() if isinstance(mbx, mailbox.Maildir): msg = mailbox.MaildirMessage(mail) msg.set_flags('S') else: msg = mailbox.Message(mail) key = mbx.add(mail) mbx.flush() mbx.unlock() def store_sent_mail(self, mail): """stores mail in send-store if sent_box is set :param mail: the mail to store :type mail: `email.message.Message` or string """ if self.sent_box: self.store_mail(self.sent_box, mail) def store_draft_mail(self, mail): """stores mail as draft if draft_box is set :param mail: the mail to store :type mail: `email.message.Message` or string """ if self.draft_box: self.store_mail(self.sent_box, mail) def send_mail(self, mail): """ sends given mail :param mail: the mail to send :type mail: `email.message.Message` or string :returns: None if successful and a string with the reason for failure otherwise """ return 'not implemented' class SendmailAccount(Account): """Account that knows how to send out mails via sendmail""" def __init__(self, cmd, **kwargs): Account.__init__(self, **kwargs) self.cmd = cmd def send_mail(self, mail): mail['Date'] = email.utils.formatdate(time.time(), True) # no unicode in shlex on 2.x args = shlex.split(self.cmd.encode('ascii')) try: proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate(mail.as_string()) except OSError, e: return str(e) + '. sendmail_cmd set to: %s' % self.cmd if proc.poll(): # returncode is not 0 return err.strip() else: self.store_sent_mail(mail) return None class AccountManager: """Easy access to all known accounts""" allowed = ['realname', 'address', 'aliases', 'gpg_key', 'signature', 'signature_filename', 'type', 'sendmail_command', 'sent_box', 'draft_box'] manditory = ['realname', 'address'] accountmap = {} accounts = [] ordered_addresses = [] def __init__(self, config): sections = config.sections() accountsections = filter(lambda s: s.startswith('account '), sections) for s in accountsections: options = filter(lambda x: x in self.allowed, config.options(s)) args = {} to_set = self.manditory for o in options: args[o] = config.get(s, o) if o in to_set: to_set.remove(o) # removes obligation if not to_set: # all manditory fields were present sender_type = args.pop('type', 'sendmail') if sender_type == 'sendmail': cmd = args.pop('sendmail_command', 'sendmail') newacc = (SendmailAccount(cmd, **args)) self.accountmap[newacc.address] = newacc self.accounts.append(newacc) for alias in newacc.aliases: self.accountmap[alias] = newacc else: logging.info('account section %s lacks %s' % (s, to_set)) def get_accounts(self): """return known accounts :rtype: list of `account.Account` """ return self.accounts def get_account_by_address(self, address): """returns account for given email address :param address: address to look up :type address: string :rtype: `account.Account` or None """ if address in self.accountmap: return self.accountmap[address] else: return None # log info def get_main_addresses(self): """returns addresses of known accounts without its aliases""" return [a.address for a in self.accounts] def get_addresses(self): """returns addresses of known accounts including all their aliases""" return self.accountmap.keys()