VirtualMailManager/Account.py
author Pascal Volk <neverseen@users.sourceforge.net>
Wed, 03 Mar 2010 20:23:18 +0000
branchv0.6.x
changeset 229 0fb2f12648a7
parent 228 a7b000ca4ac9
child 235 9d3405ed08e5
permissions -rw-r--r--
vmm.cfg: renamed maildir.folders to mailbox.folders. maildir.name was removed. new: mailbox.format, in order to support all mailbox formats from Dovecot. Maildir is the default format. Adjusted VirtualMailManager/Config and update_config.py to the changes mentioned above.

# -*- coding: UTF-8 -*-
# Copyright (c) 2007 - 2010, Pascal Volk
# See COPYING for distribution information.

"""Virtual Mail Manager's Account class to manage e-mail accounts."""

import VirtualMailManager.constants.ERROR as ERR
from VirtualMailManager.Domain import Domain
from VirtualMailManager.EmailAddress import EmailAddress
from VirtualMailManager.errors import AccountError as AccE
from VirtualMailManager.maillocation import MailLocation, known_format
from VirtualMailManager.Transport import Transport


_ = lambda msg: msg


class Account(object):
    """Class to manage e-mail accounts."""
    __slots__ = ('_addr', '_base', '_gid', '_mid', '_passwd', '_tid', '_uid',
                 '_dbh')

    def __init__(self, dbh, address, password=None):
        self._dbh = dbh
        self._base = None
        if isinstance(address, EmailAddress):
            self._addr = address
        else:
            raise TypeError("Argument 'address' is not an EmailAddress")
        self._uid = 0
        self._gid = 0
        self._mid = 0
        self._tid = 0
        self._passwd = password
        self._setAddr()
        self._exists()
        from VirtualMailManager.Handler import Handler
        if self._uid < 1 and Handler.aliasExists(self._dbh, self._addr):
            # TP: Hm, what quotation marks should be used?
            # If you are unsure have a look at:
            # http://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
            raise AccE(_(u"There is already an alias with the address “%s”.") %
                       self._addr, ERR.ALIAS_EXISTS)
        if self._uid < 1 and Handler.relocatedExists(self._dbh, self._addr):
            raise AccE(
              _(u"There is already a relocated user with the address “%s”.") %
                       self._addr, ERR.RELOCATED_EXISTS)

    def _exists(self):
        dbc = self._dbh.cursor()
        dbc.execute(
            "SELECT uid, mid, tid FROM users WHERE gid=%s AND local_part=%s",
                    self._gid, self._addr.localpart)
        result = dbc.fetchone()
        dbc.close()
        if result is not None:
            self._uid, self._mid, self._tid = result
            return True
        else:
            return False

    def _setAddr(self):
        dom = Domain(self._dbh, self._addr.domainname)
        self._gid = dom.getID()
        if self._gid == 0:
            raise AccE(_(u"The domain “%s” doesn't exist.") %
                       self._addr.domainname, ERR.NO_SUCH_DOMAIN)
        self._base = dom.getDir()
        self._tid = dom.getTransportID()

    def _setID(self):
        dbc = self._dbh.cursor()
        dbc.execute("SELECT nextval('users_uid')")
        self._uid = dbc.fetchone()[0]
        dbc.close()

    def _prepare(self, maillocation):
        if not known_format(maillocation):                                  
            raise AccE(_(u'Unknown mail_location mailbox format: %r') %
                       maillocation, ERR.UNKNOWN_MAILLOCATION_NAME)
        self._setID()
        self._mid = MailLocation(format=maillocation).mid

    def _switchState(self, state, dcvers, service):
        if not isinstance(state, bool):
            return False
        if not service in (None, 'all', 'imap', 'pop3', 'sieve', 'smtp'):
            raise AccE(_(u"Unknown service “%s”.") % service,
                    ERR.UNKNOWN_SERVICE)
        if self._uid < 1:
            raise AccE(_(u"The account “%s” doesn't exist.") % self._addr,
                    ERR.NO_SUCH_ACCOUNT)
        if dcvers > 11:
            sieve_col = 'sieve'
        else:
            sieve_col = 'managesieve'
        if service in ('smtp', 'pop3', 'imap'):
            sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (service, state,
                    self._uid)
        elif service == 'sieve':
            sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (sieve_col,
                    state, self._uid)
        else:
            sql = 'UPDATE users SET smtp = %(s)s, pop3 = %(s)s, imap = %(s)s,\
 %(col)s = %(s)s WHERE uid = %(uid)d' % {
                's': state, 'col': sieve_col, 'uid': self._uid}
        dbc = self._dbh.cursor()
        dbc.execute(sql)
        if dbc.rowcount > 0:
            self._dbh.commit()
        dbc.close()

    def __aliaseCount(self):
        dbc = self._dbh.cursor()
        q = "SELECT COUNT(destination) FROM alias WHERE destination = '%s'"\
            % self._addr
        dbc.execute(q)
        a_count = dbc.fetchone()[0]
        dbc.close()
        return a_count

    def setPassword(self, password):
        self._passwd = password

    def getUID(self):
        return self._uid

    def getGID(self):
        return self._gid

    def getDir(self, directory):
        if directory == 'domain':
            return '%s' % self._base
        elif directory == 'home':
            return '%s/%i' % (self._base, self._uid)

    def enable(self, dcvers, service=None):
        self._switchState(True, dcvers, service)

    def disable(self, dcvers, service=None):
        self._switchState(False, dcvers, service)

    def save(self, maillocation, dcvers, smtp, pop3, imap, sieve):
        if self._uid < 1:
            if dcvers > 11:
                sieve_col = 'sieve'
            else:
                sieve_col = 'managesieve'
            self._prepare(maillocation)
            sql = "INSERT INTO users (local_part, passwd, uid, gid, mid, tid,\
 smtp, pop3, imap, %s) VALUES ('%s', '%s', %d, %d, %d, %d, %s, %s, %s, %s)" % (
                sieve_col, self._addr.localpart, self._passwd, self._uid,
                self._gid, self._mid, self._tid, smtp, pop3, imap, sieve)
            dbc = self._dbh.cursor()
            dbc.execute(sql)
            self._dbh.commit()
            dbc.close()
        else:
            raise AccE(_(u'The account “%s” already exists.') % self._addr,
                    ERR.ACCOUNT_EXISTS)

    def modify(self, what, value):
        if self._uid == 0:
            raise AccE(_(u"The account “%s” doesn't exist.") % self._addr,
                    ERR.NO_SUCH_ACCOUNT)
        if what not in ['name', 'password', 'transport']:
            return False
        dbc = self._dbh.cursor()
        if what == 'password':
            dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s',
                    value, self._uid)
        elif what == 'transport':
            self._tid = Transport(self._dbh, transport=value).id
            dbc.execute('UPDATE users SET tid = %s WHERE uid = %s',
                    self._tid, self._uid)
        else:
            dbc.execute('UPDATE users SET name = %s WHERE uid = %s',
                    value, self._uid)
        if dbc.rowcount > 0:
            self._dbh.commit()
        dbc.close()

    def getInfo(self, dcvers):
        if dcvers > 11:
            sieve_col = 'sieve'
        else:
            sieve_col = 'managesieve'
        sql = 'SELECT name, uid, gid, mid, tid, smtp, pop3, imap, %s\
 FROM users WHERE uid = %d' % (sieve_col, self._uid)
        dbc = self._dbh.cursor()
        dbc.execute(sql)
        info = dbc.fetchone()
        dbc.close()
        if info is None:
            raise AccE(_(u"The account “%s” doesn't exist.") % self._addr,
                    ERR.NO_SUCH_ACCOUNT)
        else:
            keys = ['name', 'uid', 'gid', 'mid', 'transport', 'smtp',
                    'pop3', 'imap', sieve_col]
            info = dict(zip(keys, info))
            for service in ('smtp', 'pop3', 'imap', sieve_col):
                if bool(info[service]):
                    # TP: A service (pop3/imap/…) is enabled/usable for a user
                    info[service] = _('enabled')
                else:
                    # TP: A service (pop3/imap) isn't enabled/usable for a user
                    info[service] = _('disabled')
            info['address'] = self._addr
            info['home'] = '%s/%s' % (self._base, info['uid'])
            info['mail_location'] = MailLocation(mid=info['mid']).mail_location
            info['transport'] = Transport(self._dbh,
                                          tid=info['transport']).transport
            return info

    def getAliases(self):
        dbc = self._dbh.cursor()
        dbc.execute("SELECT address ||'@'|| domainname FROM alias, domain_name\
 WHERE destination = %s AND domain_name.gid = alias.gid\
 AND domain_name.is_primary ORDER BY address", str(self._addr))
        addresses = dbc.fetchall()
        dbc.close()
        aliases = []
        if len(addresses) > 0:
            aliases = [alias[0] for alias in addresses]
        return aliases

    def delete(self, delalias):
        if self._uid < 1:
            raise AccE(_(u"The account “%s” doesn't exist.") % self._addr,
                    ERR.NO_SUCH_ACCOUNT)
        dbc = self._dbh.cursor()
        if delalias == 'delalias':
            dbc.execute('DELETE FROM users WHERE uid= %s', self._uid)
            u_rc = dbc.rowcount
            # delete also all aliases where the destination address is the same
            # as for this account.
            dbc.execute("DELETE FROM alias WHERE destination = %s",
                    str(self._addr))
            if u_rc > 0 or dbc.rowcount > 0:
                self._dbh.commit()
        else: # check first for aliases
            a_count = self.__aliaseCount()
            if a_count == 0:
                dbc.execute('DELETE FROM users WHERE uid = %s', self._uid)
                if dbc.rowcount > 0:
                    self._dbh.commit()
            else:
                dbc.close()
                raise AccE(
                  _(u"There are %(count)d aliases with the destination address\
%(address)s”.") % {'count': a_count, 'address': self._addr},
                  ERR.ALIAS_PRESENT)
        dbc.close()


def getAccountByID(uid, dbh):
    try:
        uid = long(uid)
    except ValueError:
        raise AccE(_(u'uid must be an int/long.'), ERR.INVALID_AGUMENT)
    if uid < 1:
        raise AccE(_(u'uid must be greater than 0.'), ERR.INVALID_AGUMENT)
    dbc = dbh.cursor()
    dbc.execute("SELECT local_part||'@'|| domain_name.domainname AS address,\
 uid, users.gid FROM users LEFT JOIN domain_name ON (domain_name.gid \
 = users.gid AND is_primary) WHERE uid = %s;", uid)
    info = dbc.fetchone()
    dbc.close()
    if info is None:
        raise AccE(_(u"There is no account with the UID “%d”.") % uid,
                ERR.NO_SUCH_ACCOUNT)
    keys = ['address', 'uid', 'gid']
    info = dict(zip(keys, info))
    return info


del _