18 from VirtualMailManager.errors import VMMError, AccountError as AErr |
18 from VirtualMailManager.errors import VMMError, AccountError as AErr |
19 from VirtualMailManager.maillocation import MailLocation |
19 from VirtualMailManager.maillocation import MailLocation |
20 from VirtualMailManager.password import pwhash |
20 from VirtualMailManager.password import pwhash |
21 from VirtualMailManager.quotalimit import QuotaLimit |
21 from VirtualMailManager.quotalimit import QuotaLimit |
22 from VirtualMailManager.transport import Transport |
22 from VirtualMailManager.transport import Transport |
23 |
23 from VirtualMailManager.serviceset import ServiceSet |
24 __all__ = ('SERVICES', 'Account', 'get_account_by_uid') |
24 |
25 |
25 __all__ = ('Account', 'get_account_by_uid') |
26 SERVICES = ('imap', 'pop3', 'smtp', 'sieve') |
|
27 |
26 |
28 _ = lambda msg: msg |
27 _ = lambda msg: msg |
29 cfg_dget = lambda option: None |
28 cfg_dget = lambda option: None |
30 |
29 |
31 |
30 |
32 class Account(object): |
31 class Account(object): |
33 """Class to manage e-mail accounts.""" |
32 """Class to manage e-mail accounts.""" |
34 __slots__ = ('_addr', '_dbh', '_domain', '_mail', '_new', '_passwd', |
33 __slots__ = ('_addr', '_dbh', '_domain', '_mail', '_new', '_passwd', |
35 '_qlimit', '_transport', '_uid') |
34 '_qlimit', '_services', '_transport', '_uid') |
36 |
35 |
37 def __init__(self, dbh, address): |
36 def __init__(self, dbh, address): |
38 """Creates a new Account instance. |
37 """Creates a new Account instance. |
39 |
38 |
40 When an account with the given *address* could be found in the |
39 When an account with the given *address* could be found in the |
59 raise AErr(_(u"The domain '%s' does not exist.") % |
58 raise AErr(_(u"The domain '%s' does not exist.") % |
60 self._addr.domainname, NO_SUCH_DOMAIN) |
59 self._addr.domainname, NO_SUCH_DOMAIN) |
61 self._uid = 0 |
60 self._uid = 0 |
62 self._mail = None |
61 self._mail = None |
63 self._qlimit = self._domain.quotalimit |
62 self._qlimit = self._domain.quotalimit |
|
63 self._services = self._domain.serviceset |
64 self._transport = self._domain.transport |
64 self._transport = self._domain.transport |
65 self._passwd = None |
65 self._passwd = None |
66 self._new = True |
66 self._new = True |
67 self._load() |
67 self._load() |
68 |
68 |
69 def __nonzero__(self): |
69 def __nonzero__(self): |
70 """Returns `True` if the Account is known, `False` if it's new.""" |
70 """Returns `True` if the Account is known, `False` if it's new.""" |
71 return not self._new |
71 return not self._new |
72 |
72 |
73 def _load(self): |
73 def _load(self): |
74 """Load 'uid', 'mid', 'qid' and 'tid' from the database and set |
74 """Load 'uid', 'mid', 'qid', 'ssid' and 'tid' from the database and |
75 _new to `False` - if the user could be found. """ |
75 set _new to `False` - if the user could be found. """ |
76 dbc = self._dbh.cursor() |
76 dbc = self._dbh.cursor() |
77 dbc.execute('SELECT uid, mid, qid, tid FROM users WHERE gid = %s AND ' |
77 dbc.execute('SELECT uid, mid, qid, ssid, tid FROM users WHERE gid = ' |
78 'local_part=%s', (self._domain.gid, self._addr.localpart)) |
78 '%s AND local_part = %s', (self._domain.gid, |
|
79 self._addr.localpart)) |
79 result = dbc.fetchone() |
80 result = dbc.fetchone() |
80 dbc.close() |
81 dbc.close() |
81 if result: |
82 if result: |
82 self._uid, _mid, _qid, _tid = result |
83 self._uid, _mid, _qid, _ssid, _tid = result |
83 if _qid != self._qlimit.qid: |
84 if _qid != self._qlimit.qid: |
84 self._qlimit = QuotaLimit(self._dbh, qid=_qid) |
85 self._qlimit = QuotaLimit(self._dbh, qid=_qid) |
|
86 if _ssid != self._services.ssid: |
|
87 self._services = ServiceSet(self._dbh, ssid=_ssid) |
85 if _tid != self._transport.tid: |
88 if _tid != self._transport.tid: |
86 self._transport = Transport(self._dbh, tid=_tid) |
89 self._transport = Transport(self._dbh, tid=_tid) |
87 self._mail = MailLocation(self._dbh, mid=_mid) |
90 self._mail = MailLocation(self._dbh, mid=_mid) |
88 self._new = False |
91 self._new = False |
89 |
92 |
112 {'transport': self._transport, |
115 {'transport': self._transport, |
113 'mbfmt': maillocation.mbformat}, INVALID_MAIL_LOCATION) |
116 'mbfmt': maillocation.mbformat}, INVALID_MAIL_LOCATION) |
114 self._mail = maillocation |
117 self._mail = maillocation |
115 self._set_uid() |
118 self._set_uid() |
116 |
119 |
117 def _update_services(self, activate, *services): |
|
118 """Activate or deactivate the Account's services. |
|
119 |
|
120 Arguments: |
|
121 |
|
122 `activate`: bool |
|
123 When `True` the Account's user will be able to login to the |
|
124 services, otherwise the login will fail. |
|
125 `*services` |
|
126 No or one or more of the services: imap, pop3, smtp and sieve |
|
127 """ |
|
128 self._chk_state() |
|
129 if services: |
|
130 services = set(services) |
|
131 for service in services: |
|
132 if service not in SERVICES: |
|
133 raise AErr(_(u"Unknown service: '%s'") % service, |
|
134 UNKNOWN_SERVICE) |
|
135 else: |
|
136 services = SERVICES |
|
137 state = ('FALSE', 'TRUE')[activate] |
|
138 sql = 'UPDATE users SET %s WHERE uid = %u' % ( |
|
139 (' = %(s)s, '.join(services) + ' = %(s)s') % {'s': state}, |
|
140 self._uid) |
|
141 if 'sieve' in services and \ |
|
142 cfg_dget('misc.dovecot_version') < 0x10200b02: |
|
143 sql = sql.replace('sieve', 'managesieve') |
|
144 dbc = self._dbh.cursor() |
|
145 dbc.execute(sql) |
|
146 if dbc.rowcount > 0: |
|
147 self._dbh.commit() |
|
148 dbc.close() |
|
149 |
|
150 def _update_tables(self, column, value): |
120 def _update_tables(self, column, value): |
151 """Update various columns in the users table. |
121 """Update various columns in the users table. |
152 |
122 |
153 Arguments: |
123 Arguments: |
154 |
124 |
155 `column` : basestring |
125 `column` : basestring |
156 Name of the table column. Currently: qid and tid |
126 Name of the table column. Currently: qid, ssid and tid |
157 `value` : long |
127 `value` : long |
158 The referenced key |
128 The referenced key |
159 """ |
129 """ |
160 if column not in ('qid', 'tid'): |
130 if column not in ('qid', 'ssid', 'tid'): |
161 raise ValueError('Unknown column: %r' % column) |
131 raise ValueError('Unknown column: %r' % column) |
162 dbc = self._dbh.cursor() |
132 dbc = self._dbh.cursor() |
163 dbc.execute('UPDATE users SET %s = %%s WHERE uid = %%s' % column, |
133 dbc.execute('UPDATE users SET %s = %%s WHERE uid = %%s' % column, |
164 (value, self._uid)) |
134 (value, self._uid)) |
165 if dbc.rowcount > 0: |
135 if dbc.rowcount > 0: |
235 ACCOUNT_EXISTS) |
205 ACCOUNT_EXISTS) |
236 if not isinstance(password, basestring) or not password: |
206 if not isinstance(password, basestring) or not password: |
237 raise AErr(_(u"Could not accept password: '%s'") % password, |
207 raise AErr(_(u"Could not accept password: '%s'") % password, |
238 ACCOUNT_MISSING_PASSWORD) |
208 ACCOUNT_MISSING_PASSWORD) |
239 self._passwd = password |
209 self._passwd = password |
240 |
|
241 def enable(self, *services): |
|
242 """Enable all or the given service/s for the Account. |
|
243 |
|
244 Possible *services* are: 'imap', 'pop3', 'sieve' and 'smtp'. |
|
245 When all services should be enabled, give no service name. |
|
246 |
|
247 Arguments: |
|
248 |
|
249 `*services` : basestring |
|
250 No or one or more of the services 'imap', 'pop3', 'smtp', and |
|
251 'sieve'. |
|
252 """ |
|
253 self._update_services(True, *services) |
|
254 |
|
255 def disable(self, *services): |
|
256 """Disable all or the given service/s for the Account. |
|
257 |
|
258 For more information see: Account.enable().""" |
|
259 self._update_services(False, *services) |
|
260 |
210 |
261 def save(self): |
211 def save(self): |
262 """Save the new Account in the database.""" |
212 """Save the new Account in the database.""" |
263 if not self._new: |
213 if not self._new: |
264 raise AErr(_(u"The account '%s' already exists.") % self._addr, |
214 raise AErr(_(u"The account '%s' already exists.") % self._addr, |
265 ACCOUNT_EXISTS) |
215 ACCOUNT_EXISTS) |
266 if not self._passwd: |
216 if not self._passwd: |
267 raise AErr(_(u"No password set for account: '%s'") % self._addr, |
217 raise AErr(_(u"No password set for account: '%s'") % self._addr, |
268 ACCOUNT_MISSING_PASSWORD) |
218 ACCOUNT_MISSING_PASSWORD) |
269 if cfg_dget('misc.dovecot_version') >= 0x10200b02: |
|
270 sieve_col = 'sieve' |
|
271 else: |
|
272 sieve_col = 'managesieve' |
|
273 self._prepare(MailLocation(self._dbh, mbfmt=cfg_dget('mailbox.format'), |
219 self._prepare(MailLocation(self._dbh, mbfmt=cfg_dget('mailbox.format'), |
274 directory=cfg_dget('mailbox.root'))) |
220 directory=cfg_dget('mailbox.root'))) |
275 dbc = self._dbh.cursor() |
221 dbc = self._dbh.cursor() |
276 dbc.execute('INSERT INTO users (local_part, passwd, uid, gid, mid, ' |
222 dbc.execute('INSERT INTO users (local_part, passwd, uid, gid, mid, ' |
277 'qid, tid, smtp, pop3, imap, %s) VALUES' % (sieve_col,) + \ |
223 'qid, ssid, tid) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)', |
278 '(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)', |
|
279 (self._addr.localpart, |
224 (self._addr.localpart, |
280 pwhash(self._passwd, user=self._addr), self._uid, |
225 pwhash(self._passwd, user=self._addr), self._uid, |
281 self._domain.gid, self._mail.mid, self._qlimit.qid, |
226 self._domain.gid, self._mail.mid, self._qlimit.qid, |
282 self._transport.tid, cfg_dget('account.smtp'), |
227 self._services.ssid, self._transport.tid)) |
283 cfg_dget('account.pop3'), cfg_dget('account.imap'), |
|
284 cfg_dget('account.sieve'))) |
|
285 self._dbh.commit() |
228 self._dbh.commit() |
286 dbc.close() |
229 dbc.close() |
287 self._new = False |
230 self._new = False |
288 |
231 |
289 def modify(self, field, value): |
232 def modify(self, field, value): |
327 assert isinstance(quotalimit, QuotaLimit) |
270 assert isinstance(quotalimit, QuotaLimit) |
328 if quotalimit == self._qlimit: |
271 if quotalimit == self._qlimit: |
329 return |
272 return |
330 self._update_tables('qid', quotalimit.qid) |
273 self._update_tables('qid', quotalimit.qid) |
331 self._qlimit = quotalimit |
274 self._qlimit = quotalimit |
|
275 |
|
276 def update_serviceset(self, serviceset): |
|
277 """Assign a different set of services to the Account. |
|
278 |
|
279 Argument: |
|
280 |
|
281 `serviceset` : VirtualMailManager.serviceset.ServiceSet |
|
282 the new service set. |
|
283 """ |
|
284 self._chk_state() |
|
285 assert isinstance(serviceset, ServiceSet) |
|
286 if serviceset == self._services: |
|
287 return |
|
288 self._update_tables('ssid', serviceset.ssid) |
|
289 self._services = serviceset |
332 |
290 |
333 def update_transport(self, transport): |
291 def update_transport(self, transport): |
334 """Sets a new transport for the Account. |
292 """Sets a new transport for the Account. |
335 |
293 |
336 Arguments: |
294 Arguments: |
357 The keys of the dict are: 'address', 'gid', 'home', 'imap' |
315 The keys of the dict are: 'address', 'gid', 'home', 'imap' |
358 'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport', 'uid', |
316 'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport', 'uid', |
359 'uq_bytes', 'uq_messages', 'ql_bytes', and 'ql_messages'. |
317 'uq_bytes', 'uq_messages', 'ql_bytes', and 'ql_messages'. |
360 """ |
318 """ |
361 self._chk_state() |
319 self._chk_state() |
362 if cfg_dget('misc.dovecot_version') >= 0x10200b02: |
320 dbc = self._dbh.cursor() |
363 sieve_col = 'sieve' |
321 dbc.execute('SELECT name, CASE WHEN bytes IS NULL THEN 0 ELSE bytes ' |
364 else: |
322 'END, CASE WHEN messages IS NULL THEN 0 ELSE messages END ' |
365 sieve_col = 'managesieve' |
323 'FROM users LEFT JOIN userquota USING (uid) WHERE ' |
366 dbc = self._dbh.cursor() |
324 'users.uid = %s', (self._uid,)) |
367 dbc.execute('SELECT name, smtp, pop3, imap, %s, CASE WHEN bytes IS ' |
|
368 'NULL THEN 0 ELSE bytes END, CASE WHEN messages IS NULL ' |
|
369 'THEN 0 ELSE messages END FROM users LEFT JOIN userquota ' |
|
370 'USING (uid) WHERE users.uid = %u' % (sieve_col, |
|
371 self._uid)) |
|
372 info = dbc.fetchone() |
325 info = dbc.fetchone() |
373 dbc.close() |
326 dbc.close() |
374 if info: |
327 if info: |
375 keys = ('name', 'smtp', 'pop3', 'imap', sieve_col, 'uq_bytes', |
328 info = dict(zip(('name', 'uq_bytes', 'uq_messages'), info)) |
376 'uq_messages') |
329 for service, state in self._services.services.iteritems(): |
377 info = dict(zip(keys, info)) |
330 # TP: A service (e.g. pop3 or imap) may be enabled/usable or |
378 for service in keys[1:5]: |
331 # disabled/unusable for a user. |
379 if info[service]: |
332 info[service] = (_('disabled'), _('enabled'))[state] |
380 # TP: A service (pop3/imap) is enabled/usable for a user |
|
381 info[service] = _('enabled') |
|
382 else: |
|
383 # TP: A service (pop3/imap) isn't enabled/usable for a user |
|
384 info[service] = _('disabled') |
|
385 info['address'] = self._addr |
333 info['address'] = self._addr |
386 info['gid'] = self._domain.gid |
334 info['gid'] = self._domain.gid |
387 info['home'] = '%s/%s' % (self._domain.directory, self._uid) |
335 info['home'] = '%s/%s' % (self._domain.directory, self._uid) |
388 info['mail_location'] = self._mail.mail_location |
336 info['mail_location'] = self._mail.mail_location |
389 info['ql_bytes'] = self._qlimit.bytes |
337 info['ql_bytes'] = self._qlimit.bytes |