21 from shutil import rmtree |
20 from shutil import rmtree |
22 from subprocess import Popen, PIPE |
21 from subprocess import Popen, PIPE |
23 |
22 |
24 from pyPgSQL import PgSQL # python-pgsql - http://pypgsql.sourceforge.net |
23 from pyPgSQL import PgSQL # python-pgsql - http://pypgsql.sourceforge.net |
25 |
24 |
26 from Exceptions import * |
|
27 import constants.ERROR as ERR |
25 import constants.ERROR as ERR |
28 from Config import Config as Cfg |
|
29 from Account import Account |
26 from Account import Account |
30 from Alias import Alias |
27 from Alias import Alias |
|
28 from AliasDomain import AliasDomain |
|
29 from Config import Config as Cfg |
31 from Domain import Domain |
30 from Domain import Domain |
32 from AliasDomain import AliasDomain |
31 from EmailAddress import EmailAddress |
|
32 from Exceptions import * |
|
33 from Relocated import Relocated |
33 |
34 |
34 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' |
35 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' |
35 RE_ASCII_CHARS = """^[\x20-\x7E]*$""" |
36 RE_ASCII_CHARS = """^[\x20-\x7E]*$""" |
36 RE_DOMAIN = """^(?:[a-z0-9-]{1,63}\.){1,}[a-z]{2,6}$""" |
37 RE_DOMAIN = """^(?:[a-z0-9-]{1,63}\.){1,}[a-z]{2,6}$""" |
37 RE_DOMAIN_SRCH = """^[a-z0-9-\.]+$""" |
38 RE_DOMAIN_SRCH = """^[a-z0-9-\.]+$""" |
112 dbc.execute("SET NAMES 'UTF8'") |
113 dbc.execute("SET NAMES 'UTF8'") |
113 dbc.close() |
114 dbc.close() |
114 except PgSQL.libpq.DatabaseError, e: |
115 except PgSQL.libpq.DatabaseError, e: |
115 raise VMMException(str(e), ERR.DATABASE_ERROR) |
116 raise VMMException(str(e), ERR.DATABASE_ERROR) |
116 |
117 |
117 def chkLocalpart(localpart): |
|
118 """Validates the local part of an e-mail address. |
|
119 |
|
120 Keyword arguments: |
|
121 localpart -- the e-mail address that should be validated (str) |
|
122 """ |
|
123 if len(localpart) < 1: |
|
124 raise VMMException(_(u'No localpart specified.'), |
|
125 ERR.LOCALPART_INVALID) |
|
126 if len(localpart) > 64: |
|
127 raise VMMException(_(u'The local part »%s« is too long') % |
|
128 localpart, ERR.LOCALPART_TOO_LONG) |
|
129 ic = re.compile(RE_LOCALPART).findall(localpart) |
|
130 if len(ic): |
|
131 ichrs = '' |
|
132 for c in set(ic): |
|
133 ichrs += u"»%s« " % c |
|
134 raise VMMException(_(u"The local part »%(lpart)s« contains invalid\ |
|
135 characters: %(ichrs)s") % {'lpart': localpart, 'ichrs': ichrs}, |
|
136 ERR.LOCALPART_INVALID) |
|
137 return localpart |
|
138 chkLocalpart = staticmethod(chkLocalpart) |
|
139 |
|
140 def idn2ascii(domainname): |
118 def idn2ascii(domainname): |
141 """Converts an idn domainname in punycode. |
119 """Converts an idn domainname in punycode. |
142 |
120 |
143 Keyword arguments: |
121 Keyword arguments: |
144 domainname -- the domainname to convert (str) |
122 domainname -- the domainname to convert (str) |
177 if len(domainname) > 255: |
155 if len(domainname) > 255: |
178 raise VMMException(_(u'The domain name is too long.'), |
156 raise VMMException(_(u'The domain name is too long.'), |
179 ERR.DOMAIN_TOO_LONG) |
157 ERR.DOMAIN_TOO_LONG) |
180 re.compile(RE_DOMAIN) |
158 re.compile(RE_DOMAIN) |
181 if not re.match(RE_DOMAIN, domainname): |
159 if not re.match(RE_DOMAIN, domainname): |
182 raise VMMException(_(u'The domain name is invalid.'), |
160 raise VMMException(_(u'The domain name »%s« is invalid.') %\ |
183 ERR.DOMAIN_INVALID) |
161 domainname, ERR.DOMAIN_INVALID) |
184 return domainname |
162 return domainname |
185 chkDomainname = staticmethod(chkDomainname) |
163 chkDomainname = staticmethod(chkDomainname) |
186 |
164 |
187 def chkEmailAddress(address): |
165 def _exists(dbh, query): |
188 try: |
166 dbc = dbh.cursor() |
189 localpart, domain = address.split('@') |
167 dbc.execute(query) |
190 except ValueError: |
168 gid = dbc.fetchone() |
191 raise VMMException(_(u"Missing '@' sign in e-mail address »%s«.") % |
169 dbc.close() |
192 address, ERR.INVALID_ADDRESS) |
170 if gid is None: |
193 except AttributeError: |
171 return False |
194 raise VMMException(_(u"»%s« looks not like an e-mail address.") % |
172 else: |
195 address, ERR.INVALID_ADDRESS) |
173 return True |
196 if len(domain) > 0: |
174 _exists = staticmethod(_exists) |
197 domain = VirtualMailManager.chkDomainname(domain) |
175 |
198 else: |
176 def accountExists(dbh, address): |
199 raise VMMException(_(u"Missing domain name after »%s@«.") % |
177 sql = "SELECT gid FROM users WHERE gid = (SELECT gid FROM domain_name\ |
200 localpart, ERR.DOMAIN_NO_NAME) |
178 WHERE domainname = '%(_domainname)s') AND local_part = '%(_localpart)s'" %\ |
201 localpart = VirtualMailManager.chkLocalpart(localpart) |
179 address.__dict__ |
202 return '%s@%s' % (localpart, domain) |
180 return VirtualMailManager._exists(dbh, sql) |
203 chkEmailAddress = staticmethod(chkEmailAddress) |
181 accountExists = staticmethod(accountExists) |
|
182 |
|
183 def aliasExists(dbh, address): |
|
184 sql = "SELECT DISTINCT gid FROM alias WHERE gid = (SELECT gid FROM\ |
|
185 domain_name WHERE domainname = '%(_domainname)s') AND address =\ |
|
186 '%(_localpart)s'" % address.__dict__ |
|
187 return VirtualMailManager._exists(dbh, sql) |
|
188 aliasExists = staticmethod(aliasExists) |
|
189 |
|
190 def relocatedExists(dbh, address): |
|
191 sql = "SELECT gid FROM relocated WHERE gid = (SELECT gid FROM\ |
|
192 domain_name WHERE domainname = '%(_domainname)s') AND address =\ |
|
193 '%(_localpart)s'" % address.__dict__ |
|
194 return VirtualMailManager._exists(dbh, sql) |
|
195 relocatedExists = staticmethod(relocatedExists) |
204 |
196 |
205 def __getAccount(self, address, password=None): |
197 def __getAccount(self, address, password=None): |
206 self.__dbConnect() |
198 self.__dbConnect() |
|
199 address = EmailAddress(address) |
207 if not password is None: |
200 if not password is None: |
208 password = self.__pwhash(password) |
201 password = self.__pwhash(password) |
209 return Account(self.__dbh, address, password) |
202 return Account(self.__dbh, address, password) |
210 |
203 |
211 def _readpass(self): |
204 def _readpass(self): |
212 clear0 = '' |
205 mismatched = True |
213 clear1 = '1' |
206 while mismatched: |
214 while clear0 != clear1: |
207 clear0 = getpass(prompt=_('Enter new password: ')) |
215 while len(clear0) < 1: |
|
216 clear0 = getpass(prompt=_('Enter new password: ')) |
|
217 if len(clear0) < 1: |
|
218 sys.stderr.write('%s\n' |
|
219 % _('Sorry, empty passwords are not permitted')) |
|
220 clear1 = getpass(prompt=_('Retype new password: ')) |
208 clear1 = getpass(prompt=_('Retype new password: ')) |
221 if clear0 != clear1: |
209 if clear0 != clear1: |
222 clear0 = '' |
|
223 sys.stderr.write('%s\n' % _('Sorry, passwords do not match')) |
210 sys.stderr.write('%s\n' % _('Sorry, passwords do not match')) |
|
211 continue |
|
212 if len(clear0) < 1 or len(clear1) < 1: |
|
213 sys.stderr.write('%s\n' |
|
214 % _('Sorry, empty passwords are not permitted')) |
|
215 continue |
|
216 mismatched = False |
224 return clear0 |
217 return clear0 |
225 |
218 |
226 def __getAlias(self, address, destination=None): |
219 def __getAlias(self, address, destination=None): |
227 self.__dbConnect() |
220 self.__dbConnect() |
|
221 address = EmailAddress(address) |
|
222 if destination is not None: |
|
223 destination = EmailAddress(destination) |
228 return Alias(self.__dbh, address, destination) |
224 return Alias(self.__dbh, address, destination) |
229 |
225 |
230 def __getDomain(self, domainname, transport=None): |
226 def __getDomain(self, domainname, transport=None): |
231 if transport is None: |
227 if transport is None: |
232 transport = self.__Cfg.get('misc', 'transport') |
228 transport = self.__Cfg.get('misc', 'transport') |