VirtualMailManager/Account.py
branchv0.6.x
changeset 275 524f7ed5ad5b
parent 266 e14c345b44a1
child 276 f2ecfe0a0e09
equal deleted inserted replaced
274:45ec5c3cfef4 275:524f7ed5ad5b
     9 """
     9 """
    10 
    10 
    11 from VirtualMailManager.Domain import Domain
    11 from VirtualMailManager.Domain import Domain
    12 from VirtualMailManager.EmailAddress import EmailAddress
    12 from VirtualMailManager.EmailAddress import EmailAddress
    13 from VirtualMailManager.Transport import Transport
    13 from VirtualMailManager.Transport import Transport
       
    14 from VirtualMailManager.common import version_str
    14 from VirtualMailManager.constants.ERROR import \
    15 from VirtualMailManager.constants.ERROR import \
    15      ACCOUNT_EXISTS, ACCOUNT_MISSING_PASSWORD, ALIAS_PRESENT, \
    16      ACCOUNT_EXISTS, ACCOUNT_MISSING_PASSWORD, ALIAS_PRESENT, \
    16      INVALID_AGUMENT, NO_SUCH_ACCOUNT, NO_SUCH_DOMAIN, \
    17      INVALID_AGUMENT, INVALID_MAIL_LOCATION, NO_SUCH_ACCOUNT, NO_SUCH_DOMAIN, \
    17      UNKNOWN_MAILLOCATION_NAME, UNKNOWN_SERVICE
    18      UNKNOWN_SERVICE
    18 from VirtualMailManager.errors import AccountError as AErr
    19 from VirtualMailManager.errors import AccountError as AErr
    19 from VirtualMailManager.maillocation import MailLocation, known_format
    20 from VirtualMailManager.maillocation import MailLocation
    20 from VirtualMailManager.pycompat import all
    21 from VirtualMailManager.password import pwhash
    21 
    22 
    22 
    23 
    23 _ = lambda msg: msg
    24 _ = lambda msg: msg
       
    25 cfg_dget = lambda option: None
    24 
    26 
    25 
    27 
    26 class Account(object):
    28 class Account(object):
    27     """Class to manage e-mail accounts."""
    29     """Class to manage e-mail accounts."""
    28     __slots__ = ('_addr', '_domain', '_mid', '_new', '_passwd', '_tid', '_uid',
    30     __slots__ = ('_addr', '_dbh', '_domain', '_mid', '_new', '_passwd',
    29                  '_dbh')
    31                  '_transport', '_uid')
    30 
    32 
    31     def __init__(self, dbh, address):
    33     def __init__(self, dbh, address):
    32         """Creates a new Account instance.
    34         """Creates a new Account instance.
    33 
    35 
    34         When an account with the given *address* could be found in the
    36         When an account with the given *address* could be found in the
    36 
    38 
    37         Arguments:
    39         Arguments:
    38 
    40 
    39         `dbh` : pyPgSQL.PgSQL.Connection
    41         `dbh` : pyPgSQL.PgSQL.Connection
    40           A database connection for the database access.
    42           A database connection for the database access.
    41         `address` : basestring
    43         `address` : VirtualMailManager.EmailAddress.EmailAddress
    42           The e-mail address of the (new) Account.
    44           The e-mail address of the (new) Account.
    43         """
    45         """
    44         if not isinstance(address, EmailAddress):
    46         if not isinstance(address, EmailAddress):
    45             raise TypeError("Argument 'address' is not an EmailAddress")
    47             raise TypeError("Argument 'address' is not an EmailAddress")
    46         self._addr = address
    48         self._addr = address
    49         if not self._domain.gid:
    51         if not self._domain.gid:
    50             raise AErr(_(u"The domain '%s' doesn't exist.") %
    52             raise AErr(_(u"The domain '%s' doesn't exist.") %
    51                        self._addr.domainname, NO_SUCH_DOMAIN)
    53                        self._addr.domainname, NO_SUCH_DOMAIN)
    52         self._uid = 0
    54         self._uid = 0
    53         self._mid = 0
    55         self._mid = 0
    54         self._tid = 0
    56         self._transport = self._domain.transport
    55         self._passwd = None
    57         self._passwd = None
    56         self._new = True
    58         self._new = True
    57         self._load()
    59         self._load()
       
    60 
       
    61     def __nonzero__(self):
       
    62         """Returns `True` if the Account is known, `False` if it's new."""
       
    63         return not self._new
    58 
    64 
    59     def _load(self):
    65     def _load(self):
    60         """Load 'uid', 'mid' and 'tid' from the database and set _new to
    66         """Load 'uid', 'mid' and 'tid' from the database and set _new to
    61         `False` - if the user could be found. """
    67         `False` - if the user could be found. """
    62         dbc = self._dbh.cursor()
    68         dbc = self._dbh.cursor()
    64             "SELECT uid, mid, tid FROM users WHERE gid=%s AND local_part=%s",
    70             "SELECT uid, mid, tid FROM users WHERE gid=%s AND local_part=%s",
    65                     self._domain.gid, self._addr.localpart)
    71                     self._domain.gid, self._addr.localpart)
    66         result = dbc.fetchone()
    72         result = dbc.fetchone()
    67         dbc.close()
    73         dbc.close()
    68         if result:
    74         if result:
    69             self._uid, self._mid, self._tid = result
    75             self._uid, self._mid, _tid = result
       
    76             if _tid != self._transport.tid:
       
    77                 self._transport = Transport(self._dbh, tid=_tid)
    70             self._new = False
    78             self._new = False
    71 
    79 
    72     def _set_uid(self):
    80     def _set_uid(self):
    73         """Set the unique ID for the new Account."""
    81         """Set the unique ID for the new Account."""
    74         assert self._uid == 0
    82         assert self._uid == 0
    77         self._uid = dbc.fetchone()[0]
    85         self._uid = dbc.fetchone()[0]
    78         dbc.close()
    86         dbc.close()
    79 
    87 
    80     def _prepare(self, maillocation):
    88     def _prepare(self, maillocation):
    81         """Check and set different attributes - before we store the
    89         """Check and set different attributes - before we store the
    82         information in the database."""
    90         information in the database.
    83         if not known_format(maillocation):
    91         """
    84             raise AErr(_(u'Unknown mail_location mailbox format: %r') %
    92         if maillocation.dovecot_version > cfg_dget('misc.dovecot_version'):
    85                        maillocation, UNKNOWN_MAILLOCATION_NAME)
    93             raise AErr(_("The mail_location prefix '%(prefix)s' requires \
    86         self._mid = MailLocation(format=maillocation).mid
    94 Dovecot >= v%(version)s") % {'prefix': maillocation.prefix,
    87         if not self._tid:
    95                        'version': version_str(maillocation.dovecot_version)},
    88             self._tid = self._domain.transport.tid
    96                        INVALID_MAIL_LOCATION)
       
    97         if not maillocation.postfix and \
       
    98           self._transport.transport.lower() in ('virtual:', 'virtual'):
       
    99             raise AErr(_(u"Invalid transport '%(transport)s' for mail_location\
       
   100  prefix '%(prefix)s'") % {'transport': self._transport,
       
   101                        'prefix': maillocation.prefix},
       
   102                        INVALID_MAIL_LOCATION)
       
   103         self._mid = maillocation.mid
    89         self._set_uid()
   104         self._set_uid()
    90 
   105 
    91     def _switch_state(self, state, dcvers, service):
   106     def _switch_state(self, state, service):
    92         """Switch the state of the Account's services on or off. See
   107         """Switch the state of the Account's services on or off. See
    93         Account.enable()/Account.disable() for more information."""
   108         Account.enable()/Account.disable() for more information."""
    94         self._chk_state()
   109         self._chk_state()
    95         if service not in (None, 'all', 'imap', 'pop3', 'sieve', 'smtp'):
   110         if service not in (None, 'all', 'imap', 'pop3', 'sieve', 'smtp'):
    96             raise AErr(_(u"Unknown service: '%s'.") % service, UNKNOWN_SERVICE)
   111             raise AErr(_(u"Unknown service: '%s'.") % service, UNKNOWN_SERVICE)
    97         if dcvers >= 0x10200b02:
   112         if cfg_dget('misc.dovecot_version') >= 0x10200b02:
    98             sieve_col = 'sieve'
   113             sieve_col = 'sieve'
    99         else:
   114         else:
   100             sieve_col = 'managesieve'
   115             sieve_col = 'managesieve'
   101         if service in ('smtp', 'pop3', 'imap'):
   116         if service in ('smtp', 'pop3', 'imap'):
   102             sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (service, state,
   117             sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (service, state,
   139         return self._addr
   154         return self._addr
   140 
   155 
   141     @property
   156     @property
   142     def domain_directory(self):
   157     def domain_directory(self):
   143         """The directory of the domain the Account belongs to."""
   158         """The directory of the domain the Account belongs to."""
   144         return self._domain.directory
   159         if self._domain:
       
   160             return self._domain.directory
       
   161         return None
   145 
   162 
   146     @property
   163     @property
   147     def gid(self):
   164     def gid(self):
   148         """The Account's group ID."""
   165         """The Account's group ID."""
   149         return self._domain.gid
   166         if self._domain:
       
   167             return self._domain.gid
       
   168         return None
   150 
   169 
   151     @property
   170     @property
   152     def home(self):
   171     def home(self):
   153         """The Account's home directory."""
   172         """The Account's home directory."""
   154         return '%s/%s' % (self._domain.directory, self._uid)
   173         if not self._new:
       
   174             return '%s/%s' % (self._domain.directory, self._uid)
       
   175         return None
   155 
   176 
   156     @property
   177     @property
   157     def uid(self):
   178     def uid(self):
   158         """The Account's unique ID."""
   179         """The Account's unique ID."""
   159         return self._uid
   180         return self._uid
   165         Account.modify().
   186         Account.modify().
   166 
   187 
   167         Argument:
   188         Argument:
   168 
   189 
   169         `password` : basestring
   190         `password` : basestring
   170           The hashed password for the new Account."""
   191           The password for the new Account.
       
   192         """
       
   193         if not isinstance(password, basestring) or not password:
       
   194             raise AErr(_(u"Couldn't accept password: '%s'") % password,
       
   195                        ACCOUNT_MISSING_PASSWORD)
   171         self._passwd = password
   196         self._passwd = password
   172 
   197 
   173     def set_transport(self, transport):
   198     def set_transport(self, transport):
   174         """Set the transport for the new Account.
   199         """Set the transport for the new Account.
   175 
   200 
   179         Argument:
   204         Argument:
   180 
   205 
   181         `transport` : basestring
   206         `transport` : basestring
   182           The string representation of the transport, e.g.: 'dovecot:'
   207           The string representation of the transport, e.g.: 'dovecot:'
   183         """
   208         """
   184         self._tid = Transport(self._dbh, transport=transport).tid
   209         self._transport = Transport(self._dbh, transport=transport)
   185 
   210 
   186     def enable(self, dcvers, service=None):
   211     def enable(self, service=None):
   187         """Enable a/all service/s for the Account.
   212         """Enable a/all service/s for the Account.
   188 
   213 
   189         Possible values for the *service* are: 'imap', 'pop3', 'sieve' and
   214         Possible values for the *service* are: 'imap', 'pop3', 'sieve' and
   190         'smtp'. When all services should be enabled, use 'all' or the
   215         'smtp'. When all services should be enabled, use 'all' or the
   191         default value `None`.
   216         default value `None`.
   192 
   217 
   193         Arguments:
   218         Arguments:
   194 
   219 
   195         `dcvers` : int
       
   196           The concatenated major and minor version number from
       
   197           `dovecot --version`.
       
   198         `service` : basestring
   220         `service` : basestring
   199           The name of a service ('imap', 'pop3', 'smtp', 'sieve'), 'all'
   221           The name of a service ('imap', 'pop3', 'smtp', 'sieve'), 'all'
   200           or `None`.
   222           or `None`.
   201         """
   223         """
   202         self._switch_state(True, dcvers, service)
   224         self._switch_state(True, service)
   203 
   225 
   204     def disable(self, dcvers, service=None):
   226     def disable(self, service=None):
   205         """Disable a/all service/s for the Account.
   227         """Disable a/all service/s for the Account.
   206 
   228 
   207         For more information see: Account.enable()."""
   229         For more information see: Account.enable()."""
   208         self._switch_state(False, dcvers, service)
   230         self._switch_state(False, service)
   209 
   231 
   210     def save(self, maillocation, dcvers, smtp, pop3, imap, sieve):
   232     def save(self):
   211         """Save the new Account in the database.
   233         """Save the new Account in the database."""
   212 
       
   213         Arguments:
       
   214 
       
   215         `maillocation` : basestring
       
   216           The mailbox format of the mail_location: 'maildir', 'mbox',
       
   217           'dbox' or 'mdbox'.
       
   218         `dcvers` : int
       
   219           The concatenated major and minor version number from
       
   220           `dovecot --version`.
       
   221         `smtp, pop3, imap, sieve` : bool
       
   222           Indicates if the user of the Account should be able to use this
       
   223           services.
       
   224         """
       
   225         if not self._new:
   234         if not self._new:
   226             raise AErr(_(u"The account '%s' already exists.") % self._addr,
   235             raise AErr(_(u"The account '%s' already exists.") % self._addr,
   227                        ACCOUNT_EXISTS)
   236                        ACCOUNT_EXISTS)
   228         if not self._passwd:
   237         if not self._passwd:
   229             raise AErr(_(u"No password set for '%s'.") % self._addr,
   238             raise AErr(_(u"No password set for '%s'.") % self._addr,
   230                        ACCOUNT_MISSING_PASSWORD)
   239                        ACCOUNT_MISSING_PASSWORD)
   231         assert all(isinstance(service, bool) for service in (smtp, pop3, imap,
   240         if cfg_dget('misc.dovecot_version') >= 0x10200b02:
   232                                                              sieve))
       
   233         if dcvers >= 0x10200b02:
       
   234             sieve_col = 'sieve'
   241             sieve_col = 'sieve'
   235         else:
   242         else:
   236             sieve_col = 'managesieve'
   243             sieve_col = 'managesieve'
   237         self._prepare(maillocation)
   244         self._prepare(MailLocation(format=cfg_dget('mailbox.format')))
   238         sql = "INSERT INTO users (local_part, passwd, uid, gid, mid, tid,\
   245         sql = "INSERT INTO users (local_part, passwd, uid, gid, mid, tid,\
   239  smtp, pop3, imap, %s) VALUES ('%s', '%s', %d, %d, %d, %d, %s, %s, %s, %s)" % (
   246  smtp, pop3, imap, %s) VALUES ('%s', '%s', %d, %d, %d, %d, %s, %s, %s, %s)" % (
   240             sieve_col, self._addr.localpart, self._passwd, self._uid,
   247             sieve_col, self._addr.localpart, pwhash(self._passwd), self._uid,
   241             self._domain.gid, self._mid, self._tid, smtp, pop3, imap, sieve)
   248             self._domain.gid, self._mid, self._transport.tid,
       
   249             cfg_dget('account.smtp'), cfg_dget('account.pop3'),
       
   250             cfg_dget('account.imap'), cfg_dget('account.sieve'))
   242         dbc = self._dbh.cursor()
   251         dbc = self._dbh.cursor()
   243         dbc.execute(sql)
   252         dbc.execute(sql)
   244         self._dbh.commit()
   253         self._dbh.commit()
   245         dbc.close()
   254         dbc.close()
   246         self._new = False
   255         self._new = False
   247 
   256 
   248     def modify(self, field, value):
   257     def modify(self, field, value):
   249         """Update the Account's *field* to the new *value*.
   258         """Update the Account's *field* to the new *value*.
   250 
   259 
   251         Possible values for *filed* are: 'name', 'password' and
   260         Possible values for *field* are: 'name', 'password' and
   252         'transport'.  *value* is the *field*'s new value.
   261         'transport'.  *value* is the *field*'s new value.
   253 
   262 
   254         Arguments:
   263         Arguments:
   255 
   264 
   256         `field` : basestring
   265         `field` : basestring
   257           The attribute name: 'name', 'password' or 'transport'
   266           The attribute name: 'name', 'password' or 'transport'
   258         `value` : basestring
   267         `value` : basestring
   259           The new value of the attribute. The password is expected as a
   268           The new value of the attribute.
   260           hashed password string.
       
   261         """
   269         """
   262         if field not in ('name', 'password', 'transport'):
   270         if field not in ('name', 'password', 'transport'):
   263             raise AErr(_(u"Unknown field: '%s'") % field, INVALID_AGUMENT)
   271             raise AErr(_(u"Unknown field: '%s'") % field, INVALID_AGUMENT)
   264         self._chk_state()
   272         self._chk_state()
   265         dbc = self._dbh.cursor()
   273         dbc = self._dbh.cursor()
   266         if field == 'password':
   274         if field == 'password':
   267             dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s',
   275             dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s',
   268                         value, self._uid)
   276                         pwhash(value), self._uid)
   269         elif field == 'transport':
   277         elif field == 'transport':
   270             self._tid = Transport(self._dbh, transport=value).tid
   278             if value != self._transport.transport:
   271             dbc.execute('UPDATE users SET tid = %s WHERE uid = %s',
   279                 self._transport = Transport(self._dbh, transport=value)
   272                         self._tid, self._uid)
   280                 dbc.execute('UPDATE users SET tid = %s WHERE uid = %s',
       
   281                             self._transport.tid, self._uid)
   273         else:
   282         else:
   274             dbc.execute('UPDATE users SET name = %s WHERE uid = %s',
   283             dbc.execute('UPDATE users SET name = %s WHERE uid = %s',
   275                         value, self._uid)
   284                         value, self._uid)
   276         if dbc.rowcount > 0:
   285         if dbc.rowcount > 0:
   277             self._dbh.commit()
   286             self._dbh.commit()
   278         dbc.close()
   287         dbc.close()
   279 
   288 
   280     def get_info(self, dcvers):
   289     def get_info(self):
   281         """Returns a dict with some information about the Account.
   290         """Returns a dict with some information about the Account.
   282 
   291 
   283         The keys of the dict are: 'address', 'gid', 'home', 'imap'
   292         The keys of the dict are: 'address', 'gid', 'home', 'imap'
   284         'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport' and
   293         'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport' and
   285         'uid'.
   294         'uid'.
   286 
       
   287         Argument:
       
   288 
       
   289         `dcvers` : int
       
   290           The concatenated major and minor version number from
       
   291           `dovecot --version`.
       
   292         """
   295         """
   293         self._chk_state()
   296         self._chk_state()
   294         if dcvers >= 0x10200b02:
   297         if cfg_dget('misc.dovecot_version') >= 0x10200b02:
   295             sieve_col = 'sieve'
   298             sieve_col = 'sieve'
   296         else:
   299         else:
   297             sieve_col = 'managesieve'
   300             sieve_col = 'managesieve'
   298         sql = 'SELECT name, uid, gid, mid, tid, smtp, pop3, imap, %s\
   301         sql = 'SELECT name, smtp, pop3, imap, %s FROM users WHERE uid = %d' % \
   299  FROM users WHERE uid = %d' % (sieve_col, self._uid)
   302             (sieve_col, self._uid)
   300         dbc = self._dbh.cursor()
   303         dbc = self._dbh.cursor()
   301         dbc.execute(sql)
   304         dbc.execute(sql)
   302         info = dbc.fetchone()
   305         info = dbc.fetchone()
   303         dbc.close()
   306         dbc.close()
   304         if info:
   307         if info:
   305             keys = ('name', 'uid', 'gid', 'mid', 'transport', 'smtp',
   308             keys = ('name', 'smtp', 'pop3', 'imap', sieve_col)
   306                     'pop3', 'imap', sieve_col)
       
   307             info = dict(zip(keys, info))
   309             info = dict(zip(keys, info))
   308             for service in ('smtp', 'pop3', 'imap', sieve_col):
   310             for service in keys[1:]:
   309                 if info[service]:
   311                 if info[service]:
   310                     # TP: A service (pop3/imap) is enabled/usable for a user
   312                     # TP: A service (pop3/imap) is enabled/usable for a user
   311                     info[service] = _('enabled')
   313                     info[service] = _('enabled')
   312                 else:
   314                 else:
   313                     # TP: A service (pop3/imap) isn't enabled/usable for a user
   315                     # TP: A service (pop3/imap) isn't enabled/usable for a user
   314                     info[service] = _('disabled')
   316                     info[service] = _('disabled')
   315             info['address'] = self._addr
   317             info['address'] = self._addr
   316             info['home'] = '%s/%s' % (self._domain.directory, info['uid'])
   318             info['gid'] = self._domain.gid
   317             info['mail_location'] = MailLocation(mid=info['mid']).mail_location
   319             info['home'] = '%s/%s' % (self._domain.directory, self._uid)
   318             if info['transport'] == self._domain.transport.tid:
   320             info['mail_location'] = MailLocation(mid=self._mid).mail_location
   319                 info['transport'] = self._domain.transport.transport
   321             info['transport'] = self._transport.transport
   320             else:
   322             info['uid'] = self._uid
   321                 info['transport'] = Transport(self._dbh,
       
   322                                               tid=info['transport']).transport
       
   323             del info['mid']
       
   324             return info
   323             return info
   325         # nearly impossibleā€½
   324         # nearly impossibleā€½
   326         raise AErr(_(u"Couldn't fetch information for account: '%s'") \
   325         raise AErr(_(u"Couldn't fetch information for account: '%s'") \
   327                    % self._addr, NO_SUCH_ACCOUNT)
   326                    % self._addr, NO_SUCH_ACCOUNT)
   328 
   327 
   339         aliases = []
   338         aliases = []
   340         if addresses:
   339         if addresses:
   341             aliases = [alias[0] for alias in addresses]
   340             aliases = [alias[0] for alias in addresses]
   342         return aliases
   341         return aliases
   343 
   342 
   344     def delete(self, delalias):
   343     def delete(self, delalias=False):
   345         """Delete the Account from the database.
   344         """Delete the Account from the database.
   346 
   345 
   347         Argument:
   346         Argument:
   348 
   347 
   349         `delalias` : basestring
   348         `delalias` : bool
   350           if the values of delalias is 'delalias', all aliases, which
   349           if *delalias* is `True`, all aliases, which points to the Account,
   351           points to the Account, will be also deleted."""
   350           will be also deleted.  If there are aliases and *delalias* is
       
   351           `False`, an AccountError will be raised.
       
   352         """
       
   353         assert isinstance(delalias, bool)
   352         self._chk_state()
   354         self._chk_state()
   353         dbc = self._dbh.cursor()
   355         dbc = self._dbh.cursor()
   354         if delalias == 'delalias':
   356         if delalias:
   355             dbc.execute('DELETE FROM users WHERE uid= %s', self._uid)
   357             dbc.execute('DELETE FROM users WHERE uid = %s', self._uid)
   356             # delete also all aliases where the destination address is the same
   358             # delete also all aliases where the destination address is the same
   357             # as for this account.
   359             # as for this account.
   358             dbc.execute("DELETE FROM alias WHERE destination = %s",
   360             dbc.execute("DELETE FROM alias WHERE destination = %s",
   359                         str(self._addr))
   361                         str(self._addr))
   360             self._dbh.commit()
   362             self._dbh.commit()
   361         else:  # check first for aliases
   363         else:  # check first for aliases
   362             a_count = self._count_aliases()
   364             a_count = self._count_aliases()
   363             if a_count == 0:
   365             if a_count > 0:
   364                 dbc.execute('DELETE FROM users WHERE uid = %s', self._uid)
       
   365                 self._dbh.commit()
       
   366             else:
       
   367                 dbc.close()
   366                 dbc.close()
   368                 raise AErr(_(u"There are %(count)d aliases with the \
   367                 raise AErr(_(u"There are %(count)d aliases with the \
   369 destination address '%(address)s'.") % \
   368 destination address '%(address)s'.") % \
   370                            {'count': a_count, 'address': self._addr},
   369                            {'count': a_count, 'address': self._addr},
   371                            ALIAS_PRESENT)
   370                            ALIAS_PRESENT)
   372         dbc.close()
   371             dbc.execute('DELETE FROM users WHERE uid = %s', self._uid)
       
   372             self._dbh.commit()
       
   373         dbc.close()
       
   374         self._new = True
       
   375         self._uid = self._mid = 0
       
   376         self._addr = self._dbh = self._domain = self._passwd = None
       
   377         self._transport = None
   373 
   378 
   374 
   379 
   375 def get_account_by_uid(uid, dbh):
   380 def get_account_by_uid(uid, dbh):
   376     """Search an Account by its UID.
   381     """Search an Account by its UID.
   377 
   382 
   401         raise AErr(_(u"There is no account with the UID '%d'.") % uid,
   406         raise AErr(_(u"There is no account with the UID '%d'.") % uid,
   402                    NO_SUCH_ACCOUNT)
   407                    NO_SUCH_ACCOUNT)
   403     info = dict(zip(('address', 'uid', 'gid'), info))
   408     info = dict(zip(('address', 'uid', 'gid'), info))
   404     return info
   409     return info
   405 
   410 
   406 
   411 del _, cfg_dget
   407 del _