1 # -*- coding: UTF-8 -*- |
1 # -*- coding: UTF-8 -*- |
2 # Copyright (c) 2007 - 2010, Pascal Volk |
2 # Copyright (c) 2007 - 2010, Pascal Volk |
3 # See COPYING for distribution information. |
3 # See COPYING for distribution information. |
4 |
4 |
5 """Virtual Mail Manager's Alias class to manage e-mail aliases.""" |
5 """ |
|
6 VirtualMailManager.Alias |
6 |
7 |
7 import VirtualMailManager.constants.ERROR as ERR |
8 Virtual Mail Manager's Alias class to manage e-mail aliases. |
|
9 """ |
|
10 |
8 from VirtualMailManager.Domain import Domain |
11 from VirtualMailManager.Domain import Domain |
9 from VirtualMailManager.EmailAddress import EmailAddress |
12 from VirtualMailManager.EmailAddress import EmailAddress |
10 from VirtualMailManager.Exceptions import VMMAliasException as VMMAE |
13 from VirtualMailManager.Exceptions import VMMAliasException as VMMAE |
11 import VirtualMailManager as VMM |
14 from VirtualMailManager.constants.ERROR import ALIAS_ADDR_DEST_IDENTICAL, \ |
|
15 ALIAS_EXCEEDS_EXPANSION_LIMIT, ALIAS_EXISTS, NO_SUCH_ALIAS, NO_SUCH_DOMAIN |
|
16 |
|
17 |
|
18 _ = lambda msg: msg |
|
19 |
12 |
20 |
13 class Alias(object): |
21 class Alias(object): |
14 """Class to manage e-mail aliases.""" |
22 """Class to manage e-mail aliases.""" |
15 __slots__ = ('_addr', '_dest', '_gid', '_isNew', '_dbh') |
23 __slots__ = ('_addr', '_dests', '_gid', '_dbh') |
16 def __init__(self, dbh, address, destination=None): |
24 |
|
25 def __init__(self, dbh, address): |
17 if isinstance(address, EmailAddress): |
26 if isinstance(address, EmailAddress): |
18 self._addr = address |
27 self._addr = address |
19 else: |
28 else: |
20 raise TypeError("Argument 'address' is not an EmailAddress") |
29 raise TypeError("Argument 'address' is not an EmailAddress") |
21 if destination is None: |
|
22 self._dest = None |
|
23 elif isinstance(destination, EmailAddress): |
|
24 self._dest = destination |
|
25 else: |
|
26 raise TypeError("Argument 'destination' is not an EmailAddress") |
|
27 if address == destination: |
|
28 raise VMMAE(_(u"Address and destination are identical."), |
|
29 ERR.ALIAS_ADDR_DEST_IDENTICAL) |
|
30 self._dbh = dbh |
30 self._dbh = dbh |
31 self._gid = 0 |
31 self._gid = 0 |
32 self._isNew = False |
32 self._dests = [] |
33 self._setAddr() |
|
34 if not self._dest is None: |
|
35 self._exists() |
|
36 if VMM.VirtualMailManager.accountExists(self._dbh, self._addr): |
|
37 raise VMMAE(_(u"There is already an account with address “%s”.") %\ |
|
38 self._addr, ERR.ACCOUNT_EXISTS) |
|
39 if VMM.VirtualMailManager.relocatedExists(self._dbh, self._addr): |
|
40 raise VMMAE( |
|
41 _(u"There is already a relocated user with the address “%s”.") %\ |
|
42 self._addr, ERR.RELOCATED_EXISTS) |
|
43 |
33 |
44 def _exists(self): |
34 self.__set_gid() |
45 dbc = self._dbh.cursor() |
35 self.__load_dests() |
46 dbc.execute("SELECT gid FROM alias WHERE gid=%s AND address=%s\ |
|
47 AND destination=%s", self._gid, self._addr._localpart, str(self._dest)) |
|
48 gid = dbc.fetchone() |
|
49 dbc.close() |
|
50 if gid is None: |
|
51 self._isNew = True |
|
52 |
36 |
53 def _setAddr(self): |
37 def __set_gid(self): |
54 dom = Domain(self._dbh, self._addr._domainname) |
38 """Sets the alias' _gid based on its _addr.domainname.""" |
|
39 dom = Domain(self._dbh, self._addr.domainname) |
55 self._gid = dom.getID() |
40 self._gid = dom.getID() |
56 if self._gid == 0: |
41 if self._gid == 0: |
57 raise VMMAE(_(u"The domain “%s” doesn't exist.") %\ |
42 raise VMMAE(_(u"The domain “%s” doesn't exist.") % |
58 self._addr._domainname, ERR.NO_SUCH_DOMAIN) |
43 self._addr.domainname, NO_SUCH_DOMAIN) |
59 |
44 |
60 def _checkExpansion(self, limit): |
45 def __load_dests(self): |
|
46 """Loads all known destination addresses into the _dests list.""" |
61 dbc = self._dbh.cursor() |
47 dbc = self._dbh.cursor() |
62 dbc.execute('SELECT count(gid) FROM alias where gid=%s AND address=%s', |
48 dbc.execute( |
63 self._gid, self._addr._localpart) |
49 'SELECT destination FROM alias WHERE gid=%s AND address=%s', |
64 curEx = dbc.fetchone()[0] |
50 self._gid, self._addr.localpart) |
|
51 dests = iter(dbc.fetchall()) |
|
52 if dbc.rowcount > 0: |
|
53 dest_add = self._dests.append |
|
54 for dest in dests: |
|
55 dest_add(EmailAddress(dest[0])) |
65 dbc.close() |
56 dbc.close() |
66 if curEx == limit: |
57 |
67 errmsg = _(u"""Can't add new destination to alias “%(address)s”. |
58 def __check_expansion(self, limit): |
68 Currently this alias expands into %(count)i recipients. |
59 """Checks the current expansion limit of the alias.""" |
|
60 dcount = len(self._dests) |
|
61 failed = False |
|
62 if dcount == limit: |
|
63 failed = True |
|
64 errmsg = _( |
|
65 u"""Can't add new destination to alias “%(address)s”. |
|
66 Currently this alias expands into %(count)i/%(limit)i recipients. |
69 One more destination will render this alias unusable. |
67 One more destination will render this alias unusable. |
70 Hint: Increase Postfix' virtual_alias_expansion_limit |
68 Hint: Increase Postfix' virtual_alias_expansion_limit""") |
71 """) % {'address': self._addr, 'count': curEx} |
69 elif dcount > limit: |
72 raise VMMAE(errmsg, ERR.ALIAS_EXCEEDS_EXPANSION_LIMIT) |
70 failed = True |
|
71 errmsg = _( |
|
72 u"""Can't add new destination to alias “%(address)s”. |
|
73 This alias already exceeds it's expansion limit (%(count)i/%(limit)i). |
|
74 So its unusable, all messages addressed to this alias will be bounced. |
|
75 Hint: Delete some destination addresses.""") |
|
76 if failed: |
|
77 raise VMMAE(errmsg % {'address': self._addr, 'count': dcount, |
|
78 'limit': limit}, |
|
79 ALIAS_EXCEEDS_EXPANSION_LIMIT) |
73 |
80 |
74 def save(self, expansion_limit): |
81 def __delete(self, destination=None): |
75 if self._dest is None: |
82 """Deletes a destination from the alias, if ``destination`` is not |
76 raise VMMAE(_(u"No destination address specified for alias."), |
83 ``None``. If ``destination`` is None, the alias with all it's |
77 ERR.ALIAS_MISSING_DEST) |
84 destination addresses will be deleted.""" |
78 if self._isNew: |
85 dbc = self._dbh.cursor() |
79 self._checkExpansion(expansion_limit) |
86 if destination is None: |
|
87 dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s", |
|
88 self._gid, self._addr.localpart) |
|
89 else: |
|
90 dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s AND \ |
|
91 destination=%s", |
|
92 self._gid, self._addr.localpart, str(destination)) |
|
93 if dbc.rowcount > 0: |
|
94 self._dbh.commit() |
|
95 dbc.close() |
|
96 |
|
97 def __len__(self): |
|
98 """Returns the number of destinations of the alias.""" |
|
99 return len(self._dests) |
|
100 |
|
101 def addDestination(self, destination, expansion_limit): |
|
102 """Adds the ``destination`` `EmailAddress` to the alias.""" |
|
103 if not isinstance(destination, EmailAddress): |
|
104 raise TypeError("Argument 'destination' is not an EmailAddress") |
|
105 if self._addr == destination: |
|
106 raise VMMAE(_(u"Address and destination are identical."), |
|
107 ALIAS_ADDR_DEST_IDENTICAL) |
|
108 if not destination in self._dests: |
|
109 self.__check_expansion(expansion_limit) |
80 dbc = self._dbh.cursor() |
110 dbc = self._dbh.cursor() |
81 dbc.execute("INSERT INTO alias (gid, address, destination) VALUES\ |
111 dbc.execute('INSERT INTO alias (gid, address, destination) \ |
82 (%s, %s, %s)", self._gid, self._addr._localpart, str(self._dest)) |
112 VALUES (%s, %s, %s)', |
|
113 self._gid, self._addr.localpart, str(destination)) |
83 self._dbh.commit() |
114 self._dbh.commit() |
84 dbc.close() |
115 dbc.close() |
|
116 self._dests.append(destination) |
85 else: |
117 else: |
86 raise VMMAE( |
118 raise VMMAE(_( |
87 _(u"The alias “%(a)s” with destination “%(d)s” already exists.")\ |
119 u'The alias “%(a)s” has already the destination “%(d)s”.') % |
88 % {'a': self._addr, 'd': self._dest}, ERR.ALIAS_EXISTS) |
120 {'a': self._addr, 'd': destination}, ALIAS_EXISTS) |
89 |
121 |
90 def getInfo(self): |
122 def delDestination(self, destination): |
91 dbc = self._dbh.cursor() |
123 """Deletes the specified ``destination`` address from the alias.""" |
92 dbc.execute('SELECT destination FROM alias WHERE gid=%s AND address=%s', |
124 if not isinstance(destination, EmailAddress): |
93 self._gid, self._addr._localpart) |
125 raise TypeError("Argument 'destination' is not an EmailAddress") |
94 destinations = dbc.fetchall() |
126 if not self._dests: |
95 dbc.close() |
127 raise VMMAE(_(u"The alias “%s” doesn't exist.") % self._addr, |
96 if len(destinations) > 0: |
128 NO_SUCH_ALIAS) |
97 targets = [destination[0] for destination in destinations] |
129 if not destination in self._dests: |
98 return targets |
130 raise VMMAE(_(u"The address “%(d)s” isn't a destination of \ |
|
131 the alias “%(a)s”.") % |
|
132 {'a': self._addr, 'd': destination}, NO_SUCH_ALIAS) |
|
133 self.__delete(destination) |
|
134 self._dests.remove(destination) |
|
135 |
|
136 def getDestinations(self): |
|
137 """Returns an iterator for all destinations of the alias.""" |
|
138 if self._dests: |
|
139 return iter(self._dests) |
99 else: |
140 else: |
100 raise VMMAE(_(u"The alias “%s” doesn't exist.") % self._addr, |
141 raise VMMAE(_(u"The alias “%s” doesn't exist.") % self._addr, |
101 ERR.NO_SUCH_ALIAS) |
142 NO_SUCH_ALIAS) |
102 |
143 |
103 def delete(self): |
144 def delete(self): |
104 dbc = self._dbh.cursor() |
145 """Deletes the alias with all it's destinations.""" |
105 if self._dest is None: |
146 if self._dests: |
106 dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s", |
147 self.__delete() |
107 self._gid, self._addr._localpart) |
148 del self._dests[:] |
108 else: |
149 else: |
109 dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s AND \ |
150 raise VMMAE(_(u"The alias “%s” doesn't exist.") % self._addr, |
110 destination=%s", self._gid, self._addr._localpart, str(self._dest)) |
151 NO_SUCH_ALIAS) |
111 rowcount = dbc.rowcount |
|
112 dbc.close() |
|
113 if rowcount > 0: |
|
114 self._dbh.commit() |
|
115 else: |
|
116 if self._dest is None: |
|
117 msg = _(u"The alias “%s” doesn't exist.") % self._addr |
|
118 else: |
|
119 msg = _(u"The alias “%(a)s” with destination “%(d)s” doesn't\ |
|
120 exist.") % {'a': self._addr, 'd': self._dest} |
|
121 raise VMMAE(msg, ERR.NO_SUCH_ALIAS) |
|
122 |
152 |
|
153 |
|
154 del _ |