1 # -*- coding: UTF-8 -*- |
|
2 # Copyright (c) 2007 - 2010, Pascal Volk |
|
3 # See COPYING for distribution information. |
|
4 |
|
5 """Virtual Mail Manager's Account class to manage e-mail accounts.""" |
|
6 |
|
7 from __main__ import ERR |
|
8 from Exceptions import VMMAccountException as AccE |
|
9 from Domain import Domain |
|
10 from Transport import Transport |
|
11 from MailLocation import MailLocation |
|
12 from EmailAddress import EmailAddress |
|
13 import VirtualMailManager as VMM |
|
14 |
|
15 class Account(object): |
|
16 """Class to manage e-mail accounts.""" |
|
17 __slots__ = ('_addr','_base','_gid','_mid','_passwd','_tid','_uid','_dbh') |
|
18 def __init__(self, dbh, address, password=None): |
|
19 self._dbh = dbh |
|
20 self._base = None |
|
21 if isinstance(address, EmailAddress): |
|
22 self._addr = address |
|
23 else: |
|
24 raise TypeError("Argument 'address' is not an EmailAddress") |
|
25 self._uid = 0 |
|
26 self._gid = 0 |
|
27 self._mid = 0 |
|
28 self._tid = 0 |
|
29 self._passwd = password |
|
30 self._setAddr() |
|
31 self._exists() |
|
32 if self._uid < 1 and VMM.VirtualMailManager.aliasExists(self._dbh, |
|
33 self._addr): |
|
34 # TP: Hm, what quotation marks should be used? |
|
35 # If you are unsure have a look at: |
|
36 # http://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage |
|
37 raise AccE(_(u"There is already an alias with the address “%s”.") %\ |
|
38 self._addr, ERR.ALIAS_EXISTS) |
|
39 if self._uid < 1 and VMM.VirtualMailManager.relocatedExists(self._dbh, |
|
40 self._addr): |
|
41 raise AccE( |
|
42 _(u"There is already a relocated user with the address “%s”.") %\ |
|
43 self._addr, ERR.RELOCATED_EXISTS) |
|
44 |
|
45 def _exists(self): |
|
46 dbc = self._dbh.cursor() |
|
47 dbc.execute("SELECT uid, mid, tid FROM users \ |
|
48 WHERE gid=%s AND local_part=%s", |
|
49 self._gid, self._addr._localpart) |
|
50 result = dbc.fetchone() |
|
51 dbc.close() |
|
52 if result is not None: |
|
53 self._uid, self._mid, self._tid = result |
|
54 return True |
|
55 else: |
|
56 return False |
|
57 |
|
58 def _setAddr(self): |
|
59 dom = Domain(self._dbh, self._addr._domainname) |
|
60 self._gid = dom.getID() |
|
61 if self._gid == 0: |
|
62 raise AccE(_(u"The domain “%s” doesn't exist.") %\ |
|
63 self._addr._domainname, ERR.NO_SUCH_DOMAIN) |
|
64 self._base = dom.getDir() |
|
65 self._tid = dom.getTransportID() |
|
66 |
|
67 def _setID(self): |
|
68 dbc = self._dbh.cursor() |
|
69 dbc.execute("SELECT nextval('users_uid')") |
|
70 self._uid = dbc.fetchone()[0] |
|
71 dbc.close() |
|
72 |
|
73 def _prepare(self, maillocation): |
|
74 self._setID() |
|
75 self._mid = MailLocation(self._dbh, maillocation=maillocation).getID() |
|
76 |
|
77 def _switchState(self, state, dcvers, service): |
|
78 if not isinstance(state, bool): |
|
79 return False |
|
80 if not service in (None, 'all', 'imap', 'pop3', 'sieve', 'smtp'): |
|
81 raise AccE(_(u"Unknown service “%s”.") % service, |
|
82 ERR.UNKNOWN_SERVICE) |
|
83 if self._uid < 1: |
|
84 raise AccE(_(u"The account “%s” doesn't exist.") % self._addr, |
|
85 ERR.NO_SUCH_ACCOUNT) |
|
86 if dcvers > 11: |
|
87 sieve_col = 'sieve' |
|
88 else: |
|
89 sieve_col = 'managesieve' |
|
90 if service in ('smtp', 'pop3', 'imap'): |
|
91 sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (service, state, |
|
92 self._uid) |
|
93 elif service == 'sieve': |
|
94 sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (sieve_col, |
|
95 state, self._uid) |
|
96 else: |
|
97 sql = 'UPDATE users SET smtp = %(s)s, pop3 = %(s)s, imap = %(s)s,\ |
|
98 %(col)s = %(s)s WHERE uid = %(uid)d' % { |
|
99 's': state, 'col': sieve_col, 'uid': self._uid} |
|
100 dbc = self._dbh.cursor() |
|
101 dbc.execute(sql) |
|
102 if dbc.rowcount > 0: |
|
103 self._dbh.commit() |
|
104 dbc.close() |
|
105 |
|
106 def __aliaseCount(self): |
|
107 dbc = self._dbh.cursor() |
|
108 q = "SELECT COUNT(destination) FROM alias WHERE destination = '%s'"\ |
|
109 %self._addr |
|
110 dbc.execute(q) |
|
111 a_count = dbc.fetchone()[0] |
|
112 dbc.close() |
|
113 return a_count |
|
114 |
|
115 def setPassword(self, password): |
|
116 self._passwd = password |
|
117 |
|
118 def getUID(self): |
|
119 return self._uid |
|
120 |
|
121 def getGID(self): |
|
122 return self._gid |
|
123 |
|
124 def getDir(self, directory): |
|
125 if directory == 'domain': |
|
126 return '%s' % self._base |
|
127 elif directory == 'home': |
|
128 return '%s/%i' % (self._base, self._uid) |
|
129 |
|
130 def enable(self, dcvers, service=None): |
|
131 self._switchState(True, dcvers, service) |
|
132 |
|
133 def disable(self, dcvers, service=None): |
|
134 self._switchState(False, dcvers, service) |
|
135 |
|
136 def save(self, maillocation, dcvers, smtp, pop3, imap, sieve): |
|
137 if self._uid < 1: |
|
138 if dcvers > 11: |
|
139 sieve_col = 'sieve' |
|
140 else: |
|
141 sieve_col = 'managesieve' |
|
142 self._prepare(maillocation) |
|
143 sql = "INSERT INTO users (local_part, passwd, uid, gid, mid, tid,\ |
|
144 smtp, pop3, imap, %s) VALUES ('%s', '%s', %d, %d, %d, %d, %s, %s, %s, %s)" % ( |
|
145 sieve_col, self._addr._localpart, self._passwd, self._uid, |
|
146 self._gid, self._mid, self._tid, smtp, pop3, imap, sieve) |
|
147 dbc = self._dbh.cursor() |
|
148 dbc.execute(sql) |
|
149 self._dbh.commit() |
|
150 dbc.close() |
|
151 else: |
|
152 raise AccE(_(u'The account “%s” already exists.') % self._addr, |
|
153 ERR.ACCOUNT_EXISTS) |
|
154 |
|
155 def modify(self, what, value): |
|
156 if self._uid == 0: |
|
157 raise AccE(_(u"The account “%s” doesn't exist.") % self._addr, |
|
158 ERR.NO_SUCH_ACCOUNT) |
|
159 if what not in ['name', 'password', 'transport']: |
|
160 return False |
|
161 dbc = self._dbh.cursor() |
|
162 if what == 'password': |
|
163 dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s', |
|
164 value, self._uid) |
|
165 elif what == 'transport': |
|
166 self._tid = Transport(self._dbh, transport=value).getID() |
|
167 dbc.execute('UPDATE users SET tid = %s WHERE uid = %s', |
|
168 self._tid, self._uid) |
|
169 else: |
|
170 dbc.execute('UPDATE users SET name = %s WHERE uid = %s', |
|
171 value, self._uid) |
|
172 if dbc.rowcount > 0: |
|
173 self._dbh.commit() |
|
174 dbc.close() |
|
175 |
|
176 def getInfo(self, dcvers): |
|
177 if dcvers > 11: |
|
178 sieve_col = 'sieve' |
|
179 else: |
|
180 sieve_col = 'managesieve' |
|
181 sql = 'SELECT name, uid, gid, mid, tid, smtp, pop3, imap, %s\ |
|
182 FROM users WHERE uid = %d' % (sieve_col, self._uid) |
|
183 dbc = self._dbh.cursor() |
|
184 dbc.execute(sql) |
|
185 info = dbc.fetchone() |
|
186 dbc.close() |
|
187 if info is None: |
|
188 raise AccE(_(u"The account “%s” doesn't exist.") % self._addr, |
|
189 ERR.NO_SUCH_ACCOUNT) |
|
190 else: |
|
191 keys = ['name', 'uid', 'gid', 'maildir', 'transport', 'smtp', |
|
192 'pop3', 'imap', sieve_col] |
|
193 info = dict(zip(keys, info)) |
|
194 for service in ('smtp', 'pop3', 'imap', sieve_col): |
|
195 if bool(info[service]): |
|
196 # TP: A service (pop3/imap/…) is enabled/usable for a user |
|
197 info[service] = _('enabled') |
|
198 else: |
|
199 # TP: A service (pop3/imap) isn't enabled/usable for a user |
|
200 info[service] = _('disabled') |
|
201 info['address'] = self._addr |
|
202 info['maildir'] = '%s/%s/%s' % (self._base, info['uid'], |
|
203 MailLocation(self._dbh, |
|
204 mid=info['maildir']).getMailLocation()) |
|
205 info['transport'] = Transport(self._dbh, |
|
206 tid=info['transport']).getTransport() |
|
207 return info |
|
208 |
|
209 def getAliases(self): |
|
210 dbc = self._dbh.cursor() |
|
211 dbc.execute("SELECT address ||'@'|| domainname FROM alias, domain_name\ |
|
212 WHERE destination = %s AND domain_name.gid = alias.gid\ |
|
213 AND domain_name.is_primary ORDER BY address", str(self._addr)) |
|
214 addresses = dbc.fetchall() |
|
215 dbc.close() |
|
216 aliases = [] |
|
217 if len(addresses) > 0: |
|
218 aliases = [alias[0] for alias in addresses] |
|
219 return aliases |
|
220 |
|
221 def delete(self, delalias): |
|
222 if self._uid < 1: |
|
223 raise AccE(_(u"The account “%s” doesn't exist.") % self._addr, |
|
224 ERR.NO_SUCH_ACCOUNT) |
|
225 dbc = self._dbh.cursor() |
|
226 if delalias == 'delalias': |
|
227 dbc.execute('DELETE FROM users WHERE uid= %s', self._uid) |
|
228 u_rc = dbc.rowcount |
|
229 # delete also all aliases where the destination address is the same |
|
230 # as for this account. |
|
231 dbc.execute("DELETE FROM alias WHERE destination = %s", |
|
232 str(self._addr)) |
|
233 if u_rc > 0 or dbc.rowcount > 0: |
|
234 self._dbh.commit() |
|
235 else: # check first for aliases |
|
236 a_count = self.__aliaseCount() |
|
237 if a_count == 0: |
|
238 dbc.execute('DELETE FROM users WHERE uid = %s', self._uid) |
|
239 if dbc.rowcount > 0: |
|
240 self._dbh.commit() |
|
241 else: |
|
242 dbc.close() |
|
243 raise AccE( |
|
244 _(u"There are %(count)d aliases with the destination address\ |
|
245 “%(address)s”.") %{'count': a_count, 'address': self._addr}, ERR.ALIAS_PRESENT) |
|
246 dbc.close() |
|
247 |
|
248 def getAccountByID(uid, dbh): |
|
249 try: |
|
250 uid = long(uid) |
|
251 except ValueError: |
|
252 raise AccE(_(u'uid must be an int/long.'), ERR.INVALID_AGUMENT) |
|
253 if uid < 1: |
|
254 raise AccE(_(u'uid must be greater than 0.'), ERR.INVALID_AGUMENT) |
|
255 dbc = dbh.cursor() |
|
256 dbc.execute("SELECT local_part||'@'|| domain_name.domainname AS address,\ |
|
257 uid, users.gid FROM users LEFT JOIN domain_name ON (domain_name.gid \ |
|
258 = users.gid AND is_primary) WHERE uid = %s;", uid) |
|
259 info = dbc.fetchone() |
|
260 dbc.close() |
|
261 if info is None: |
|
262 raise AccE(_(u"There is no account with the UID “%d”.") % uid, |
|
263 ERR.NO_SUCH_ACCOUNT) |
|
264 keys = ['address', 'uid', 'gid'] |
|
265 info = dict(zip(keys, info)) |
|
266 return info |
|
267 |
|