VirtualMailManager/alias.py
changeset 760 b678a1c43027
parent 748 659c4476c57c
child 761 e4e656f19771
equal deleted inserted replaced
748:659c4476c57c 760:b678a1c43027
     1 # -*- coding: UTF-8 -*-
       
     2 # Copyright (c) 2007 - 2014, 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 ORDER BY destination',
       
    46                     (self._gid, self._addr.localpart))
       
    47         dests = dbc.fetchall()
       
    48         if dbc.rowcount > 0:
       
    49             self._dests.extend(DestAddr(dest[0], self._dbh) for dest in dests)
       
    50         dbc.close()
       
    51 
       
    52     def _check_expansion(self, count_new):
       
    53         """Checks the current expansion limit of the alias."""
       
    54         postconf = Postconf(cfg_dget('bin.postconf'))
       
    55         limit = long(postconf.read('virtual_alias_expansion_limit'))
       
    56         dcount = len(self._dests)
       
    57         failed = False
       
    58         if dcount == limit or dcount + count_new > limit:
       
    59             failed = True
       
    60             errmsg = _(
       
    61 u"""Cannot add %(count_new)i new destination(s) to alias '%(address)s'.
       
    62 Currently this alias expands into %(count)i/%(limit)i recipients.
       
    63 %(count_new)i additional destination(s) will render this alias unusable.
       
    64 Hint: Increase Postfix' virtual_alias_expansion_limit""")
       
    65         elif dcount > limit:
       
    66             failed = True
       
    67             errmsg = _(
       
    68 u"""Cannot add %(count_new)i new destination(s) to alias '%(address)s'.
       
    69 This alias already exceeds its expansion limit (%(count)i/%(limit)i).
       
    70 So its unusable, all messages addressed to this alias will be bounced.
       
    71 Hint: Delete some destination addresses.""")
       
    72         if failed:
       
    73             raise AErr(errmsg % {'address': self._addr, 'count': dcount,
       
    74                                  'limit': limit, 'count_new': count_new},
       
    75                        ALIAS_EXCEEDS_EXPANSION_LIMIT)
       
    76 
       
    77     def _delete(self, destinations=None):
       
    78         """Deletes the *destinations* from the alias, if ``destinations``
       
    79         is not ``None``.  If ``destinations`` is None, the alias with all
       
    80         its destination addresses will be deleted.
       
    81 
       
    82         """
       
    83         dbc = self._dbh.cursor()
       
    84         if not destinations:
       
    85             dbc.execute('DELETE FROM alias WHERE gid = %s AND address = %s',
       
    86                         (self._gid, self._addr.localpart))
       
    87         else:
       
    88             dbc.executemany("DELETE FROM alias WHERE gid = %d AND address = "
       
    89                             "'%s' AND destination = %%s" % (self._gid,
       
    90                                                          self._addr.localpart),
       
    91                             ((str(dest),) for dest in destinations))
       
    92         if dbc.rowcount > 0:
       
    93             self._dbh.commit()
       
    94         dbc.close()
       
    95 
       
    96     def __len__(self):
       
    97         """Returns the number of destinations of the alias."""
       
    98         return len(self._dests)
       
    99 
       
   100     @property
       
   101     def address(self):
       
   102         """The Alias' EmailAddress instance."""
       
   103         return self._addr
       
   104 
       
   105     def add_destinations(self, destinations, warnings=None):
       
   106         """Adds the `EmailAddress`es from *destinations* list to the
       
   107         destinations of the alias.
       
   108 
       
   109         Destinations, that are already assigned to the alias, will be
       
   110         removed from *destinations*.  When done, this method will return
       
   111         a set with all destinations, that were saved in the database.
       
   112         """
       
   113         destinations = set(destinations)
       
   114         assert destinations and \
       
   115                 all(isinstance(dest, EmailAddress) for dest in destinations)
       
   116         if not warnings is None:
       
   117             assert isinstance(warnings, list)
       
   118         if self._addr in destinations:
       
   119             destinations.remove(self._addr)
       
   120             if not warnings is None:
       
   121                 warnings.append(self._addr)
       
   122         duplicates = destinations.intersection(set(self._dests))
       
   123         if duplicates:
       
   124             destinations.difference_update(set(self._dests))
       
   125             if not warnings is None:
       
   126                 warnings.extend(duplicates)
       
   127         if not destinations:
       
   128             return destinations
       
   129         self._check_expansion(len(destinations))
       
   130         dbc = self._dbh.cursor()
       
   131         dbc.executemany("INSERT INTO alias (gid, address, destination) "
       
   132                         "VALUES (%d, '%s', %%s)" % (self._gid,
       
   133                                                     self._addr.localpart),
       
   134                         ((str(destination),) for destination in destinations))
       
   135         self._dbh.commit()
       
   136         dbc.close()
       
   137         self._dests.extend(destinations)
       
   138         return destinations
       
   139 
       
   140     def del_destinations(self, destinations, warnings=None):
       
   141         """Delete the specified `EmailAddress`es of *destinations* from
       
   142         the alias's destinations.
       
   143 
       
   144         """
       
   145         destinations = set(destinations)
       
   146         assert destinations and \
       
   147                 all(isinstance(dest, EmailAddress) for dest in destinations)
       
   148         if not warnings is None:
       
   149             assert isinstance(warnings, list)
       
   150         if self._addr in destinations:
       
   151             destinations.remove(self._addr)
       
   152             if not warnings is None:
       
   153                 warnings.append(self._addr)
       
   154         if not self._dests:
       
   155             raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
       
   156                        NO_SUCH_ALIAS)
       
   157         unknown = destinations.difference(set(self._dests))
       
   158         if unknown:
       
   159             destinations.intersection_update(set(self._dests))
       
   160             if not warnings is None:
       
   161                 warnings.extend(unknown)
       
   162         if not destinations:
       
   163             raise AErr(_(u"No suitable destinations left to remove from alias "
       
   164                          u"'%s'.") % self._addr, NO_SUCH_ALIAS)
       
   165         self._delete(destinations)
       
   166         for destination in destinations:
       
   167             self._dests.remove(destination)
       
   168 
       
   169     def get_destinations(self):
       
   170         """Returns an iterator for all destinations of the alias."""
       
   171         if not self._dests:
       
   172             raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
       
   173                        NO_SUCH_ALIAS)
       
   174         return iter(self._dests)
       
   175 
       
   176     def delete(self):
       
   177         """Deletes the alias with all its destinations."""
       
   178         if not self._dests:
       
   179             raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
       
   180                        NO_SUCH_ALIAS)
       
   181         self._delete()
       
   182         del self._dests[:]
       
   183 
       
   184 del _, cfg_dget