18 _ = lambda x: x |
23 _ = lambda x: x |
19 |
24 |
20 |
25 |
21 class Domain(object): |
26 class Domain(object): |
22 """Class to manage e-mail domains.""" |
27 """Class to manage e-mail domains.""" |
23 __slots__ = ('_basedir', '_domaindir', '_id', '_name', '_transport', |
28 __slots__ = ('_directory', '_gid', '_name', '_transport', '_dbh', '_new') |
24 '_dbh') |
29 |
25 |
30 def __init__(self, dbh, domainname): |
26 def __init__(self, dbh, domainname, basedir=None, transport=None): |
|
27 """Creates a new Domain instance. |
31 """Creates a new Domain instance. |
28 |
32 |
29 Keyword arguments: |
33 Loads all relevant data from the database, if the domain could be |
30 dbh -- a pyPgSQL.PgSQL.connection |
34 found. To create a new domain call the methods set_directory() and |
31 domainname -- name of the domain (str) |
35 set_transport() before save(). |
32 transport -- default vmm.cfg/misc/transport (str) |
36 |
33 """ |
37 A DomainError will be thrown when the *domainname* is the name of |
|
38 an alias domain. |
|
39 |
|
40 Arguments: |
|
41 |
|
42 `dbh` : pyPgSQL.PgSQL.Connection |
|
43 a database connection for the database access |
|
44 `domainname` : basestring |
|
45 The name of the domain |
|
46 """ |
|
47 self._name = check_domainname(domainname) |
34 self._dbh = dbh |
48 self._dbh = dbh |
35 self._name = check_domainname(domainname) |
49 self._gid = 0 |
36 self._basedir = basedir |
50 self._transport = None |
37 if transport is not None: |
51 self._directory = None |
38 self._transport = Transport(self._dbh, transport=transport) |
52 self._new = True |
39 else: |
53 self._load() |
40 self._transport = transport |
54 |
41 self._id = 0 |
55 def _load(self): |
42 self._domaindir = None |
56 """Load information from the database and checks if the domain name |
43 if not self._exists() and self._isAlias(): |
57 is the primary one. |
44 raise DomErr(_(u"The domain “%s” is an alias domain.") % |
58 |
45 self._name, DOMAIN_ALIAS_EXISTS) |
59 Raises a DomainError if Domain._name isn't the primary name of the |
46 |
60 domain. |
47 def _exists(self): |
61 """ |
48 """Checks if the domain already exists. |
62 dbc = self._dbh.cursor() |
49 |
63 dbc.execute('SELECT dd.gid, tid, domaindir, is_primary FROM \ |
50 If the domain exists _id will be set and returns True, otherwise False |
64 domain_data dd, domain_name dn WHERE domainname = %s AND dn.gid = dd.gid', |
51 will be returned. |
65 self._name) |
52 """ |
|
53 dbc = self._dbh.cursor() |
|
54 dbc.execute("SELECT gid, tid, domaindir FROM domain_data WHERE gid =\ |
|
55 (SELECT gid FROM domain_name WHERE domainname = %s AND is_primary)", |
|
56 self._name) |
|
57 result = dbc.fetchone() |
66 result = dbc.fetchone() |
58 dbc.close() |
67 dbc.close() |
59 if result is not None: |
68 if result: |
60 self._id, self._domaindir = result[0], result[2] |
69 if not result[3]: |
|
70 raise DomErr(_(u"The domain '%s' is an alias domain.") % |
|
71 self._name, DOMAIN_ALIAS_EXISTS) |
|
72 self._gid, self._directory = result[0], result[2] |
61 self._transport = Transport(self._dbh, tid=result[1]) |
73 self._transport = Transport(self._dbh, tid=result[1]) |
62 return True |
74 self._new = False |
63 else: |
75 |
64 return False |
76 def _set_gid(self): |
65 |
77 """Sets the ID of the domain - if not set yet.""" |
66 def _isAlias(self): |
78 assert self._gid == 0 |
67 """Checks if self._name is known for an alias domain.""" |
|
68 dbc = self._dbh.cursor() |
|
69 dbc.execute('SELECT is_primary FROM domain_name WHERE domainname = %s', |
|
70 self._name) |
|
71 result = dbc.fetchone() |
|
72 dbc.close() |
|
73 if result is not None and not result[0]: |
|
74 return True |
|
75 else: |
|
76 return False |
|
77 |
|
78 def _setID(self): |
|
79 """Sets the ID of the domain.""" |
|
80 dbc = self._dbh.cursor() |
79 dbc = self._dbh.cursor() |
81 dbc.execute("SELECT nextval('domain_gid')") |
80 dbc.execute("SELECT nextval('domain_gid')") |
82 self._id = dbc.fetchone()[0] |
81 self._gid = dbc.fetchone()[0] |
83 dbc.close() |
82 dbc.close() |
84 |
|
85 def _prepare(self): |
|
86 self._setID() |
|
87 self._domaindir = "%s/%s/%i" % (self._basedir, choice(MAILDIR_CHARS), |
|
88 self._id) |
|
89 |
83 |
90 def _has(self, what): |
84 def _has(self, what): |
91 """Checks if aliases or accounts are assigned to the domain. |
85 """Checks if aliases or accounts are assigned to the domain. |
92 |
86 |
93 If there are assigned accounts or aliases True will be returned, |
87 If there are assigned accounts or aliases True will be returned, |
94 otherwise False will be returned. |
88 otherwise False will be returned. |
95 |
89 |
96 Keyword arguments: |
90 Argument: |
97 what -- 'alias' or 'users' (strings) |
91 |
98 """ |
92 `what` : basestring |
99 if what not in ['alias', 'users']: |
93 "alias" or "users" |
100 return False |
94 """ |
|
95 assert what in ('alias', 'users') |
101 dbc = self._dbh.cursor() |
96 dbc = self._dbh.cursor() |
102 if what == 'users': |
97 if what == 'users': |
103 dbc.execute("SELECT count(gid) FROM users WHERE gid=%s", self._id) |
98 dbc.execute("SELECT count(gid) FROM users WHERE gid=%s", self._gid) |
104 else: |
99 else: |
105 dbc.execute("SELECT count(gid) FROM alias WHERE gid=%s", self._id) |
100 dbc.execute("SELECT count(gid) FROM alias WHERE gid=%s", self._gid) |
106 count = dbc.fetchone() |
101 count = dbc.fetchone() |
107 dbc.close() |
102 dbc.close() |
108 if count[0] > 0: |
103 return count[0] > 0 |
109 return True |
104 |
|
105 def _chk_delete(self, deluser, delalias): |
|
106 """Checks dependencies for deletion. |
|
107 |
|
108 Arguments: |
|
109 deluser -- ignore available accounts (bool) |
|
110 delalias -- ignore available aliases (bool) |
|
111 """ |
|
112 if not deluser: |
|
113 hasuser = self._has('users') |
110 else: |
114 else: |
111 return False |
115 hasuser = False |
112 |
116 if not delalias: |
113 def _chkDelete(self, delUser, delAlias): |
117 hasalias = self._has('alias') |
114 """Checks dependencies for deletion. |
|
115 |
|
116 Keyword arguments: |
|
117 delUser -- ignore available accounts (bool) |
|
118 delAlias -- ignore available aliases (bool) |
|
119 """ |
|
120 if not delUser: |
|
121 hasUser = self._has('users') |
|
122 else: |
118 else: |
123 hasUser = False |
119 hasalias = False |
124 if not delAlias: |
120 if hasuser and hasalias: |
125 hasAlias = self._has('alias') |
|
126 else: |
|
127 hasAlias = False |
|
128 if hasUser and hasAlias: |
|
129 raise DomErr(_(u'There are accounts and aliases.'), |
121 raise DomErr(_(u'There are accounts and aliases.'), |
130 ACCOUNT_AND_ALIAS_PRESENT) |
122 ACCOUNT_AND_ALIAS_PRESENT) |
131 elif hasUser: |
123 elif hasuser: |
132 raise DomErr(_(u'There are accounts.'), ACCOUNT_PRESENT) |
124 raise DomErr(_(u'There are accounts.'), ACCOUNT_PRESENT) |
133 elif hasAlias: |
125 elif hasalias: |
134 raise DomErr(_(u'There are aliases.'), ALIAS_PRESENT) |
126 raise DomErr(_(u'There are aliases.'), ALIAS_PRESENT) |
|
127 |
|
128 def _chk_state(self): |
|
129 """Throws a DomainError if the Domain is new - not saved in the |
|
130 database.""" |
|
131 if self._new: |
|
132 raise DomErr(_(u"The domain '%s' doesn't exist.") % self._name, |
|
133 NO_SUCH_DOMAIN) |
|
134 |
|
135 @property |
|
136 def gid(self): |
|
137 """The GID of the Domain.""" |
|
138 return self._gid |
|
139 |
|
140 @property |
|
141 def name(self): |
|
142 """The Domain's name.""" |
|
143 return self._name |
|
144 |
|
145 @property |
|
146 def directory(self): |
|
147 """The Domain's directory.""" |
|
148 return self._directory |
|
149 |
|
150 def set_directory(self, basedir): |
|
151 """Set the path value of the Domain's directory, inside *basedir*. |
|
152 |
|
153 Argument: |
|
154 |
|
155 `basedir` : basestring |
|
156 The base directory of all domains |
|
157 """ |
|
158 assert self._new and self._directory is None |
|
159 self._set_gid() |
|
160 self._directory = os.path.join(basedir, choice(MAILDIR_CHARS), |
|
161 str(self._gid)) |
|
162 |
|
163 @property |
|
164 def transport(self): |
|
165 """The Domain's transport.""" |
|
166 return self._transport |
|
167 |
|
168 def set_transport(self, transport): |
|
169 """Set the transport for the new Domain. |
|
170 |
|
171 Argument: |
|
172 |
|
173 `transport` : VirtualMailManager.Transport |
|
174 The transport of the new Domain |
|
175 """ |
|
176 assert self._new and isinstance(transport, Transport) |
|
177 self._transport = transport |
135 |
178 |
136 def save(self): |
179 def save(self): |
137 """Stores the new domain in the database.""" |
180 """Stores the new domain in the database.""" |
138 if self._id < 1: |
181 if not self._new: |
139 self._prepare() |
182 raise DomErr(_(u"The domain '%s' already exists.") % self._name, |
140 dbc = self._dbh.cursor() |
183 DOMAIN_EXISTS) |
141 dbc.execute("INSERT INTO domain_data (gid, tid, domaindir)\ |
184 assert self._directory is not None and self._transport is not None |
142 VALUES (%s, %s, %s)", self._id, self._transport.id, self._domaindir) |
185 dbc = self._dbh.cursor() |
143 dbc.execute("INSERT INTO domain_name (domainname, gid, is_primary)\ |
186 dbc.execute("INSERT INTO domain_data VALUES (%s, %s, %s)", self._gid, |
144 VALUES (%s, %s, %s)", self._name, self._id, True) |
187 self._transport.tid, self._directory) |
|
188 dbc.execute("INSERT INTO domain_name VALUES (%s, %s, %s)", self._name, |
|
189 self._gid, True) |
|
190 self._dbh.commit() |
|
191 dbc.close() |
|
192 self._new = False |
|
193 |
|
194 def delete(self, deluser=False, delalias=False): |
|
195 """Deletes the domain. |
|
196 |
|
197 Arguments: |
|
198 |
|
199 `deluser` : bool |
|
200 force deletion of all available accounts, default `False` |
|
201 `delalias` : bool |
|
202 force deletion of all available aliases, default `False` |
|
203 """ |
|
204 self._chk_state() |
|
205 self._chk_delete(deluser, delalias) |
|
206 dbc = self._dbh.cursor() |
|
207 for tbl in ('alias', 'users', 'relocated', 'domain_name', |
|
208 'domain_data'): |
|
209 dbc.execute("DELETE FROM %s WHERE gid = %d" % (tbl, self._gid)) |
|
210 self._dbh.commit() |
|
211 dbc.close() |
|
212 self._gid = 0 |
|
213 self._directory = self._transport = None |
|
214 self._new = True |
|
215 |
|
216 def update_transport(self, transport, force=False): |
|
217 """Sets a new transport for the Domain. |
|
218 |
|
219 If *force* is `True` the new *transport* will be assigned to all |
|
220 existing accounts. Otherwise the *transport* will be only used for |
|
221 accounts created from now on. |
|
222 |
|
223 Arguments: |
|
224 |
|
225 `transport` : VirtualMailManager.Transport |
|
226 the new transport |
|
227 `force` : bool |
|
228 enforce new transport setting for all accounts, default `False` |
|
229 """ |
|
230 self._chk_state() |
|
231 assert isinstance(transport, Transport) |
|
232 if transport == self._transport: |
|
233 return |
|
234 dbc = self._dbh.cursor() |
|
235 dbc.execute("UPDATE domain_data SET tid = %s WHERE gid = %s", |
|
236 transport.tid, self._gid) |
|
237 if dbc.rowcount > 0: |
145 self._dbh.commit() |
238 self._dbh.commit() |
146 dbc.close() |
239 if force: |
147 else: |
240 dbc.execute("UPDATE users SET tid = %s WHERE gid = %s", |
148 raise DomErr(_(u'The domain “%s” already exists.') % self._name, |
241 transport.tid, self._gid) |
149 DOMAIN_EXISTS) |
|
150 |
|
151 def delete(self, delUser=False, delAlias=False): |
|
152 """Deletes the domain. |
|
153 |
|
154 Keyword arguments: |
|
155 delUser -- force deletion of available accounts (bool) |
|
156 delAlias -- force deletion of available aliases (bool) |
|
157 """ |
|
158 if self._id > 0: |
|
159 self._chkDelete(delUser, delAlias) |
|
160 dbc = self._dbh.cursor() |
|
161 for tbl in ('alias', 'users', 'relocated', 'domain_name', |
|
162 'domain_data'): |
|
163 dbc.execute("DELETE FROM %s WHERE gid = %d" % (tbl, self._id)) |
|
164 self._dbh.commit() |
|
165 dbc.close() |
|
166 else: |
|
167 raise DomErr(_(u"The domain “%s” doesn't exist.") % self._name, |
|
168 NO_SUCH_DOMAIN) |
|
169 |
|
170 def updateTransport(self, transport, force=False): |
|
171 """Sets a new transport for the domain. |
|
172 |
|
173 Keyword arguments: |
|
174 transport -- the new transport (str) |
|
175 force -- True/False force new transport for all accounts (bool) |
|
176 """ |
|
177 if self._id > 0: |
|
178 if transport == self._transport.transport: |
|
179 return |
|
180 trsp = Transport(self._dbh, transport=transport) |
|
181 dbc = self._dbh.cursor() |
|
182 dbc.execute("UPDATE domain_data SET tid = %s WHERE gid = %s", |
|
183 trsp.id, self._id) |
|
184 if dbc.rowcount > 0: |
242 if dbc.rowcount > 0: |
185 self._dbh.commit() |
243 self._dbh.commit() |
186 if force: |
244 dbc.close() |
187 dbc.execute("UPDATE users SET tid = %s WHERE gid = %s", |
245 self._transport = transport |
188 trsp.id, self._id) |
246 |
189 if dbc.rowcount > 0: |
247 def get_info(self): |
190 self._dbh.commit() |
|
191 dbc.close() |
|
192 else: |
|
193 raise DomErr(_(u"The domain “%s” doesn't exist.") % self._name, |
|
194 NO_SUCH_DOMAIN) |
|
195 |
|
196 def getID(self): |
|
197 """Returns the ID of the domain.""" |
|
198 return self._id |
|
199 |
|
200 def getDir(self): |
|
201 """Returns the directory of the domain.""" |
|
202 return self._domaindir |
|
203 |
|
204 def getTransport(self): |
|
205 """Returns domain's transport.""" |
|
206 return self._transport.transport |
|
207 |
|
208 def getTransportID(self): |
|
209 """Returns the ID from the domain's transport.""" |
|
210 return self._transport.id |
|
211 |
|
212 def getInfo(self): |
|
213 """Returns a dictionary with information about the domain.""" |
248 """Returns a dictionary with information about the domain.""" |
214 sql = """\ |
249 self._chk_state() |
215 SELECT gid, domainname, transport, domaindir, aliasdomains, accounts, |
250 sql = """SELECT gid, domainname, transport, domaindir, aliasdomains, |
216 aliases, relocated |
251 accounts, aliases, relocated |
217 FROM vmm_domain_info |
252 FROM vmm_domain_info |
218 WHERE gid = %i""" % self._id |
253 WHERE gid = %i""" % self._gid |
219 dbc = self._dbh.cursor() |
254 dbc = self._dbh.cursor() |
220 dbc.execute(sql) |
255 dbc.execute(sql) |
221 info = dbc.fetchone() |
256 info = dbc.fetchone() |
222 dbc.close() |
257 dbc.close() |
223 if info is None: |
258 keys = ('gid', 'domainname', 'transport', 'domaindir', 'aliasdomains', |
224 raise DomErr(_(u"The domain “%s” doesn't exist.") % self._name, |
259 'accounts', 'aliases', 'relocated') |
225 NO_SUCH_DOMAIN) |
260 return dict(zip(keys, info)) |
226 else: |
261 |
227 keys = ['gid', 'domainname', 'transport', 'domaindir', |
262 def get_accounts(self): |
228 'aliasdomains', 'accounts', 'aliases', 'relocated'] |
263 """Returns a list with all accounts of the domain.""" |
229 return dict(zip(keys, info)) |
264 self._chk_state() |
230 |
|
231 def getAccounts(self): |
|
232 """Returns a list with all accounts from the domain.""" |
|
233 dbc = self._dbh.cursor() |
265 dbc = self._dbh.cursor() |
234 dbc.execute("SELECT local_part from users where gid = %s ORDER BY\ |
266 dbc.execute("SELECT local_part from users where gid = %s ORDER BY\ |
235 local_part", self._id) |
267 local_part", self._gid) |
236 users = dbc.fetchall() |
268 users = dbc.fetchall() |
237 dbc.close() |
269 dbc.close() |
238 accounts = [] |
270 accounts = [] |
239 if len(users) > 0: |
271 if len(users) > 0: |
240 addr = u'@'.join |
272 addr = u'@'.join |
241 _dom = self._name |
273 _dom = self._name |
242 accounts = [addr((account[0], _dom)) for account in users] |
274 accounts = [addr((account[0], _dom)) for account in users] |
243 return accounts |
275 return accounts |
244 |
276 |
245 def getAliases(self): |
277 def get_aliases(self): |
246 """Returns a list with all aliases from the domain.""" |
278 """Returns a list with all aliases e-mail addresses of the domain.""" |
|
279 self._chk_state() |
247 dbc = self._dbh.cursor() |
280 dbc = self._dbh.cursor() |
248 dbc.execute("SELECT DISTINCT address FROM alias WHERE gid = %s\ |
281 dbc.execute("SELECT DISTINCT address FROM alias WHERE gid = %s\ |
249 ORDER BY address", self._id) |
282 ORDER BY address", self._gid) |
250 addresses = dbc.fetchall() |
283 addresses = dbc.fetchall() |
251 dbc.close() |
284 dbc.close() |
252 aliases = [] |
285 aliases = [] |
253 if len(addresses) > 0: |
286 if addresses: |
254 addr = u'@'.join |
287 addr = u'@'.join |
255 _dom = self._name |
288 _dom = self._name |
256 aliases = [addr((alias[0], _dom)) for alias in addresses] |
289 aliases = [addr((alias[0], _dom)) for alias in addresses] |
257 return aliases |
290 return aliases |
258 |
291 |
259 def getRelocated(self): |
292 def get_relocated(self): |
260 """Returns a list with all addresses from relocated users.""" |
293 """Returns a list with all addresses of relocated users.""" |
|
294 self._chk_state() |
261 dbc = self._dbh.cursor() |
295 dbc = self._dbh.cursor() |
262 dbc.execute("SELECT address FROM relocated WHERE gid = %s\ |
296 dbc.execute("SELECT address FROM relocated WHERE gid = %s\ |
263 ORDER BY address", self._id) |
297 ORDER BY address", self._gid) |
264 addresses = dbc.fetchall() |
298 addresses = dbc.fetchall() |
265 dbc.close() |
299 dbc.close() |
266 relocated = [] |
300 relocated = [] |
267 if len(addresses) > 0: |
301 if addresses: |
268 addr = u'@'.join |
302 addr = u'@'.join |
269 _dom = self._name |
303 _dom = self._name |
270 relocated = [addr((address[0], _dom)) for address in addresses] |
304 relocated = [addr((address[0], _dom)) for address in addresses] |
271 return relocated |
305 return relocated |
272 |
306 |
273 def getAliaseNames(self): |
307 def get_aliase_names(self): |
274 """Returns a list with all alias names from the domain.""" |
308 """Returns a list with all alias domain names of the domain.""" |
|
309 self._chk_state() |
275 dbc = self._dbh.cursor() |
310 dbc = self._dbh.cursor() |
276 dbc.execute("SELECT domainname FROM domain_name WHERE gid = %s\ |
311 dbc.execute("SELECT domainname FROM domain_name WHERE gid = %s\ |
277 AND NOT is_primary ORDER BY domainname", self._id) |
312 AND NOT is_primary ORDER BY domainname", self._gid) |
278 anames = dbc.fetchall() |
313 anames = dbc.fetchall() |
279 dbc.close() |
314 dbc.close() |
280 aliasdomains = [] |
315 aliasdomains = [] |
281 if len(anames) > 0: |
316 if anames: |
282 aliasdomains = [aname[0] for aname in anames] |
317 aliasdomains = [aname[0] for aname in anames] |
283 return aliasdomains |
318 return aliasdomains |
284 |
319 |
285 |
320 |
286 def search(dbh, pattern=None, like=False): |
321 def search(dbh, pattern=None, like=False): |
|
322 """'Search' for domains by *pattern* in the database. |
|
323 |
|
324 *pattern* may be a domain name or a partial domain name - starting |
|
325 and/or ending with a '%' sign. When the *pattern* starts or ends with |
|
326 a '%' sign *like* has to be `True` to perform a wildcard search. |
|
327 To retrieve all available domains use the arguments' default values. |
|
328 |
|
329 This function returns a tuple with a list and a dict: (order, domains). |
|
330 The order list contains the domains' gid, alphabetical sorted by the |
|
331 primary domain name. The domains dict's keys are the gids of the |
|
332 domains. The value of item is a list. The first list element contains |
|
333 the primary domain name or `None`. The elements [1:] contains the |
|
334 names of alias domains. |
|
335 |
|
336 Arguments: |
|
337 |
|
338 `pattern` : basestring |
|
339 a (partial) domain name (starting and/or ending with a "%" sign) |
|
340 `like` : bool |
|
341 should be `True` when *pattern* starts/ends with a "%" sign |
|
342 """ |
287 if pattern is not None and like is False: |
343 if pattern is not None and like is False: |
288 pattern = check_domainname(pattern) |
344 pattern = check_domainname(pattern) |
289 sql = 'SELECT gid, domainname, is_primary FROM domain_name' |
345 sql = 'SELECT gid, domainname, is_primary FROM domain_name' |
290 if pattern is None: |
346 if pattern is None: |
291 pass |
347 pass |