diff -r 45ec5c3cfef4 -r 524f7ed5ad5b VirtualMailManager/Account.py --- a/VirtualMailManager/Account.py Thu Apr 29 03:38:19 2010 +0000 +++ b/VirtualMailManager/Account.py Thu Apr 29 05:57:53 2010 +0000 @@ -11,22 +11,24 @@ from VirtualMailManager.Domain import Domain from VirtualMailManager.EmailAddress import EmailAddress from VirtualMailManager.Transport import Transport +from VirtualMailManager.common import version_str from VirtualMailManager.constants.ERROR import \ ACCOUNT_EXISTS, ACCOUNT_MISSING_PASSWORD, ALIAS_PRESENT, \ - INVALID_AGUMENT, NO_SUCH_ACCOUNT, NO_SUCH_DOMAIN, \ - UNKNOWN_MAILLOCATION_NAME, UNKNOWN_SERVICE + INVALID_AGUMENT, INVALID_MAIL_LOCATION, NO_SUCH_ACCOUNT, NO_SUCH_DOMAIN, \ + UNKNOWN_SERVICE from VirtualMailManager.errors import AccountError as AErr -from VirtualMailManager.maillocation import MailLocation, known_format -from VirtualMailManager.pycompat import all +from VirtualMailManager.maillocation import MailLocation +from VirtualMailManager.password import pwhash _ = lambda msg: msg +cfg_dget = lambda option: None class Account(object): """Class to manage e-mail accounts.""" - __slots__ = ('_addr', '_domain', '_mid', '_new', '_passwd', '_tid', '_uid', - '_dbh') + __slots__ = ('_addr', '_dbh', '_domain', '_mid', '_new', '_passwd', + '_transport', '_uid') def __init__(self, dbh, address): """Creates a new Account instance. @@ -38,7 +40,7 @@ `dbh` : pyPgSQL.PgSQL.Connection A database connection for the database access. - `address` : basestring + `address` : VirtualMailManager.EmailAddress.EmailAddress The e-mail address of the (new) Account. """ if not isinstance(address, EmailAddress): @@ -51,11 +53,15 @@ self._addr.domainname, NO_SUCH_DOMAIN) self._uid = 0 self._mid = 0 - self._tid = 0 + self._transport = self._domain.transport self._passwd = None self._new = True self._load() + def __nonzero__(self): + """Returns `True` if the Account is known, `False` if it's new.""" + return not self._new + def _load(self): """Load 'uid', 'mid' and 'tid' from the database and set _new to `False` - if the user could be found. """ @@ -66,7 +72,9 @@ result = dbc.fetchone() dbc.close() if result: - self._uid, self._mid, self._tid = result + self._uid, self._mid, _tid = result + if _tid != self._transport.tid: + self._transport = Transport(self._dbh, tid=_tid) self._new = False def _set_uid(self): @@ -79,22 +87,29 @@ def _prepare(self, maillocation): """Check and set different attributes - before we store the - information in the database.""" - if not known_format(maillocation): - raise AErr(_(u'Unknown mail_location mailbox format: %r') % - maillocation, UNKNOWN_MAILLOCATION_NAME) - self._mid = MailLocation(format=maillocation).mid - if not self._tid: - self._tid = self._domain.transport.tid + information in the database. + """ + if maillocation.dovecot_version > cfg_dget('misc.dovecot_version'): + raise AErr(_("The mail_location prefix '%(prefix)s' requires \ +Dovecot >= v%(version)s") % {'prefix': maillocation.prefix, + 'version': version_str(maillocation.dovecot_version)}, + INVALID_MAIL_LOCATION) + if not maillocation.postfix and \ + self._transport.transport.lower() in ('virtual:', 'virtual'): + raise AErr(_(u"Invalid transport '%(transport)s' for mail_location\ + prefix '%(prefix)s'") % {'transport': self._transport, + 'prefix': maillocation.prefix}, + INVALID_MAIL_LOCATION) + self._mid = maillocation.mid self._set_uid() - def _switch_state(self, state, dcvers, service): + def _switch_state(self, state, service): """Switch the state of the Account's services on or off. See Account.enable()/Account.disable() for more information.""" self._chk_state() if service not in (None, 'all', 'imap', 'pop3', 'sieve', 'smtp'): raise AErr(_(u"Unknown service: '%s'.") % service, UNKNOWN_SERVICE) - if dcvers >= 0x10200b02: + if cfg_dget('misc.dovecot_version') >= 0x10200b02: sieve_col = 'sieve' else: sieve_col = 'managesieve' @@ -141,17 +156,23 @@ @property def domain_directory(self): """The directory of the domain the Account belongs to.""" - return self._domain.directory + if self._domain: + return self._domain.directory + return None @property def gid(self): """The Account's group ID.""" - return self._domain.gid + if self._domain: + return self._domain.gid + return None @property def home(self): """The Account's home directory.""" - return '%s/%s' % (self._domain.directory, self._uid) + if not self._new: + return '%s/%s' % (self._domain.directory, self._uid) + return None @property def uid(self): @@ -167,7 +188,11 @@ Argument: `password` : basestring - The hashed password for the new Account.""" + The password for the new Account. + """ + if not isinstance(password, basestring) or not password: + raise AErr(_(u"Couldn't accept password: '%s'") % password, + ACCOUNT_MISSING_PASSWORD) self._passwd = password def set_transport(self, transport): @@ -181,9 +206,9 @@ `transport` : basestring The string representation of the transport, e.g.: 'dovecot:' """ - self._tid = Transport(self._dbh, transport=transport).tid + self._transport = Transport(self._dbh, transport=transport) - def enable(self, dcvers, service=None): + def enable(self, service=None): """Enable a/all service/s for the Account. Possible values for the *service* are: 'imap', 'pop3', 'sieve' and @@ -192,53 +217,37 @@ Arguments: - `dcvers` : int - The concatenated major and minor version number from - `dovecot --version`. `service` : basestring The name of a service ('imap', 'pop3', 'smtp', 'sieve'), 'all' or `None`. """ - self._switch_state(True, dcvers, service) + self._switch_state(True, service) - def disable(self, dcvers, service=None): + def disable(self, service=None): """Disable a/all service/s for the Account. For more information see: Account.enable().""" - self._switch_state(False, dcvers, service) - - def save(self, maillocation, dcvers, smtp, pop3, imap, sieve): - """Save the new Account in the database. - - Arguments: + self._switch_state(False, service) - `maillocation` : basestring - The mailbox format of the mail_location: 'maildir', 'mbox', - 'dbox' or 'mdbox'. - `dcvers` : int - The concatenated major and minor version number from - `dovecot --version`. - `smtp, pop3, imap, sieve` : bool - Indicates if the user of the Account should be able to use this - services. - """ + def save(self): + """Save the new Account in the database.""" if not self._new: raise AErr(_(u"The account '%s' already exists.") % self._addr, ACCOUNT_EXISTS) if not self._passwd: raise AErr(_(u"No password set for '%s'.") % self._addr, ACCOUNT_MISSING_PASSWORD) - assert all(isinstance(service, bool) for service in (smtp, pop3, imap, - sieve)) - if dcvers >= 0x10200b02: + if cfg_dget('misc.dovecot_version') >= 0x10200b02: sieve_col = 'sieve' else: sieve_col = 'managesieve' - self._prepare(maillocation) + self._prepare(MailLocation(format=cfg_dget('mailbox.format'))) sql = "INSERT INTO users (local_part, passwd, uid, gid, mid, tid,\ smtp, pop3, imap, %s) VALUES ('%s', '%s', %d, %d, %d, %d, %s, %s, %s, %s)" % ( - sieve_col, self._addr.localpart, self._passwd, self._uid, - self._domain.gid, self._mid, self._tid, smtp, pop3, imap, sieve) + sieve_col, self._addr.localpart, pwhash(self._passwd), self._uid, + self._domain.gid, self._mid, self._transport.tid, + cfg_dget('account.smtp'), cfg_dget('account.pop3'), + cfg_dget('account.imap'), cfg_dget('account.sieve')) dbc = self._dbh.cursor() dbc.execute(sql) self._dbh.commit() @@ -248,7 +257,7 @@ def modify(self, field, value): """Update the Account's *field* to the new *value*. - Possible values for *filed* are: 'name', 'password' and + Possible values for *field* are: 'name', 'password' and 'transport'. *value* is the *field*'s new value. Arguments: @@ -256,8 +265,7 @@ `field` : basestring The attribute name: 'name', 'password' or 'transport' `value` : basestring - The new value of the attribute. The password is expected as a - hashed password string. + The new value of the attribute. """ if field not in ('name', 'password', 'transport'): raise AErr(_(u"Unknown field: '%s'") % field, INVALID_AGUMENT) @@ -265,11 +273,12 @@ dbc = self._dbh.cursor() if field == 'password': dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s', - value, self._uid) + pwhash(value), self._uid) elif field == 'transport': - self._tid = Transport(self._dbh, transport=value).tid - dbc.execute('UPDATE users SET tid = %s WHERE uid = %s', - self._tid, self._uid) + if value != self._transport.transport: + self._transport = Transport(self._dbh, transport=value) + dbc.execute('UPDATE users SET tid = %s WHERE uid = %s', + self._transport.tid, self._uid) else: dbc.execute('UPDATE users SET name = %s WHERE uid = %s', value, self._uid) @@ -277,35 +286,28 @@ self._dbh.commit() dbc.close() - def get_info(self, dcvers): + def get_info(self): """Returns a dict with some information about the Account. The keys of the dict are: 'address', 'gid', 'home', 'imap' 'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport' and 'uid'. - - Argument: - - `dcvers` : int - The concatenated major and minor version number from - `dovecot --version`. """ self._chk_state() - if dcvers >= 0x10200b02: + if cfg_dget('misc.dovecot_version') >= 0x10200b02: sieve_col = 'sieve' else: sieve_col = 'managesieve' - sql = 'SELECT name, uid, gid, mid, tid, smtp, pop3, imap, %s\ - FROM users WHERE uid = %d' % (sieve_col, self._uid) + sql = 'SELECT name, smtp, pop3, imap, %s FROM users WHERE uid = %d' % \ + (sieve_col, self._uid) dbc = self._dbh.cursor() dbc.execute(sql) info = dbc.fetchone() dbc.close() if info: - keys = ('name', 'uid', 'gid', 'mid', 'transport', 'smtp', - 'pop3', 'imap', sieve_col) + keys = ('name', 'smtp', 'pop3', 'imap', sieve_col) info = dict(zip(keys, info)) - for service in ('smtp', 'pop3', 'imap', sieve_col): + for service in keys[1:]: if info[service]: # TP: A service (pop3/imap) is enabled/usable for a user info[service] = _('enabled') @@ -313,14 +315,11 @@ # TP: A service (pop3/imap) isn't enabled/usable for a user info[service] = _('disabled') info['address'] = self._addr - info['home'] = '%s/%s' % (self._domain.directory, info['uid']) - info['mail_location'] = MailLocation(mid=info['mid']).mail_location - if info['transport'] == self._domain.transport.tid: - info['transport'] = self._domain.transport.transport - else: - info['transport'] = Transport(self._dbh, - tid=info['transport']).transport - del info['mid'] + info['gid'] = self._domain.gid + info['home'] = '%s/%s' % (self._domain.directory, self._uid) + info['mail_location'] = MailLocation(mid=self._mid).mail_location + info['transport'] = self._transport.transport + info['uid'] = self._uid return info # nearly impossibleā€½ raise AErr(_(u"Couldn't fetch information for account: '%s'") \ @@ -341,18 +340,21 @@ aliases = [alias[0] for alias in addresses] return aliases - def delete(self, delalias): + def delete(self, delalias=False): """Delete the Account from the database. Argument: - `delalias` : basestring - if the values of delalias is 'delalias', all aliases, which - points to the Account, will be also deleted.""" + `delalias` : bool + if *delalias* is `True`, all aliases, which points to the Account, + will be also deleted. If there are aliases and *delalias* is + `False`, an AccountError will be raised. + """ + assert isinstance(delalias, bool) self._chk_state() dbc = self._dbh.cursor() - if delalias == 'delalias': - dbc.execute('DELETE FROM users WHERE uid= %s', self._uid) + if delalias: + dbc.execute('DELETE FROM users WHERE uid = %s', self._uid) # delete also all aliases where the destination address is the same # as for this account. dbc.execute("DELETE FROM alias WHERE destination = %s", @@ -360,16 +362,19 @@ self._dbh.commit() else: # check first for aliases a_count = self._count_aliases() - if a_count == 0: - dbc.execute('DELETE FROM users WHERE uid = %s', self._uid) - self._dbh.commit() - else: + if a_count > 0: dbc.close() raise AErr(_(u"There are %(count)d aliases with the \ destination address '%(address)s'.") % \ {'count': a_count, 'address': self._addr}, ALIAS_PRESENT) + dbc.execute('DELETE FROM users WHERE uid = %s', self._uid) + self._dbh.commit() dbc.close() + self._new = True + self._uid = self._mid = 0 + self._addr = self._dbh = self._domain = self._passwd = None + self._transport = None def get_account_by_uid(uid, dbh): @@ -403,5 +408,4 @@ info = dict(zip(('address', 'uid', 'gid'), info)) return info - -del _ +del _, cfg_dget