VirtualMailManager/alias.py
changeset 571 a4aead244f75
parent 568 14abdd04ddf5
child 618 d8736bb80bdc
equal deleted inserted replaced
465:c0e1fb1b0145 571:a4aead244f75
       
     1 # -*- coding: UTF-8 -*-
       
     2 # Copyright (c) 2007 - 2012, Pascal Volk
       
     3 # See COPYING for distribution information.
       
     4 """
       
     5     VirtualMailManager.alias
       
     6     ~~~~~~~~~~~~~~~~~~~~~~~~
       
     7 
       
     8     Virtual Mail Manager's Alias class to manage e-mail aliases.
       
     9 """
       
    10 
       
    11 from VirtualMailManager.domain import get_gid
       
    12 from VirtualMailManager.emailaddress import \
       
    13      EmailAddress, DestinationEmailAddress as DestAddr
       
    14 from VirtualMailManager.errors import AliasError as AErr
       
    15 from VirtualMailManager.ext.postconf import Postconf
       
    16 from VirtualMailManager.pycompat import all
       
    17 from VirtualMailManager.constants import \
       
    18      ALIAS_EXCEEDS_EXPANSION_LIMIT, NO_SUCH_ALIAS, NO_SUCH_DOMAIN
       
    19 
       
    20 
       
    21 _ = lambda msg: msg
       
    22 cfg_dget = lambda option: None
       
    23 
       
    24 
       
    25 class Alias(object):
       
    26     """Class to manage e-mail aliases."""
       
    27     __slots__ = ('_addr', '_dests', '_gid', '_dbh')
       
    28 
       
    29     def __init__(self, dbh, address):
       
    30         assert isinstance(address, EmailAddress)
       
    31         self._addr = address
       
    32         self._dbh = dbh
       
    33         self._gid = get_gid(self._dbh, self._addr.domainname)
       
    34         if not self._gid:
       
    35             raise AErr(_(u"The domain '%s' does not exist.") %
       
    36                        self._addr.domainname, NO_SUCH_DOMAIN)
       
    37         self._dests = []
       
    38 
       
    39         self._load_dests()
       
    40 
       
    41     def _load_dests(self):
       
    42         """Loads all known destination addresses into the _dests list."""
       
    43         dbc = self._dbh.cursor()
       
    44         dbc.execute('SELECT destination FROM alias WHERE gid = %s AND '
       
    45                     'address = %s', (self._gid, self._addr.localpart))
       
    46         dests = dbc.fetchall()
       
    47         if dbc.rowcount > 0:
       
    48             self._dests.extend(DestAddr(dest[0], self._dbh) for dest in dests)
       
    49         dbc.close()
       
    50 
       
    51     def _check_expansion(self, count_new):
       
    52         """Checks the current expansion limit of the alias."""
       
    53         postconf = Postconf(cfg_dget('bin.postconf'))
       
    54         limit = long(postconf.read('virtual_alias_expansion_limit'))
       
    55         dcount = len(self._dests)
       
    56         failed = False
       
    57         if dcount == limit or dcount + count_new > limit:
       
    58             failed = True
       
    59             errmsg = _(
       
    60 u"""Cannot add %(count_new)i new destination(s) to alias '%(address)s'.
       
    61 Currently this alias expands into %(count)i/%(limit)i recipients.
       
    62 %(count_new)i additional destination(s) will render this alias unusable.
       
    63 Hint: Increase Postfix' virtual_alias_expansion_limit""")
       
    64         elif dcount > limit:
       
    65             failed = True
       
    66             errmsg = _(
       
    67 u"""Cannot add %(count_new)i new destination(s) to alias '%(address)s'.
       
    68 This alias already exceeds its expansion limit (%(count)i/%(limit)i).
       
    69 So its unusable, all messages addressed to this alias will be bounced.
       
    70 Hint: Delete some destination addresses.""")
       
    71         if failed:
       
    72             raise AErr(errmsg % {'address': self._addr, 'count': dcount,
       
    73                                  'limit': limit, 'count_new': count_new},
       
    74                        ALIAS_EXCEEDS_EXPANSION_LIMIT)
       
    75 
       
    76     def _delete(self, destination=None):
       
    77         """Deletes a destination from the alias, if ``destination`` is
       
    78         not ``None``.  If ``destination`` is None, the alias with all
       
    79         its destination addresses will be deleted.
       
    80 
       
    81         """
       
    82         dbc = self._dbh.cursor()
       
    83         if not destination:
       
    84             dbc.execute('DELETE FROM alias WHERE gid = %s AND address = %s',
       
    85                         (self._gid, self._addr.localpart))
       
    86         else:
       
    87             dbc.execute('DELETE FROM alias WHERE gid = %s AND address = %s '
       
    88                         'AND destination = %s',
       
    89                         (self._gid, self._addr.localpart, str(destination)))
       
    90         if dbc.rowcount > 0:
       
    91             self._dbh.commit()
       
    92         dbc.close()
       
    93 
       
    94     def __len__(self):
       
    95         """Returns the number of destinations of the alias."""
       
    96         return len(self._dests)
       
    97 
       
    98     @property
       
    99     def address(self):
       
   100         """The Alias' EmailAddress instance."""
       
   101         return self._addr
       
   102 
       
   103     def add_destinations(self, destinations, warnings=None):
       
   104         """Adds the `EmailAddress`es from *destinations* list to the
       
   105         destinations of the alias.
       
   106 
       
   107         Destinations, that are already assigned to the alias, will be
       
   108         removed from *destinations*.  When done, this method will return
       
   109         a set with all destinations, that were saved in the database.
       
   110         """
       
   111         destinations = set(destinations)
       
   112         assert destinations and \
       
   113                 all(isinstance(dest, EmailAddress) for dest in destinations)
       
   114         if not warnings is None:
       
   115             assert isinstance(warnings, list)
       
   116         if self._addr in destinations:
       
   117             destinations.remove(self._addr)
       
   118             if not warnings is None:
       
   119                 warnings.append(self._addr)
       
   120         duplicates = destinations.intersection(set(self._dests))
       
   121         if duplicates:
       
   122             destinations.difference_update(set(self._dests))
       
   123             if not warnings is None:
       
   124                 warnings.extend(duplicates)
       
   125         if not destinations:
       
   126             return destinations
       
   127         self._check_expansion(len(destinations))
       
   128         dbc = self._dbh.cursor()
       
   129         dbc.executemany("INSERT INTO alias (gid, address, destination) "
       
   130                         "VALUES (%d, '%s', %%s)" % (self._gid,
       
   131                                                     self._addr.localpart),
       
   132                         ((str(destination),) for destination in destinations))
       
   133         self._dbh.commit()
       
   134         dbc.close()
       
   135         self._dests.extend(destinations)
       
   136         return destinations
       
   137 
       
   138     def del_destination(self, destination):
       
   139         """Deletes the specified ``destination`` address from the alias."""
       
   140         assert isinstance(destination, EmailAddress)
       
   141         if not self._dests:
       
   142             raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
       
   143                        NO_SUCH_ALIAS)
       
   144         if not destination in self._dests:
       
   145             raise AErr(_(u"The address '%(addr)s' is not a destination of "
       
   146                          u"the alias '%(alias)s'.") % {'addr': destination,
       
   147                        'alias': self._addr}, NO_SUCH_ALIAS)
       
   148         self._delete(destination)
       
   149         self._dests.remove(destination)
       
   150 
       
   151     def get_destinations(self):
       
   152         """Returns an iterator for all destinations of the alias."""
       
   153         if not self._dests:
       
   154             raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
       
   155                        NO_SUCH_ALIAS)
       
   156         return iter(self._dests)
       
   157 
       
   158     def delete(self):
       
   159         """Deletes the alias with all its destinations."""
       
   160         if not self._dests:
       
   161             raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
       
   162                        NO_SUCH_ALIAS)
       
   163         self._delete()
       
   164         del self._dests[:]
       
   165 
       
   166 del _, cfg_dget