diff --git a/conf.py b/conf.py index 8e5ef43..9a111b6 100755 --- a/conf.py +++ b/conf.py @@ -1,45 +1,47 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # """ Kolab configuration utility. """ +from __future__ import print_function + import logging import os import sys sys.path.append('.') from pykolab.translate import _ try: import pykolab.logger except ImportError, e: - print >> sys.stderr, _("Cannot load pykolab/logger.py:") - print >> sys.stderr, "%s" % e + print(_("Cannot load pykolab/logger.py:"), file=sys.stderr) + print("%s" % e, file=sys.stderr) sys.exit(1) import pykolab if __name__ == "__main__": pykolab = pykolab.Conf() pykolab.finalize_conf() pykolab.run() diff --git a/cyruslib.py b/cyruslib.py index 9e42f39..0edbfdb 100644 --- a/cyruslib.py +++ b/cyruslib.py @@ -1,831 +1,833 @@ # -*- coding: utf-8 -*- # # Cyruslib v0.8.5-20090401 # Copyright (C) 2007-2009 Reinaldo de Carvalho # Copyright (C) 2003-2006 Gianluigi Tiesi # Copyright (C) 2003-2006 NetFarm S.r.l. [http://www.netfarm.it] # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -# Requires python >= 2.3 +# Requires python >= 2.6 # +from __future__ import print_function + __version__ = '0.8.5' __all__ = [ 'CYRUS' ] __doc__ = """Cyrus admin wrapper Adds cyrus-specific commands to imaplib IMAP4 Class and defines new CYRUS class for cyrus imapd commands """ from sys import exit, stdout try: import imaplib import re from binascii import b2a_base64 except ImportError, e: - print e + print(e) exit(1) Commands = { 'RECONSTRUCT' : ('AUTH',), 'DUMP' : ('AUTH',), # To check admin status 'ID' : ('AUTH',), # Only one ID allowed in non auth mode 'GETANNOTATION': ('AUTH',), 'SETANNOTATION': ('AUTH',), 'XFER' : ('AUTH',) } imaplib.Commands.update(Commands) DEFAULT_SEP = '.' QUOTE = '"' DQUOTE = '""' re_ns = re.compile(r'.*\(\(\".*(\.|/)\"\)\).*') re_q0 = re.compile(r'(.*)\s\(\)') re_q = re.compile(r'(.*)\s\(STORAGE (\d+) (\d+)\)') re_mb = re.compile(r'\((.*)\)\s\".\"\s(.*)') re_url = re.compile(r'^(imaps?)://(.+?):?(\d{0,5})$') def ok(res): return res.upper().startswith('OK') def quote(text, qchar=QUOTE): return text.join([qchar, qchar]) def unquote(text, qchar=QUOTE): return ''.join(text.split(qchar)) def getflags(test): flags = [] for flag in test.split('\\'): flag = flag.strip() if len(flag): flags.append(flag) return flags ### A smart function to return an array of split strings ### and honours quoted strings def splitquote(text): data = text.split(QUOTE) if len(data) == 1: # no quotes res = data[0].split() else: res = [] for match in data: if len(match.strip()) == 0: continue if match[0] == ' ': res = res + match.strip().split() else: res.append(match) return res ### return a dictionary from a cyrus info response def res2dict(data): data = splitquote(data) datalen = len(data) if datalen % 2: # Unmatched pair return False, {} res = {} for i in range(0, datalen, 2): res[data[i]] = data[i+1] return True, res class CYRUSError(Exception): pass class IMAP4(imaplib.IMAP4): def getsep(self): """Get mailbox separator""" ### yes, ugly but cyradm does it in the same way ### also more realable then calling NAMESPACE ### and it should be also compatibile with other servers try: return unquote(self.list(DQUOTE, DQUOTE)[1][0]).split()[1] except: return DEFAULT_SEP def isadmin(self): ### A trick to check if the user is admin or not ### normal users cannot use dump command try: res, msg = self._simple_command('DUMP', 'NIL') if msg[0].lower().find('denied') == -1: return True except: pass return False def id(self): try: typ, dat = self._simple_command('ID', '("name" "PyKolab/Kolab")') res, dat = self._untagged_response(typ, dat, 'ID') except: return False, dat[0] return ok(res), dat[0] def getannotation(self, mailbox, pattern='*', shared=None): if shared == None: typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('*')) elif shared: typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('value.shared')) else: typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('value.priv')) return self._untagged_response(typ, dat, 'ANNOTATION') def setannotation(self, mailbox, desc, value, shared=False): if value: value = quote(value) else: value = "NIL" if shared: typ, dat = self._simple_command('SETANNOTATION', mailbox, quote(desc), "(%s %s)" % (quote('value.shared'), value) ) else: typ, dat = self._simple_command('SETANNOTATION', mailbox, quote(desc), "(%s %s)" % (quote('value.priv'), value) ) return self._untagged_response(typ, dat, 'ANNOTATION') def setquota(self, mailbox, limit): """Set quota of a mailbox""" if limit == 0: quota = '()' else: quota = '(STORAGE %s)' % limit return self._simple_command('SETQUOTA', mailbox, quota) ### Overridden to support partition ### Pychecker will complain about non matching signature def create(self, mailbox, partition=None): """Create a mailbox, partition is optional""" if partition is not None: return self._simple_command('CREATE', mailbox, partition) else: return self._simple_command('CREATE', mailbox) ### Overridden to support partition ### Pychecker: same here def rename(self, from_mailbox, to_mailbox, partition=None): """Rename a from_mailbox to to_mailbox, partition is optional""" if partition is not None: return self._simple_command('RENAME', from_mailbox, to_mailbox, partition) else: return self._simple_command('RENAME', from_mailbox, to_mailbox) def reconstruct(self, mailbox): return self._simple_command('RECONSTRUCT', mailbox) class IMAP4_SSL(imaplib.IMAP4_SSL): def getsep(self): """Get mailbox separator""" ### yes, ugly but cyradm does it in the same way ### also more realable then calling NAMESPACE ### and it should be also compatibile with other servers try: return unquote(self.list(DQUOTE, DQUOTE)[1][0]).split()[1] except: return DEFAULT_SEP def isadmin(self): ### A trick to check if the user is admin or not ### normal users cannot use dump command try: res, msg = self._simple_command('DUMP', 'NIL') if msg[0].lower().find('denied') == -1: return True except: pass return False def id(self): try: typ, dat = self._simple_command('ID', '("name" "PyKolab/Kolab")') res, dat = self._untagged_response(typ, dat, 'ID') except: return False, dat[0] return ok(res), dat[0] def getannotation(self, mailbox, pattern='*', shared=None): if shared == None: typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('*')) elif shared: typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('value.shared')) else: typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('value.priv')) return self._untagged_response(typ, dat, 'ANNOTATION') def setannotation(self, mailbox, desc, value, shared=False): if value: value = quote(value) else: value = "NIL" if shared: typ, dat = self._simple_command('SETANNOTATION', mailbox, quote(desc), "(%s %s)" % (quote('value.shared'), value) ) else: typ, dat = self._simple_command('SETANNOTATION', mailbox, quote(desc), "(%s %s)" % (quote('value.priv'), value) ) return self._untagged_response(typ, dat, 'ANNOTATION') def setquota(self, mailbox, limit): """Set quota of a mailbox""" if limit == 0: quota = '()' else: quota = '(STORAGE %s)' % limit return self._simple_command('SETQUOTA', mailbox, quota) ### Overridden to support partition ### Pychecker will complain about non matching signature def create(self, mailbox, partition=None): """Create a mailbox, partition is optional""" if partition is not None: return self._simple_command('CREATE', mailbox, partition) else: return self._simple_command('CREATE', mailbox) ### Overridden to support partition ### Pychecker: same here def rename(self, from_mailbox, to_mailbox, partition=None): """Rename a from_mailbox to to_mailbox, partition is optional""" if partition is not None: return self._simple_command('RENAME', from_mailbox, to_mailbox, partition) else: return self._simple_command('RENAME', from_mailbox, to_mailbox) def reconstruct(self, mailbox): return self._simple_command('RECONSTRUCT', mailbox) def login_plain(self, admin, password, asUser): if asUser: encoded = b2a_base64("%s\0%s\0%s" % (asUser, admin, password)).strip() else: encoded = b2a_base64("%s\0%s\0%s" % (admin, admin, password)).strip() res, data = self._simple_command('AUTHENTICATE', 'PLAIN', encoded) self.AUTH = True if ok(res): self.state = 'AUTH' return res, data class CYRUS: ERROR = {} ERROR["CONNECT"] = [0, "Connection error"] ERROR["INVALID_URL"] = [1, "Invalid URL"] ERROR["ENCODING"] = [3, "Invalid encondig"] ERROR["MBXNULL"] = [5, "Mailbox is Null"] ERROR["NOAUTH"] = [7, "Connection is not authenticated"] ERROR["LOGIN"] = [10, "User or password is wrong"] ERROR["ADMIN"] = [11, "User is not cyrus administrator"] ERROR["AUTH"] = [12, "Connection already authenticated"] ERROR["LOGINPLAIN"] = [15, "Encryption needed to use mechanism"] ERROR["LOGIN_PLAIN"] = [16, "User or password is wrong"] ERROR["CREATE"] = [20, "Unable create mailbox"] ERROR["DELETE"] = [25, "Unable delete mailbox"] ERROR["GETACL"] = [30, "Unable parse GETACL result"] ERROR["SETQUOTA"] = [40, "Invalid integer argument"] ERROR["GETQUOTA"] = [45, "Quota root does not exist"] ERROR["RENAME"] = [50, "Unable rename mailbox"] ERROR["RECONSTRUCT"] = [60, "Unable reconstruct mailbox"] ERROR["SUBSCRIBE"] = [70, "User is cyrus administrator, normal user required"] ERROR["UNSUBSCRIBE"] = [75, "User is cyrus administrator, normal user required"] ERROR["LSUB"] = [77, "User is cyrus administrator, normal user required"] ERROR["UNKCMD"] = [98, "Command not implemented"] ERROR["IMAPLIB"] = [99, "Generic imaplib error"] ENCODING_LIST = ['imap', 'utf-8', 'iso-8859-1'] def __init__(self, url = 'imap://localhost:143'): self.VERBOSE = False self.AUTH = False self.ADMIN = None self.AUSER = None self.ADMINACL = 'c' self.SEP = DEFAULT_SEP self.ENCODING = 'imap' self.LOGFD = stdout match = re_url.match(url) if match: host = match.group(2) if match.group(3): port = int(match.group(3)) else: port = 143 else: self.__doraise("INVALID_URL") try: if match.group(1) == 'imap': self.ssl = False self.m = IMAP4(host, port) else: self.ssl = True self.m = IMAP4_SSL(host, port) except: self.__doraise("CONNECT") def __del__(self): if self.AUTH: self.logout() def __verbose(self, msg): if self.VERBOSE: - print >> self.LOGFD, msg + print(msg, file=self.LOGFD) def __doexception(self, function, msg=None, *args): if msg is None: try: msg = self.ERROR.get(function.upper())[1] except: msg = self.ERROR.get("IMAPLIB")[1] value = "" for arg in args: if arg is not None: value = "%s %s" % (value, arg) self.__verbose( '[%s%s] %s: %s' % (function.upper(), value, "BAD", msg) ) self.__doraise( function.upper(), msg ) def __doraise(self, mode, msg=None): idError = self.ERROR.get(mode) if idError: if msg is None: msg = idError[1] else: idError = [self.ERROR.get("IMAPLIB")[0]] raise CYRUSError( idError[0], mode, msg ) def __prepare(self, command, mailbox=True): if not self.AUTH: self.__doexception(command, self.ERROR.get("NOAUTH")[1]) elif not mailbox: self.__doexception(command, self.ERROR.get("MBXNULL")[1]) def __docommand(self, function, *args): wrapped = getattr(self.m, function, None) if wrapped is None: raise self.__doraise("UNKCMD") try: res, msg = wrapped(*args) if ok(res): return res, msg except Exception, info: error = str(info).split(':').pop().strip() if error.upper().startswith('BAD'): error = error.split('BAD', 1).pop().strip() error = unquote(error[1:-1], '\'') self.__doexception(function, error, *args) self.__doexception(function, msg[0], *args) def xfer(self, mailbox, server): """Xfer a mailbox to server""" return self.m._simple_command('XFER', mailbox, server) def id(self): self.__prepare('id') res, data = self.m.id() data = data.strip() if not res or (len(data) < 3): return False, {} data = data[1:-1] # Strip () res, rdata = res2dict(data) if not res: self.__verbose( '[ID] Umatched pairs in result' ) return res, rdata def login(self, username, password): if self.AUTH: self.__doexception("LOGIN", self.ERROR.get("AUTH")[1]) try: res, msg = self.m.login(username, password) self.AUTH = True self.id() admin = self.m.isadmin() except Exception, info: self.AUTH = False error = str(info).split(':').pop().strip() self.__doexception("LOGIN", error) if admin: self.ADMIN = username self.SEP = self.m.getsep() self.__verbose( '[LOGIN %s] %s: %s' % (username, res, msg[0]) ) def login_plain(self, username, password, asUser = None): if self.AUTH: self.__doexception("LOGINPLAIN", self.ERROR.get("AUTH")[1]) if not self.ssl: self.__doexception("LOGINPLAIN", self.ERROR.get("LOGINPLAIN")[1]) res, msg = self.__docommand("login_plain", username, password, asUser) self.__verbose( '[AUTHENTICATE PLAIN %s] %s: %s' % (username, res, msg[0]) ) if ok(res): self.AUTH = True self.id() if asUser is None: if self.m.isadmin(): self.ADMIN = admin else: self.ADMIN = asUser self.AUSER = asUser self.SEP = self.m.getsep() def logout(self): try: res, msg = self.m.logout() except Exception, info: error = str(info).split(':').pop().strip() self.__doexception("LOGOUT", error) self.AUTH = False self.ADMIN = None self.AUSER = None self.__verbose( '[LOGOUT] %s: %s' % (res, msg[0]) ) def getEncoding(self): """Get current input/output codification""" return self.ENCODING def setEncoding(self, enc = None): """Set current input/output codification""" if enc is None: self.ENCODING = 'imap' elif enc in self.ENCODING_LIST: self.ENCODING = enc else: raise self.__doraise("ENCODING") def __encode(self, text): if re.search("&", text): text = re.sub("/", "+AC8-", text) text = re.sub("&", "+", text) text = unicode(text, 'utf-7').encode(self.ENCODING) return text def encode(self, text): if self.ENCODING == 'imap': return text elif self.ENCODING in self.ENCODING_LIST: return self.__encode(text) def __decode(self, text): text = re.sub("/", "-&", text) text = re.sub(" ", "-@", text) text = unicode(text, self.ENCODING).encode('utf-7') text = re.sub("-@", " ", text) text = re.sub("-&", "/", text) text = re.sub("\+", "&", text) return text def decode(self, text): if self.ENCODING == 'imap': return text elif self.ENCODING in self.ENCODING_LIST: return self.__decode(text) def lm(self, pattern="*"): """ List mailboxes, returns dict with list of mailboxes To list all mailboxes lm() To list users top mailboxes lm("user/%") To list all users mailboxes lm("user/*") To list users mailboxes startwith a word lm("user/word*") To list global top folders lm("%") To list global startwith a word unsupported by server suggestion lm("word*") """ self.__prepare('LIST') if pattern == '': pattern = "*" if pattern == '%': res, ml = self.__docommand('list', '', '%') else: res, ml = self.__docommand('list', '""', self.decode(pattern)) if not ok(res): self.__verbose( '[LIST] %s: %s' % (res, ml) ) return [] if (len(ml) == 1) and ml[0] is None: self.__verbose( '[LIST] No results' ) return [] mb = [] for mailbox in ml: res = re_mb.match(mailbox) if res is None: continue mbe = unquote(res.group(2)) if 'Noselect' in getflags(res.group(1)): continue mb.append(self.encode(mbe)) return mb def cm(self, mailbox, partition=None): """Create mailbox""" self.__prepare('CREATE', mailbox) res, msg = self.__docommand('create', self.decode(mailbox), partition) self.__verbose( '[CREATE %s partition=%s] %s: %s' % (mailbox, partition, res, msg[0]) ) def __dm(self, mailbox): if not mailbox: return True self.__docommand("setacl", self.decode(mailbox), self.ADMIN, self.ADMINACL) res, msg = self.__docommand("delete", self.decode(mailbox)) self.__verbose( '[DELETE %s] %s: %s' % (mailbox, res, msg[0]) ) def dm(self, mailbox, recursive=True): """Delete mailbox""" self.__prepare('DELETE', mailbox) mbxTmp = mailbox.split(self.SEP) # Cyrus is not recursive for user subfolders and global folders if (recursive and mbxTmp[0] != "user") or (len(mbxTmp) > 2): mbxList = self.lm("%s%s*" % (mailbox, self.SEP)) mbxList.reverse() for mbox in mbxList: self.__dm(mbox) self.__dm(mailbox) def rename(self, fromMbx, toMbx, partition=None): """Rename or change partition""" self.__prepare('RENAME', fromMbx) # Rename is recursive! Amen! res, msg = self.__docommand("rename", self.decode(fromMbx), self.decode(toMbx), partition) self.__verbose( '[RENAME %s %s] %s: %s' % (fromMbx, toMbx, res, msg[0]) ) def lam(self, mailbox): """List ACLs""" self.__prepare('GETACL', mailbox) res, acl = self.__docommand("getacl", self.decode(mailbox)) acls = {} aclList = splitquote(acl.pop().strip()) del aclList[0] # mailbox for i in range(0, len(aclList), 2): try: userid = self.encode(aclList[i]) rights = aclList[i + 1] except Exception, info: self.__verbose( '[GETACL %s] BAD: %s' % (mailbox, info.args[0]) ) raise self.__doraise("GETACL") self.__verbose( '[GETACL %s] %s %s' % (mailbox, userid, rights) ) acls[userid] = rights return acls def sam(self, mailbox, userid, rights): """Set ACL""" self.__prepare('SETACL', mailbox) res, msg = self.__docommand("setacl", self.decode(mailbox), userid, rights) self.__verbose( '[SETACL %s %s %s] %s: %s' % (mailbox, userid, rights, res, msg[0]) ) def lq(self, mailbox): """List Quota""" self.__prepare('GETQUOTA', mailbox) res, msg = self.__docommand("getquota", self.decode(mailbox)) match = re_q0.match(msg[0]) if match: self.__verbose( '[GETQUOTA %s] QUOTA (Unlimited)' % mailbox ) return None, None match = re_q.match(msg[0]) if match is None: self.__verbose( '[GETQUOTA %s] BAD: RegExp not matched, please report' % mailbox ) return None, None try: used = int(match.group(2)) quota = int(match.group(3)) self.__verbose( '[GETQUOTA %s] %s: QUOTA (%d/%d)' % (mailbox, res, used, quota) ) return used, quota except: self.__verbose( '[GETQUOTA %s] BAD: Error while parsing results' % mailbox ) return None, None def lqr(self, mailbox): """List Quota Root""" self.__prepare('GETQUOTAROOT', mailbox) res, msg = self.__docommand("getquotaroot", self.decode(mailbox)) (_mailbox, _root) = msg[0][0].split() match = re_q0.match(msg[1][0]) if match: self.__verbose( '[GETQUOTAROOT %s] QUOTAROOT (Unlimited)' % mailbox ) return _root, None, None match = re_q.match(msg[1][0]) try: used = int(match.group(2)) quota = int(match.group(3)) self.__verbose( '[GETQUOTAROOT %s] %s: QUOTA (%d/%d)' % (mailbox, res, used, quota) ) return _root, used, quota except: self.__verbose( '[GETQUOTAROOT %s] BAD: Error while parsing results' % mailbox ) return _root, None, None def sq(self, mailbox, limit): """Set Quota""" self.__prepare('SETQUOTA', mailbox) try: limit = int(limit) except ValueError, e: self.__verbose( '[SETQUOTA %s] BAD: %s %s' % (mailbox, self.ERROR.get("SETQUOTA")[1], limit) ) raise self.__doraise("SETQUOTA") res, msg = self.__docommand("setquota", self.decode(mailbox), limit) self.__verbose( '[SETQUOTA %s %s] %s: %s' % (mailbox, limit, res, msg[0]) ) def getannotation(self, mailbox, pattern='*'): """Get Annotation""" self.__prepare('GETANNOTATION') res, data = self.__docommand('getannotation', self.decode(mailbox), pattern) if (len(data) == 1) and data[0] is None: self.__verbose( '[GETANNOTATION %s] No results' % (mailbox) ) return {} ann = {} annotations = [] empty_values = [ "NIL", '" "', None, '', ' ' ] concat_items = [] for item in data: if isinstance(item, tuple): item = ' '.join([str(x) for x in item]) if len(concat_items) > 0: concat_items.append(item) if ''.join(concat_items).count('(') == ''.join(concat_items).count(')'): annotations.append(''.join(concat_items)) concat_items = [] continue else: if item.count('(') == item.count(')'): annotations.append(item) continue else: concat_items.append(item) continue for annotation in annotations: annotation = annotation.strip() if not annotation[0] == '"': folder = annotation.split('"')[0].replace('"','').strip() key = annotation.split('"')[1].replace('"','').replace("'","").strip() _annot = annotation.split('(')[1].split(')')[0].strip() else: folder = annotation.split('"')[1].replace('"','').strip() key = annotation.split('"')[3].replace('"','').replace("'","").strip() _annot = annotation.split('(')[1].split(')')[0].strip() if not ann.has_key(folder): ann[folder] = {} try: value_priv = _annot[(_annot.index('"value.priv"')+len('"value.priv"')):_annot.index('"size.priv"')].strip() except ValueError, errmsg: value_priv = None try: size_priv = _annot[(_annot.index('"size.priv"')+len('"size.priv"')):].strip().split('"')[1].strip() try: value_priv = value_priv[value_priv.index('{%s}' % (size_priv))+len('{%s}' % (size_priv)):].strip() except Exception, errmsg: pass except Exception, errmsg: pass if value_priv in empty_values: value_priv = None else: try: value_priv = value_priv[:value_priv.index('"content-type.priv"')].strip() except: pass try: value_priv = value_priv[:value_priv.index('"modifiedsince.priv"')].strip() except: pass if value_priv.startswith('"'): value_priv = value_priv[1:] if value_priv.endswith('"'): value_priv = value_priv[:-1] if value_priv in empty_values: value_priv = None try: value_shared = _annot[(_annot.index('"value.shared"')+len('"value.shared"')):_annot.index('"size.shared"')].strip() except ValueError, errmsg: value_shared = None try: size_shared = _annot[(_annot.index('"size.shared"')+len('"size.shared"')):].strip().split('"')[1].strip() try: value_shared = value_shared[value_shared.index('{%s}' % (size_shared))+len('{%s}' % (size_shared)):].strip() except Exception, errmsg: pass except Exception, errmsg: pass if value_shared in empty_values: value_shared = None else: try: value_shared = value_shared[:value_shared.index('"content-type.shared"')].strip() except: pass try: value_shared = value_shared[:value_shared.index('"modifiedsince.shared"')].strip() except: pass if value_shared.startswith('"'): value_shared = value_shared[1:] if value_shared.endswith('"'): value_shared = value_shared[:-1] if value_shared in empty_values: value_shared = None if not value_priv == None: ann[folder]['/private' + key] = value_priv if not value_shared == None: ann[folder]['/shared' + key] = value_shared return ann def setannotation(self, mailbox, annotation, value, shared=False): """Set Annotation""" self.__prepare('SETANNOTATION') res, msg = self.__docommand("setannotation", self.decode(mailbox), annotation, value, shared) self.__verbose( '[SETANNOTATION %s] %s: %s' % (mailbox, res, msg[0]) ) def __reconstruct(self, mailbox): if not mailbox: return True res, msg = self.__docommand("reconstruct", self.decode(mailbox)) self.__verbose( '[RECONSTRUCT %s] %s: %s' % (mailbox, res, msg[0]) ) def reconstruct(self, mailbox, recursive=True): """Reconstruct""" self.__prepare('RECONSTRUCT', mailbox) # Cyrus is not recursive for remote reconstruct if recursive: mbxList = self.lm("%s%s*" % (mailbox, self.SEP)) mbxList.reverse() for mbox in mbxList: self.__reconstruct(mbox) self.__reconstruct(mailbox) def lsub(self, pattern="*"): if self.AUSER is None: self.__doexception("lsub") self.__prepare('LSUB') if pattern == '': pattern = "*" res, ml = self.__docommand('lsub', '*', pattern) if not ok(res): self.__verbose( '[LIST] %s: %s' % (res, ml) ) return [] if (len(ml) == 1) and ml[0] is None: self.__verbose( '[LIST] No results' ) return [] mb = [] for mailbox in ml: res = re_mb.match(mailbox) if res is None: continue mbe = unquote(res.group(2)) if 'Noselect' in getflags(res.group(1)): continue mb.append(self.encode(mbe)) return mb def subscribe(self, mailbox): """Subscribe""" self.__prepare('SUBSCRIBE') res, msg = self.__docommand("subscribe", self.decode(mailbox)) self.__verbose( '[SUBSCRIBE %s] %s: %s' % (mailbox, res, msg[0]) ) def unsubscribe(self, mailbox): """Unsubscribe""" self.__prepare('UNSUBSCRIBE') res, msg = self.__docommand("unsubscribe", self.decode(mailbox)) self.__verbose( '[UNSUBSCRIBE %s] %s: %s' % (mailbox, res, msg[0]) ) diff --git a/ext/python/Tools/freeze/freeze.py b/ext/python/Tools/freeze/freeze.py index 46bfd90..daa345b 100755 --- a/ext/python/Tools/freeze/freeze.py +++ b/ext/python/Tools/freeze/freeze.py @@ -1,503 +1,503 @@ #! /usr/bin/env python """Freeze a Python script into a binary. usage: freeze [options...] script [module]... Options: -p prefix: This is the prefix used when you ran ``make install'' in the Python build directory. (If you never ran this, freeze won't work.) The default is whatever sys.prefix evaluates to. It can also be the top directory of the Python source tree; then -P must point to the build tree. -P exec_prefix: Like -p but this is the 'exec_prefix', used to install objects etc. The default is whatever sys.exec_prefix evaluates to, or the -p argument if given. If -p points to the Python source tree, -P must point to the build tree, if different. -e extension: A directory containing additional .o files that may be used to resolve modules. This directory should also have a Setup file describing the .o files. On Windows, the name of a .INI file describing one or more extensions is passed. More than one -e option may be given. -o dir: Directory where the output files are created; default '.'. -m: Additional arguments are module names instead of filenames. -a package=dir: Additional directories to be added to the package's __path__. Used to simulate directories added by the package at runtime (eg, by OpenGL and win32com). More than one -a option may be given for each package. -l file: Pass the file to the linker (windows only) -d: Debugging mode for the module finder. -q: Make the module finder totally quiet. -h: Print this help message. -x module Exclude the specified module. It will still be imported by the frozen binary if it exists on the host system. -X module Like -x, except the module can never be imported by the frozen binary. -E: Freeze will fail if any modules can't be found (that were not excluded using -x or -X). -i filename: Include a file with additional command line options. Used to prevent command lines growing beyond the capabilities of the shell/OS. All arguments specified in filename are read and the -i option replaced with the parsed params (note - quoting args in this file is NOT supported) -s subsystem: Specify the subsystem (For Windows only.); 'console' (default), 'windows', 'service' or 'com_dll' -w: Toggle Windows (NT or 95) behavior. (For debugging only -- on a win32 platform, win32 behavior is automatic.) -r prefix=f: Replace path prefix. Replace prefix with f in the source path references contained in the resulting binary. Arguments: script: The Python script to be executed by the resulting binary. module ...: Additional Python modules (referenced by pathname) that will be included in the resulting binary. These may be .py or .pyc files. If -m is specified, these are module names that are search in the path instead. NOTES: In order to use freeze successfully, you must have built Python and installed it ("make install"). The script should not use modules provided only as shared libraries; if it does, the resulting binary is not self-contained. """ # Import standard modules import modulefinder import getopt import os import sys # Import the freeze-private modules import checkextensions import makeconfig import makefreeze import makemakefile import parsesetup import bkfile # Main program def main(): # overridable context prefix = None # settable with -p option exec_prefix = None # settable with -P option extensions = [] exclude = [] # settable with -x option addn_link = [] # settable with -l, but only honored under Windows. path = sys.path[:] modargs = 0 debug = 1 odir = '' win = sys.platform[:3] == 'win' replace_paths = [] # settable with -r option error_if_any_missing = 0 # default the exclude list for each platform if win: exclude = exclude + [ 'dos', 'dospath', 'mac', 'macpath', 'macfs', 'MACFS', 'posix', 'os2', 'ce', 'riscos', 'riscosenviron', 'riscospath', ] fail_import = exclude[:] # output files frozen_c = 'frozen.c' config_c = 'config.c' target = 'a.out' # normally derived from script name makefile = 'Makefile' subsystem = 'console' # parse command line by first replacing any "-i" options with the # file contents. pos = 1 while pos < len(sys.argv)-1: # last option can not be "-i", so this ensures "pos+1" is in range! if sys.argv[pos] == '-i': try: options = open(sys.argv[pos+1]).read().split() except IOError, why: usage("File name '%s' specified with the -i option " "can not be read - %s" % (sys.argv[pos+1], why) ) # Replace the '-i' and the filename with the read params. sys.argv[pos:pos+2] = options pos = pos + len(options) - 1 # Skip the name and the included args. pos = pos + 1 # Now parse the command line with the extras inserted. try: opts, args = getopt.getopt(sys.argv[1:], 'r:a:dEe:hmo:p:P:b:qs:wX:x:l:') except getopt.error, msg: usage('getopt error: ' + str(msg)) # process option arguments for o, a in opts: if o == '-h': - print __doc__ + print(__doc__) return if o == '-b': binlib = a if o == '-d': debug = debug + 1 if o == '-e': extensions.append(a) if o == '-m': modargs = 1 if o == '-o': odir = a if o == '-p': prefix = a if o == '-P': exec_prefix = a if o == '-q': debug = 0 if o == '-w': win = not win if o == '-s': if not win: usage("-s subsystem option only on Windows") subsystem = a if o == '-x': exclude.append(a) if o == '-X': exclude.append(a) fail_import.append(a) if o == '-E': error_if_any_missing = 1 if o == '-l': addn_link.append(a) if o == '-a': apply(modulefinder.AddPackagePath, tuple(a.split("=", 2))) if o == '-r': f,r = a.split("=", 2) replace_paths.append( (f,r) ) # modules that are imported by the Python runtime implicits = [] for module in ('site', 'warnings',): if module not in exclude: implicits.append(module) # default prefix and exec_prefix if not exec_prefix: if prefix: exec_prefix = prefix else: exec_prefix = sys.exec_prefix if not prefix: prefix = sys.prefix # determine whether -p points to the Python source tree ishome = os.path.exists(os.path.join(prefix, 'Python', 'ceval.c')) # locations derived from options version = sys.version[:3] if win: extensions_c = 'frozen_extensions.c' if ishome: - print "(Using Python source directory)" + print("(Using Python source directory)") binlib = exec_prefix incldir = os.path.join(prefix, 'Include') config_h_dir = exec_prefix config_c_in = os.path.join(prefix, 'Modules', 'config.c.in') frozenmain_c = os.path.join(prefix, 'Python', 'frozenmain.c') makefile_in = os.path.join(exec_prefix, 'Makefile') if win: frozendllmain_c = os.path.join(exec_prefix, 'Pc\\frozen_dllmain.c') else: if not binlib: binlib = os.path.join(exec_prefix, 'lib', 'python%s' % version, 'config') else: binlib = os.path.join(binlib, 'python%s' % version, 'config') incldir = os.path.join(prefix, 'include', 'python%s' % version) config_h_dir = os.path.join(exec_prefix, 'include', 'python%s' % version) config_c_in = os.path.join(binlib, 'config.c.in') frozenmain_c = os.path.join(binlib, 'frozenmain.c') makefile_in = os.path.join(binlib, 'Makefile') frozendllmain_c = os.path.join(binlib, 'frozen_dllmain.c') supp_sources = [] defines = [] includes = ['-I' + incldir, '-I' + config_h_dir] # sanity check of directories and files check_dirs = [prefix, exec_prefix, binlib, incldir] if not win: # These are not directories on Windows. check_dirs = check_dirs + extensions for dir in check_dirs: if not os.path.exists(dir): usage('needed directory %s not found' % dir) if not os.path.isdir(dir): usage('%s: not a directory' % dir) if win: files = supp_sources + extensions # extensions are files on Windows. else: files = [config_c_in, makefile_in] + supp_sources for file in supp_sources: if not os.path.exists(file): usage('needed file %s not found' % file) if not os.path.isfile(file): usage('%s: not a plain file' % file) if not win: for dir in extensions: setup = os.path.join(dir, 'Setup') if not os.path.exists(setup): usage('needed file %s not found' % setup) if not os.path.isfile(setup): usage('%s: not a plain file' % setup) # check that enough arguments are passed if not args: usage('at least one filename argument required') # check that file arguments exist for arg in args: if arg == '-m': break # if user specified -m on the command line before _any_ # file names, then nothing should be checked (as the # very first file should be a module name) if modargs: break if not os.path.exists(arg): usage('argument %s not found' % arg) if not os.path.isfile(arg): usage('%s: not a plain file' % arg) # process non-option arguments scriptfile = args[0] modules = args[1:] # derive target name from script name base = os.path.basename(scriptfile) base, ext = os.path.splitext(base) if base: if base != scriptfile: target = base else: target = base + '.bin' # handle -o option base_frozen_c = frozen_c base_config_c = config_c base_target = target if odir and not os.path.isdir(odir): try: os.mkdir(odir) - print "Created output directory", odir + print("Created output directory", odir) except os.error, msg: usage('%s: mkdir failed (%s)' % (odir, str(msg))) base = '' if odir: base = os.path.join(odir, '') frozen_c = os.path.join(odir, frozen_c) config_c = os.path.join(odir, config_c) target = os.path.join(odir, target) makefile = os.path.join(odir, makefile) if win: extensions_c = os.path.join(odir, extensions_c) # Handle special entry point requirements # (on Windows, some frozen programs do not use __main__, but # import the module directly. Eg, DLLs, Services, etc custom_entry_point = None # Currently only used on Windows python_entry_is_main = 1 # Is the entry point called __main__? # handle -s option on Windows if win: import winmakemakefile try: custom_entry_point, python_entry_is_main = \ winmakemakefile.get_custom_entry_point(subsystem) except ValueError, why: usage(why) # Actual work starts here... # collect all modules of the program dir = os.path.dirname(scriptfile) path[0] = dir mf = modulefinder.ModuleFinder(path, debug, exclude, replace_paths) if win and subsystem=='service': # If a Windows service, then add the "built-in" module. mod = mf.add_module("servicemanager") mod.__file__="dummy.pyd" # really built-in to the resulting EXE for mod in implicits: mf.import_hook(mod) for mod in modules: if mod == '-m': modargs = 1 continue if modargs: if mod[-2:] == '.*': mf.import_hook(mod[:-2], None, ["*"]) else: mf.import_hook(mod) else: mf.load_file(mod) # Add the main script as either __main__, or the actual module name. if python_entry_is_main: mf.run_script(scriptfile) else: mf.load_file(scriptfile) if debug > 0: mf.report() - print + print() dict = mf.modules if error_if_any_missing: missing = mf.any_missing() if missing: sys.exit("There are some missing modules: %r" % missing) # generate output for frozen modules files = makefreeze.makefreeze(base, dict, debug, custom_entry_point, fail_import) # look for unfrozen modules (builtin and of unknown origin) builtins = [] unknown = [] mods = dict.keys() mods.sort() for mod in mods: if dict[mod].__code__: continue if not dict[mod].__file__: builtins.append(mod) else: unknown.append(mod) # search for unknown modules in extensions directories (not on Windows) addfiles = [] frozen_extensions = [] # Windows list of modules. if unknown or (not win and builtins): if not win: addfiles, addmods = \ checkextensions.checkextensions(unknown+builtins, extensions) for mod in addmods: if mod in unknown: unknown.remove(mod) builtins.append(mod) else: # Do the windows thang... import checkextensions_win32 # Get a list of CExtension instances, each describing a module # (including its source files) frozen_extensions = checkextensions_win32.checkextensions( unknown, extensions, prefix) for mod in frozen_extensions: unknown.remove(mod.name) # report unknown modules if unknown: sys.stderr.write('Warning: unknown modules remain: %s\n' % ' '.join(unknown)) # windows gets different treatment if win: # Taking a shortcut here... import winmakemakefile, checkextensions_win32 checkextensions_win32.write_extension_table(extensions_c, frozen_extensions) # Create a module definition for the bootstrap C code. xtras = [frozenmain_c, os.path.basename(frozen_c), frozendllmain_c, os.path.basename(extensions_c)] + files maindefn = checkextensions_win32.CExtension( '__main__', xtras ) frozen_extensions.append( maindefn ) outfp = open(makefile, 'w') try: winmakemakefile.makemakefile(outfp, locals(), frozen_extensions, os.path.basename(target)) finally: outfp.close() return # generate config.c and Makefile builtins.sort() infp = open(config_c_in) outfp = bkfile.open(config_c, 'w') try: makeconfig.makeconfig(infp, outfp, builtins) finally: outfp.close() infp.close() cflags = ['$(OPT)'] cppflags = defines + includes libs = [os.path.join(binlib, 'libpython$(VERSION).so')] somevars = {} if os.path.exists(makefile_in): makevars = parsesetup.getmakevars(makefile_in) for key in makevars.keys(): somevars[key] = makevars[key] somevars['CFLAGS'] = ' '.join(cflags) # override somevars['CPPFLAGS'] = ' '.join(cppflags) # override files = [base_config_c, base_frozen_c] + \ files + supp_sources + addfiles + libs + \ ['$(MODLIBS)', '$(LIBS)', '$(SYSLIBS)'] outfp = bkfile.open(makefile, 'w') try: makemakefile.makemakefile(outfp, somevars, files, base_target) finally: outfp.close() # Done! if odir: - print 'Now run "make" in', odir, - print 'to build the target:', base_target + print('Now run "make" in', odir, end=' ') + print('to build the target:', base_target) else: - print 'Now run "make" to build the target:', base_target + print('Now run "make" to build the target:', base_target) # Print usage message and exit def usage(msg): sys.stdout = sys.stderr - print "Error:", msg - print "Use ``%s -h'' for help" % sys.argv[0] + print("Error:", msg) + print("Use ``%s -h'' for help" % sys.argv[0]) sys.exit(2) main() diff --git a/ext/python/Tools/freeze/hello.py b/ext/python/Tools/freeze/hello.py index f978acc..d7a3729 100644 --- a/ext/python/Tools/freeze/hello.py +++ b/ext/python/Tools/freeze/hello.py @@ -1 +1 @@ -print 'Hello world...' +print('Hello world...') diff --git a/ext/python/Tools/freeze/makeconfig.py b/ext/python/Tools/freeze/makeconfig.py index b9bfd08..8aba55e 100644 --- a/ext/python/Tools/freeze/makeconfig.py +++ b/ext/python/Tools/freeze/makeconfig.py @@ -1,61 +1,61 @@ import re # Write the config.c file never = ['marshal', '__main__', '__builtin__', 'sys', 'exceptions', '_warnings'] def makeconfig(infp, outfp, modules, with_ifdef=0): m1 = re.compile('-- ADDMODULE MARKER 1 --') m2 = re.compile('-- ADDMODULE MARKER 2 --') while 1: line = infp.readline() if not line: break outfp.write(line) if m1 and m1.search(line): m1 = None for mod in modules: if mod in never: continue if with_ifdef: outfp.write("#ifndef init%s\n"%mod) outfp.write('extern void init%s(void);\n' % mod) if with_ifdef: outfp.write("#endif\n") elif m2 and m2.search(line): m2 = None for mod in modules: if mod in never: continue outfp.write('\t{"%s", init%s},\n' % (mod, mod)) if m1: sys.stderr.write('MARKER 1 never found\n') elif m2: sys.stderr.write('MARKER 2 never found\n') # Test program. def test(): import sys if not sys.argv[3:]: - print 'usage: python makeconfig.py config.c.in outputfile', - print 'modulename ...' + print('usage: python makeconfig.py config.c.in outputfile', end=' ') + print('modulename ...') sys.exit(2) if sys.argv[1] == '-': infp = sys.stdin else: infp = open(sys.argv[1]) if sys.argv[2] == '-': outfp = sys.stdout else: outfp = open(sys.argv[2], 'w') makeconfig(infp, outfp, sys.argv[3:]) if outfp != sys.stdout: outfp.close() if infp != sys.stdin: infp.close() if __name__ == '__main__': test() diff --git a/ext/python/Tools/freeze/makefreeze.py b/ext/python/Tools/freeze/makefreeze.py index 1208b67..e359db9 100644 --- a/ext/python/Tools/freeze/makefreeze.py +++ b/ext/python/Tools/freeze/makefreeze.py @@ -1,90 +1,90 @@ import marshal import bkfile # Write a file containing frozen code for the modules in the dictionary. header = """ #include "Python.h" static struct _frozen _PyImport_FrozenModules[] = { """ trailer = """\ {0, 0, 0} /* sentinel */ }; """ # if __debug__ == 0 (i.e. -O option given), set Py_OptimizeFlag in frozen app. default_entry_point = """ int main(int argc, char **argv) { extern int Py_FrozenMain(int, char **); """ + ((not __debug__ and """ Py_OptimizeFlag++; """) or "") + """ PyImport_FrozenModules = _PyImport_FrozenModules; return Py_FrozenMain(argc, argv); } """ def makefreeze(base, dict, debug=0, entry_point=None, fail_import=()): if entry_point is None: entry_point = default_entry_point done = [] files = [] mods = dict.keys() mods.sort() for mod in mods: m = dict[mod] mangled = "__".join(mod.split(".")) if m.__code__: file = 'M_' + mangled + '.c' outfp = bkfile.open(base + file, 'w') files.append(file) if debug: - print "freezing", mod, "..." + print("freezing", mod, "...") str = marshal.dumps(m.__code__) size = len(str) if m.__path__: # Indicate package by negative size size = -size done.append((mod, mangled, size)) writecode(outfp, mangled, str) outfp.close() if debug: - print "generating table of frozen modules" + print("generating table of frozen modules") outfp = bkfile.open(base + 'frozen.c', 'w') for mod, mangled, size in done: outfp.write('extern unsigned char M_%s[];\n' % mangled) outfp.write(header) for mod, mangled, size in done: outfp.write('\t{"%s", M_%s, %d},\n' % (mod, mangled, size)) outfp.write('\n') # The following modules have a NULL code pointer, indicating # that the prozen program should not search for them on the host # system. Importing them will *always* raise an ImportError. # The zero value size is never used. for mod in fail_import: outfp.write('\t{"%s", NULL, 0},\n' % (mod,)) outfp.write(trailer) outfp.write(entry_point) outfp.close() return files # Write a C initializer for a module containing the frozen python code. # The array is called M_. def writecode(outfp, mod, str): outfp.write('unsigned char M_%s[] = {' % mod) for i in range(0, len(str), 16): outfp.write('\n\t') for c in str[i:i+16]: outfp.write('%d,' % ord(c)) outfp.write('\n};\n') ## def writecode(outfp, mod, str): ## outfp.write('unsigned char M_%s[%d] = "%s";\n' % (mod, len(str), ## '\\"'.join(map(lambda s: repr(s)[1:-1], str.split('"'))))) diff --git a/ext/python/Tools/freeze/parsesetup.py b/ext/python/Tools/freeze/parsesetup.py index 856234d..ae0bc43 100644 --- a/ext/python/Tools/freeze/parsesetup.py +++ b/ext/python/Tools/freeze/parsesetup.py @@ -1,112 +1,112 @@ # Parse Makefiles and Python Setup(.in) files. import re # Extract variable definitions from a Makefile. # Return a dictionary mapping names to values. # May raise IOError. makevardef = re.compile('^([a-zA-Z0-9_]+)[ \t]*=(.*)') def getmakevars(filename): variables = {} fp = open(filename) pendingline = "" try: while 1: line = fp.readline() if pendingline: line = pendingline + line pendingline = "" if not line: break if line.endswith('\\\n'): pendingline = line[:-2] matchobj = makevardef.match(line) if not matchobj: continue (name, value) = matchobj.group(1, 2) # Strip trailing comment i = value.find('#') if i >= 0: value = value[:i] value = value.strip() variables[name] = value finally: fp.close() return variables # Parse a Python Setup(.in) file. # Return two dictionaries, the first mapping modules to their # definitions, the second mapping variable names to their values. # May raise IOError. setupvardef = re.compile('^([a-zA-Z0-9_]+)=(.*)') def getsetupinfo(filename): modules = {} variables = {} fp = open(filename) pendingline = "" try: while 1: line = fp.readline() if pendingline: line = pendingline + line pendingline = "" if not line: break # Strip comments i = line.find('#') if i >= 0: line = line[:i] if line.endswith('\\\n'): pendingline = line[:-2] continue matchobj = setupvardef.match(line) if matchobj: (name, value) = matchobj.group(1, 2) variables[name] = value.strip() else: words = line.split() if words: modules[words[0]] = words[1:] finally: fp.close() return modules, variables # Test the above functions. def test(): import sys import os if not sys.argv[1:]: - print 'usage: python parsesetup.py Makefile*|Setup* ...' + print('usage: python parsesetup.py Makefile*|Setup* ...') sys.exit(2) for arg in sys.argv[1:]: base = os.path.basename(arg) if base[:8] == 'Makefile': - print 'Make style parsing:', arg + print('Make style parsing:', arg) v = getmakevars(arg) prdict(v) elif base[:5] == 'Setup': - print 'Setup style parsing:', arg + print('Setup style parsing:', arg) m, v = getsetupinfo(arg) prdict(m) prdict(v) else: - print arg, 'is neither a Makefile nor a Setup file' - print '(name must begin with "Makefile" or "Setup")' + print(arg, 'is neither a Makefile nor a Setup file') + print('(name must begin with "Makefile" or "Setup")') def prdict(d): keys = d.keys() keys.sort() for key in keys: value = d[key] - print "%-15s" % key, str(value) + print("%-15s" % key, str(value)) if __name__ == '__main__': test() diff --git a/ext/python/Tools/freeze/winmakemakefile.py b/ext/python/Tools/freeze/winmakemakefile.py index 8570f3d..e473fed 100644 --- a/ext/python/Tools/freeze/winmakemakefile.py +++ b/ext/python/Tools/freeze/winmakemakefile.py @@ -1,146 +1,146 @@ import sys, os # Template used then the program is a GUI program WINMAINTEMPLATE = """ #include int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // pointer to command line int nCmdShow // show state of window ) { extern int Py_FrozenMain(int, char **); PyImport_FrozenModules = _PyImport_FrozenModules; return Py_FrozenMain(__argc, __argv); } """ SERVICETEMPLATE = """ extern int PythonService_main(int, char **); int main( int argc, char **argv) { PyImport_FrozenModules = _PyImport_FrozenModules; return PythonService_main(argc, argv); } """ subsystem_details = { # -s flag : (C entry point template), (is it __main__?), (is it a DLL?) 'console' : (None, 1, 0), 'windows' : (WINMAINTEMPLATE, 1, 0), 'service' : (SERVICETEMPLATE, 0, 0), 'com_dll' : ("", 0, 1), } def get_custom_entry_point(subsystem): try: return subsystem_details[subsystem][:2] except KeyError: raise ValueError, "The subsystem %s is not known" % subsystem def makemakefile(outfp, vars, files, target): save = sys.stdout try: sys.stdout = outfp realwork(vars, files, target) finally: sys.stdout = save def realwork(vars, moddefns, target): version_suffix = "%r%r" % sys.version_info[:2] - print "# Makefile for Microsoft Visual C++ generated by freeze.py script" - print - print 'target = %s' % target - print 'pythonhome = %s' % vars['prefix'] - print - print 'DEBUG=0 # Set to 1 to use the _d versions of Python.' - print '!IF $(DEBUG)' - print 'debug_suffix=_d' - print 'c_debug=/Zi /Od /DDEBUG /D_DEBUG' - print 'l_debug=/DEBUG' - print 'temp_dir=Build\\Debug' - print '!ELSE' - print 'debug_suffix=' - print 'c_debug=/Ox' - print 'l_debug=' - print 'temp_dir=Build\\Release' - print '!ENDIF' - print - - print '# The following line assumes you have built Python using the standard instructions' - print '# Otherwise fix the following line to point to the library.' - print 'pythonlib = "$(pythonhome)/pcbuild/python%s$(debug_suffix).lib"' % version_suffix - print + print("# Makefile for Microsoft Visual C++ generated by freeze.py script") + print() + print('target = %s' % target) + print('pythonhome = %s' % vars['prefix']) + print() + print('DEBUG=0 # Set to 1 to use the _d versions of Python.') + print('!IF $(DEBUG)') + print('debug_suffix=_d') + print('c_debug=/Zi /Od /DDEBUG /D_DEBUG') + print('l_debug=/DEBUG') + print('temp_dir=Build\\Debug') + print('!ELSE') + print('debug_suffix=') + print('c_debug=/Ox') + print('l_debug=') + print('temp_dir=Build\\Release') + print('!ENDIF') + print() + + print('# The following line assumes you have built Python using the standard instructions') + print('# Otherwise fix the following line to point to the library.') + print('pythonlib = "$(pythonhome)/pcbuild/python%s$(debug_suffix).lib"' % version_suffix) + print() # We only ever write one "entry point" symbol - either # "main" or "WinMain". Therefore, there is no need to # pass a subsystem switch to the linker as it works it # out all by itself. However, the subsystem _does_ determine # the file extension and additional linker flags. target_link_flags = "" target_ext = ".exe" if subsystem_details[vars['subsystem']][2]: target_link_flags = "-dll" target_ext = ".dll" - print "# As the target uses Python%s.dll, we must use this compiler option!" % version_suffix - print "cdl = /MD" - print - print "all: $(target)$(debug_suffix)%s" % (target_ext) - print + print("# As the target uses Python%s.dll, we must use this compiler option!" % version_suffix) + print("cdl = /MD") + print() + print("all: $(target)$(debug_suffix)%s" % (target_ext)) + print() - print '$(temp_dir):' - print ' if not exist $(temp_dir)\. mkdir $(temp_dir)' - print + print('$(temp_dir):') + print(' if not exist $(temp_dir)\. mkdir $(temp_dir)') + print() objects = [] libs = ["shell32.lib", "comdlg32.lib", "wsock32.lib", "user32.lib", "oleaut32.lib"] for moddefn in moddefns: - print "# Module", moddefn.name + print("# Module", moddefn.name) for file in moddefn.sourceFiles: base = os.path.basename(file) base, ext = os.path.splitext(base) objects.append(base + ".obj") - print '$(temp_dir)\%s.obj: "%s"' % (base, file) - print "\t@$(CC) -c -nologo /Fo$* $(cdl) $(c_debug) /D BUILD_FREEZE", - print '"-I$(pythonhome)/Include" "-I$(pythonhome)/PC" \\' - print "\t\t$(cflags) $(cdebug) $(cinclude) \\" + print('$(temp_dir)\%s.obj: "%s"' % (base, file)) + print("\t@$(CC) -c -nologo /Fo$* $(cdl) $(c_debug) /D BUILD_FREEZE", end=' ') + print('"-I$(pythonhome)/Include" "-I$(pythonhome)/PC" \\') + print("\t\t$(cflags) $(cdebug) $(cinclude) \\") extra = moddefn.GetCompilerOptions() if extra: - print "\t\t%s \\" % (' '.join(extra),) - print '\t\t"%s"' % file - print + print("\t\t%s \\" % (' '.join(extra),)) + print('\t\t"%s"' % file) + print() # Add .lib files this module needs for modlib in moddefn.GetLinkerLibs(): if modlib not in libs: libs.append(modlib) - print "ADDN_LINK_FILES=", - for addn in vars['addn_link']: print '"%s"' % (addn), - print ; print - - print "OBJS=", - for obj in objects: print '"$(temp_dir)\%s"' % (obj), - print ; print - - print "LIBS=", - for lib in libs: print '"%s"' % (lib), - print ; print - - print "$(target)$(debug_suffix)%s: $(temp_dir) $(OBJS)" % (target_ext) - print "\tlink -out:$(target)$(debug_suffix)%s %s" % (target_ext, target_link_flags), - print "\t$(OBJS) \\" - print "\t$(LIBS) \\" - print "\t$(ADDN_LINK_FILES) \\" - print "\t$(pythonlib) $(lcustom) $(l_debug)\\" - print "\t$(resources)" - print - print "clean:" - print "\t-rm -f *.obj" - print "\t-rm -f $(target).exe" + print("ADDN_LINK_FILES=", end=' ') + for addn in vars['addn_link']: print('"%s"' % (addn), end=' ') + print() ; print() + + print("OBJS=", end=' ') + for obj in objects: print('"$(temp_dir)\%s"' % (obj), end=' ') + print() ; print() + + print("LIBS=", end=' ') + for lib in libs: print('"%s"' % (lib), end=' ') + print() ; print() + + print("$(target)$(debug_suffix)%s: $(temp_dir) $(OBJS)" % (target_ext)) + print("\tlink -out:$(target)$(debug_suffix)%s %s" % (target_ext, target_link_flags), end=' ') + print("\t$(OBJS) \\") + print("\t$(LIBS) \\") + print("\t$(ADDN_LINK_FILES) \\") + print("\t$(pythonlib) $(lcustom) $(l_debug)\\") + print("\t$(resources)") + print() + print("clean:") + print("\t-rm -f *.obj") + print("\t-rm -f $(target).exe") diff --git a/kolab-cli.py b/kolab-cli.py index fbcfac5..d97d715 100755 --- a/kolab-cli.py +++ b/kolab-cli.py @@ -1,40 +1,42 @@ #!/usr/bin/python # # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import logging import os import sys # For development purposes sys.path = [ '.' ] + sys.path from pykolab.translate import _ from pykolab.cli import Cli try: import pykolab.logger except ImportError, e: - print >> sys.stderr, _("Cannot load pykolab/logger.py:") - print >> sys.stderr, "%s" % e + print(_("Cannot load pykolab/logger.py:"), file=sys.stderr) + print("%s" % e, file=sys.stderr) sys.exit(1) if __name__ == "__main__": kolab = Cli() kolab.run() diff --git a/pykolab/cli/cmd_add_alias.py b/pykolab/cli/cmd_add_alias.py index 9f7a2c6..e905f79 100644 --- a/pykolab/cli/cmd_add_alias.py +++ b/pykolab/cli/cmd_add_alias.py @@ -1,129 +1,131 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.auth import Auth from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('add_alias', execute, description="Add alias.") def execute(*args, **kw): try: primary_rcpt_address = conf.cli_args.pop(0) try: secondary_rcpt_address = conf.cli_args.pop(0) except: - print >> sys.stderr, _("Specify the (new) alias address") + print(_("Specify the (new) alias address"), file=sys.stderr) sys.exit(1) except: - print >> sys.stderr, _("Specify the existing recipient address") + print(_("Specify the existing recipient address"), file=sys.stderr) sys.exit(1) if len(primary_rcpt_address.split('@')) > 1: primary_rcpt_domain = primary_rcpt_address.split('@')[-1] else: primary_rcpt_domain = conf.get('kolab', 'primary_domain') auth = Auth(domain=primary_rcpt_domain) domains = auth.list_domains() #print domains if len(secondary_rcpt_address.split('@')) > 1: secondary_rcpt_domain = secondary_rcpt_address.split('@')[-1] else: secondary_rcpt_domain = conf.get('kolab', 'primary_domain') # Check if either is in fact a domain if not primary_rcpt_domain.lower() in domains.keys(): - print >> sys.stderr, _("Domain %r is not a local domain") % (primary_rcpt_domain) + print(_("Domain %r is not a local domain") % (primary_rcpt_domain), file=sys.stderr) sys.exit(1) if not secondary_rcpt_domain.lower() in domains.keys(): - print >> sys.stderr, _("Domain %r is not a local domain") % (secondary_rcpt_domain) + print(_("Domain %r is not a local domain") % (secondary_rcpt_domain), file=sys.stderr) sys.exit(1) if not primary_rcpt_domain == secondary_rcpt_domain: if not domains[primary_rcpt_domain] == domains[secondary_rcpt_domain]: - print >> sys.stderr, _("Primary and secondary domain do not have the same parent domain") + print(_("Primary and secondary domain do not have the same parent domain"), file=sys.stderr) sys.exit(1) primary_recipient_dn = auth.find_recipient(primary_rcpt_address) if primary_recipient_dn == [] or len(primary_recipient_dn) == 0: - print >> sys.stderr, _("No such recipient %r") % (primary_rcpt_address) + print(_("No such recipient %r") % (primary_rcpt_address), file=sys.stderr) sys.exit(1) secondary_recipient_dn = auth.find_recipient(secondary_rcpt_address) if not secondary_recipient_dn == [] and not len(secondary_recipient_dn) == 0: - print >> sys.stderr, _("Recipient for alias %r already exists") % (secondary_rcpt_address) + print(_("Recipient for alias %r already exists") % (secondary_rcpt_address), file=sys.stderr) sys.exit(1) rcpt_attrs = conf.get_list('ldap', 'mail_attributes') primary_rcpt_attr = rcpt_attrs[0] if len(rcpt_attrs) >= 2: secondary_rcpt_attr = rcpt_attrs[1] else: - print >> sys.stderr, _("Environment is not configured for " + \ - "users to hold secondary mail attributes") + print(_("Environment is not configured for " + \ + "users to hold secondary mail attributes"), file=sys.stderr) sys.exit(1) primary_recipient = auth.get_entry_attributes(primary_rcpt_domain, primary_recipient_dn, rcpt_attrs) if not primary_recipient.has_key(primary_rcpt_attr): - print >> sys.stderr, _("Recipient %r is not the primary recipient for address %r") % (primary_recipient, primary_rcpt_address) + print(_("Recipient %r is not the primary recipient for address %r") % (primary_recipient, primary_rcpt_address), file=sys.stderr) sys.exit(1) if not primary_recipient.has_key(secondary_rcpt_attr): auth.set_entry_attributes(primary_rcpt_domain, primary_recipient_dn, {secondary_rcpt_attr: [ secondary_rcpt_address ] }) else: if isinstance(primary_recipient[secondary_rcpt_attr], basestring): new_secondary_rcpt_attrs = [ primary_recipient[secondary_rcpt_attr], secondary_rcpt_address ] else: new_secondary_rcpt_attrs = \ primary_recipient[secondary_rcpt_attr] + \ [ secondary_rcpt_address ] auth.set_entry_attributes( primary_rcpt_domain, primary_recipient_dn, { secondary_rcpt_attr: new_secondary_rcpt_attrs } ) diff --git a/pykolab/cli/cmd_add_user_subscription.py b/pykolab/cli/cmd_add_user_subscription.py index d8a8901..eb0ec40 100644 --- a/pykolab/cli/cmd_add_user_subscription.py +++ b/pykolab/cli/cmd_add_user_subscription.py @@ -1,85 +1,87 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ from pykolab import utils log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register( 'add_user_subscription', execute, aliases=['aus', 'subscribe'], description=description() ) def description(): return _("Subscribe a user to a folder.") def execute(*args, **kw): folder_pattern = "*" try: user = conf.cli_args.pop(0) try: folder_pattern = conf.cli_args.pop(0) except IndexError, errmsg: folder_pattern = utils.ask_question(_("Folder pattern")) except IndexError, errmsg: user = utils.ask_question(_("User ID")) folder_pattern = utils.ask_question(_("Folder pattern")) if len(user.split('@')) > 1: domain = user.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') imap = IMAP() imap.connect(domain=domain, login=False) backend = conf.get(domain, 'imap_backend') if backend == None: backend = conf.get('kolab', 'imap_backend') admin_login = conf.get(backend, 'admin_login') admin_password = conf.get(backend, 'admin_password') imap.login_plain(admin_login, admin_password, user) if not imap.has_folder(folder_pattern): - print >> sys.stderr, \ - _("Cannot subscribe user to folder %r:") % (folder_pattern), \ - _("No such folder") + print(_("Cannot subscribe user to folder %r:") % (folder_pattern), \ + _("No such folder"), \ + file=sys.stderr) sys.exit(1) _folders = imap.lm(folder_pattern) for _folder in _folders: imap.subscribe(_folder) diff --git a/pykolab/cli/cmd_check_quota.py b/pykolab/cli/cmd_check_quota.py index ecbd66d..71af296 100644 --- a/pykolab/cli/cmd_check_quota.py +++ b/pykolab/cli/cmd_check_quota.py @@ -1,108 +1,110 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.auth import Auth from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('check_quota', execute, description=description()) def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--dry-run', dest = "dryrun", action = "store", default = False, help = _("Do not apply any changes.") ) my_option_group.add_option( '--server', dest = "connect_server", action = "store", default = None, metavar = "SERVER", help = _("List mailboxes on server SERVER only.") ) def description(): return _("Compare existing IMAP quota with LDAP quota.") def execute(*args, **kw): """ List mailboxes """ imap = IMAP() imap.connect(server=conf.connect_server) auth = Auth() auth.connect() domains = auth.list_domains() folders = [] for domain in domains.keys(): folders = imap.lm("user/%%@%s" % (domain)) domain_auth = Auth(domain=domain) domain_auth.connect(domain=domain) for folder in folders: login = folder.split('/')[1] user_dn = domain_auth.find_recipient(login) if user_dn == None: - print >> sys.stderr, _("No such user %s") % (login) + print(_("No such user %s") % (login), file=sys.stderr) continue if len(login.split('@')) > 1: domain = login.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') try: user_quota = auth.get_entry_attribute(domain, user_dn, 'mailquota') except: user_quota = None if user_quota == None: - print >> sys.stderr, _("No quota for user %s") % (login) + print(_("No quota for user %s") % (login), file=sys.stderr) continue try: (used, quota) = imap.get_quota(folder) if not (int)(quota) == (int)(user_quota): - print >> sys.stderr, _("user quota does not match for %s (IMAP: %d, LDAP: %d)") % (login, (int)(quota), (int)(user_quota)) + print(_("user quota does not match for %s (IMAP: %d, LDAP: %d)") % (login, (int)(quota), (int)(user_quota)), file=sys.stderr) except: pass diff --git a/pykolab/cli/cmd_count_domain_mailboxes.py b/pykolab/cli/cmd_count_domain_mailboxes.py index 958bccd..2a3ea95 100644 --- a/pykolab/cli/cmd_count_domain_mailboxes.py +++ b/pykolab/cli/cmd_count_domain_mailboxes.py @@ -1,66 +1,66 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import datetime import commands import pykolab from pykolab import imap_utf7 from pykolab.auth import Auth from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('count_domain_mailboxes', execute) def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--server', dest = "connect_server", action = "store", default = None, metavar = "SERVER", help = _("List mailboxes on server SERVER only.")) def execute(*args, **kw): """ List deleted mailboxes """ imap = IMAP() imap.connect() auth = Auth() auth.connect() domains = auth.list_domains() folders = [] for domain in domains.keys(): - print "%s: %d" % (domain,len(imap.lm("user/%%@%s" % (domain)))) + print("%s: %d" % (domain,len(imap.lm("user/%%@%s" % (domain))))) null_realm = len(imap.lm("user/%%")) if null_realm > 0: - print "null: %d" % (null_realm) + print("null: %d" % (null_realm)) diff --git a/pykolab/cli/cmd_delete_mailbox.py b/pykolab/cli/cmd_delete_mailbox.py index 9a7e1b1..f6ff254 100644 --- a/pykolab/cli/cmd_delete_mailbox.py +++ b/pykolab/cli/cmd_delete_mailbox.py @@ -1,70 +1,72 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('delete_mailbox', execute, description=description(), aliases=['dm']) def description(): return """Delete a mailbox or sub-folder. Note that the mailbox or folder is removed recursively.""" def execute(*args, **kw): """ Delete mailbox """ if len(conf.cli_args) < 1: - print >> sys.stderr, _("No mailbox specified") + print(_("No mailbox specified"), file=sys.stderr) sys.exit(1) imap = IMAP() imap.connect() delete_folders = [] while len(conf.cli_args) > 0: folder = conf.cli_args.pop(0) folders = imap.list_folders(folder) if len(folders) < 1: - print >> sys.stderr, _("No such folder(s): %s") % (folder) + print(_("No such folder(s): %s") % (folder), file=sys.stderr) delete_folders.extend(folders) if len(delete_folders) == 0: - print >> sys.stderr, _("No folders to delete.") + print(_("No folders to delete."), file=sys.stderr) sys.exit(1) for delete_folder in delete_folders: try: imap.delete_mailfolder(delete_folder) except Exception, errmsg: log.error(_("Could not delete mailbox '%s'") % (delete_folder)) diff --git a/pykolab/cli/cmd_delete_mailbox_acl.py b/pykolab/cli/cmd_delete_mailbox_acl.py index b7a7ede..ebfc2ec 100644 --- a/pykolab/cli/cmd_delete_mailbox_acl.py +++ b/pykolab/cli/cmd_delete_mailbox_acl.py @@ -1,69 +1,71 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ from pykolab import utils log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('delete_mailbox_acl', execute, description=description(), aliases=['dam']) def description(): return """Delete an ACL entry for a folder.""" def execute(*args, **kw): try: folder = conf.cli_args.pop(0) try: identifier = conf.cli_args.pop(0) except IndexError, errmsg: identifier = utils.ask_question(_("ACI Subject")) except IndexError, errmsg: folder = utils.ask_question(_("Folder name")) quota = utils.ask_question(_("ACI Subject")) if len(folder.split('@')) > 1: domain = folder.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') imap = IMAP() imap.connect(domain=domain) if not imap.has_folder(folder): - print >> sys.stderr, _("No such folder %r") % (folder) + print(_("No such folder %r") % (folder), file=sys.stderr) else: folders = imap.list_folders(folder) for folder in folders: try: imap.set_acl(folder, identifier, '') except: # Mailbox no longer exists? pass diff --git a/pykolab/cli/cmd_export_mailbox.py b/pykolab/cli/cmd_export_mailbox.py index 37a862c..78d55f1 100644 --- a/pykolab/cli/cmd_export_mailbox.py +++ b/pykolab/cli/cmd_export_mailbox.py @@ -1,122 +1,124 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import commands import pykolab from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('export_mailbox', execute) def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--all', dest = "all", action = "store_true", default = False, help = _("All folders this user has access to")) def execute(*args, **kw): import os import subprocess user = conf.cli_args.pop(0) # TODO: /etc/imapd.conf is not the definitive location for the # imapd.conf configuration file. partition_proc = subprocess.Popen( ['grep', '^partition', '/etc/imapd.conf'], stdout=subprocess.PIPE ) partitions = [ x.split(':')[1].strip() for x in partition_proc.communicate()[0].split('\n') if len(x.split(':')) > 1 ] # TODO: ctl_mboxlist is not necessarily in this location. ctl_mboxlist_args = [ '/usr/lib/cyrus-imapd/ctl_mboxlist', '-d' ] ctl_mboxlist = subprocess.Popen( ctl_mboxlist_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) mboxlist_proc = subprocess.Popen( ['grep', '-E', '\s*%s\s*.*i.*p.*' % (user)], stdin=ctl_mboxlist.stdout, stdout=subprocess.PIPE ) ctl_mboxlist.stdout.close() # TODO: Handle errors from ctl_mboxlist process (stderr) mboxlist_output = mboxlist_proc.communicate()[0] zipper_args = [ 'zip', '-r', '%s.zip' % (user) ] directories = [] for mbox_internal in mboxlist_output.split('\n'): if len(mbox_internal.split('\t')[0].split('!')) > 1: domain = mbox_internal.split('\t')[0].split('!')[0] mailbox = '/'.join( mbox_internal.split( '\t' )[0].split( '!' )[1].split( '.' )[1:] ) for partition in partitions: mbox_dir = '%s/domain/%s/%s/%s/user/%s/' % ( partition, domain[0], domain, user[0], mailbox ) if os.path.isdir(mbox_dir): directories.append(mbox_dir) else: log.debug( _('%s is not a directory') % (mbox_dir), level=5 ) if not len(directories) == 0: zipper_output = subprocess.Popen( zipper_args + directories, stdout=subprocess.PIPE ).communicate()[0] - print >> sys.stderr, _("ZIP file at %s.zip") % (user) + print(_("ZIP file at %s.zip") % (user), file=sys.stderr) else: - print >> sys.stderr, _("No directories found for user %s") % (user) + print(_("No directories found for user %s") % (user), file=sys.stderr) sys.exit(1) diff --git a/pykolab/cli/cmd_list_deleted_mailboxes.py b/pykolab/cli/cmd_list_deleted_mailboxes.py index 6d07816..55b6b9f 100644 --- a/pykolab/cli/cmd_list_deleted_mailboxes.py +++ b/pykolab/cli/cmd_list_deleted_mailboxes.py @@ -1,114 +1,114 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import datetime import commands import pykolab from pykolab import imap_utf7 from pykolab.auth import Auth from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_deleted_mailboxes', execute) def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--raw', dest="raw", action="store_true", default=False, help=_("Display raw IMAP UTF-7 folder names") ) my_option_group.add_option( '--server', dest="connect_server", action="store", default=None, metavar="SERVER", help=_("List mailboxes on server SERVER only.") ) def execute(*args, **kw): """ List deleted mailboxes """ folders = [] searches = [] imap = IMAP() if conf.connect_server is not None: imap.connect(server=conf.connect_server) else: imap.connect() # See if conf.cli_args components make sense. for arg in conf.cli_args: if arg == '*': searches.append(arg) if arg.startswith('user'): searches.append(arg) if arg.startswith('shared'): searches.append(arg) if arg.startswith('DELETED'): searches.append(arg) if arg.startswith('news'): searches.append(arg) if len(searches) == 0: auth = Auth() auth.connect() domains = auth.list_domains() folders = [] for domain in list(set(domains.keys())): folders.extend(imap.lm("DELETED/*@%s" % (domain))) folders.extend(imap.lm("DELETED/*")) else: for search in searches: log.debug(_("Appending folder search for %r") % (search), level=8) folders.extend(imap.lm(imap_utf7.encode(search))) - print "Deleted folders:" + print("Deleted folders:") for folder in folders: utf8_folder = imap_utf7.decode(folder).encode('utf-8') mbox_parts = imap.parse_mailfolder(utf8_folder) ts = datetime.datetime.fromtimestamp(int(mbox_parts['hex_timestamp'], 16)) if not conf.raw: - print "%s (Deleted at %s)" % (utf8_folder, ts) + print("%s (Deleted at %s)" % (utf8_folder, ts)) else: - print "%s (Deleted at %s)" % (folder, ts) + print("%s (Deleted at %s)" % (folder, ts)) diff --git a/pykolab/cli/cmd_list_domain_mailboxes.py b/pykolab/cli/cmd_list_domain_mailboxes.py index d4c8bfe..fd9a7fd 100644 --- a/pykolab/cli/cmd_list_domain_mailboxes.py +++ b/pykolab/cli/cmd_list_domain_mailboxes.py @@ -1,84 +1,84 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import commands import pykolab from pykolab import utils from pykolab import imap_utf7 from pykolab.auth import Auth from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_domain_mailboxes', execute) def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--raw', dest = "raw", action = "store_true", default = False, help = _("Display raw IMAP UTF-7 folder names")) my_option_group.add_option( '--server', dest = "connect_server", action = "store", default = None, metavar = "SERVER", help = _("List mailboxes on server SERVER only.")) def execute(*args, **kw): """ List deleted mailboxes """ try: domain = conf.cli_args.pop(0) except: domain = utils.ask_question(_("Domain")) imap = IMAP() imap.connect() auth = Auth() auth.connect() domains = auth.list_domains() folders = [] for primary,secondaries in domains: if not domain == primary and not domain in secondaries: continue folders.extend(imap.lm("user/%%@%s" % (primary))) for secondary in secondaries: folders.extend(imap.lm("user/%%@%s" % (secondary))) - print "Deleted folders:" + print("Deleted folders:") for folder in folders: if not conf.raw: - print imap_utf7.decode(folder) + print(imap_utf7.decode(folder)) else: - print folder + print(folder) diff --git a/pykolab/cli/cmd_list_domains.py b/pykolab/cli/cmd_list_domains.py index 3ee4d20..62ee29f 100644 --- a/pykolab/cli/cmd_list_domains.py +++ b/pykolab/cli/cmd_list_domains.py @@ -1,52 +1,52 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import commands import pykolab from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_domains', execute, description="List Kolab domains.") def execute(*args, **kw): from pykolab import wap_client # Create the authentication object. # TODO: Binds with superuser credentials! wap_client.authenticate() domains = wap_client.domains_list() dna = conf.get('ldap', 'domain_name_attribute') - print "%-39s %-40s" % ("Primary Domain Name Space","Secondary Domain Name Space(s)") + print("%-39s %-40s" % ("Primary Domain Name Space","Secondary Domain Name Space(s)")) # TODO: Take a hint in --quiet, and otherwise print out a nice table # with headers and such. if isinstance(domains['list'], dict): for domain_dn in domains['list'].keys(): if isinstance(domains['list'][domain_dn][dna], list): - print domains['list'][domain_dn][dna][0] + print(domains['list'][domain_dn][dna][0]) for domain_alias in domains['list'][domain_dn][dna][1:]: - print "%-39s %-40s" % ('', domain_alias) + print("%-39s %-40s" % ('', domain_alias)) else: - print domains['list'][domain_dn][dna] + print(domains['list'][domain_dn][dna]) diff --git a/pykolab/cli/cmd_list_mailbox_acls.py b/pykolab/cli/cmd_list_mailbox_acls.py index c9e47ac..bf93429 100644 --- a/pykolab/cli/cmd_list_mailbox_acls.py +++ b/pykolab/cli/cmd_list_mailbox_acls.py @@ -1,65 +1,67 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ from pykolab import utils log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_mailbox_acls', execute, description=description(), aliases=['lam']) def description(): return """Obtain a list of ACL entries on a folder.""" def execute(*args, **kw): try: folder = conf.cli_args.pop(0) except IndexError, errmsg: folder = utils.ask_question(_("Folder name")) if len(folder.split('@')) > 1: domain = folder.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') imap = IMAP() imap.connect(domain=domain) if not imap.has_folder(folder): - print >> sys.stderr, _("No such folder %r") % (folder) + print(_("No such folder %r") % (folder), file=sys.stderr) else: acls = [] folders = imap.list_folders(folder) for folder in folders: - print "Folder", folder + print("Folder", folder) acls = imap.list_acls(folder) for acl in acls.keys(): - print " %-13s %s" %(acls[acl], acl) + print(" %-13s %s" %(acls[acl], acl)) diff --git a/pykolab/cli/cmd_list_mailbox_metadata.py b/pykolab/cli/cmd_list_mailbox_metadata.py index 223482b..718c376 100644 --- a/pykolab/cli/cmd_list_mailbox_metadata.py +++ b/pykolab/cli/cmd_list_mailbox_metadata.py @@ -1,95 +1,97 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ from pykolab import utils log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_mailbox_metadata', execute, aliases='lmm', description=description()) def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--user', dest = "user", action = "store", default = None, metavar = "USER", help = _("List annotations as user USER") ) def description(): return """Obtain a list of metadata entries on a folder.""" def execute(*args, **kw): try: folder = conf.cli_args.pop(0) except IndexError, errmsg: folder = utils.ask_question(_("Folder name")) if len(folder.split('@')) > 1: domain = folder.split('@')[1] elif not conf.user == None and len(conf.user.split('@')) > 1: domain = conf.user.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') imap = IMAP() if not conf.user == None: imap.connect(domain=domain, login=False) backend = conf.get(domain, 'imap_backend') if backend == None: backend = conf.get('kolab', 'imap_backend') admin_login = conf.get(backend, 'admin_login') admin_password = conf.get(backend, 'admin_password') imap.login_plain(admin_login, admin_password, conf.user) else: imap.connect(domain=domain) if not imap.has_folder(folder): - print >> sys.stderr, _("No such folder %r") % (folder) + print(_("No such folder %r") % (folder), file=sys.stderr) else: metadata = [] folders = imap.list_folders(folder) for folder in folders: - print "Folder", folder + print("Folder", folder) metadata = imap.get_metadata(folder) if metadata.has_key(folder): for annotation in metadata[folder].keys(): - print " %-49s %s" % ( + print(" %-49s %s" % ( annotation, metadata[folder][annotation] - ) + )) diff --git a/pykolab/cli/cmd_list_mailboxes.py b/pykolab/cli/cmd_list_mailboxes.py index 3b3bbd8..738cd86 100644 --- a/pykolab/cli/cmd_list_mailboxes.py +++ b/pykolab/cli/cmd_list_mailboxes.py @@ -1,103 +1,103 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import commands import pykolab from pykolab import imap_utf7 from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_mailboxes', execute, description=description(), aliases='lm') def description(): return "List mailboxes.\n" + \ "%-28s" % ('') + \ "Use wildcards '*' and '%' for more control.\n" def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--raw', dest="raw", action="store_true", default=False, help=_("Display raw IMAP UTF-7 folder names") ) my_option_group.add_option( '--server', dest="connect_server", action="store", default=None, metavar="SERVER", help=_("List mailboxes on server SERVER only.") ) def execute(*args, **kw): """ List mailboxes """ searches = [] # See if conf.cli_args components make sense. for arg in conf.cli_args: if arg == '*': searches.append(arg) if arg.startswith('user'): searches.append(arg) if arg.startswith('shared'): searches.append(arg) if arg.startswith('DELETED'): searches.append(arg) if arg.startswith('news'): searches.append(arg) if len(searches) == 0: searches = [''] imap = IMAP() if not conf.connect_server == None: imap.connect(server=conf.connect_server) else: imap.connect() folders = [] for search in searches: log.debug(_("Appending folder search for %r") % (search), level=8) folders.extend(imap.lm(imap_utf7.encode(search))) for folder in folders: if not conf.raw: - print imap_utf7.decode(folder) + print(imap_utf7.decode(folder)) else: - print folder + print(folder) diff --git a/pykolab/cli/cmd_list_messages.py b/pykolab/cli/cmd_list_messages.py index db59a48..786a5f0 100644 --- a/pykolab/cli/cmd_list_messages.py +++ b/pykolab/cli/cmd_list_messages.py @@ -1,134 +1,134 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import sys import commands import pykolab from pykolab import imap_utf7 from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_messages', execute, description=description()) def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--deleted', dest = "list_deleted", action = "store_true", default = False, help = _("Include messages flagged as \Deleted") ) my_option_group.add_option( '--server', dest = "connect_server", action = "store", default = None, metavar = "SERVER", help = _("List mailboxes on server SERVER only.") ) my_option_group.add_option( '--user', dest = "user", action = "store", default = None, metavar = "USER", help = _("List messages as user USER") ) def description(): return _("List messages in a folder") def execute(*args, **kw): """ List messages in a folder """ try: folder = conf.cli_args.pop(0) except: log.error(_("Specify a folder")) sys.exit(1) domain = None imap = IMAP() if not conf.user == None: imap.connect(domain=domain, login=False, server=conf.connect_server) backend = conf.get(domain, 'imap_backend') if backend == None: backend = conf.get('kolab', 'imap_backend') admin_login = conf.get(backend, 'admin_login') admin_password = conf.get(backend, 'admin_password') imap.login_plain(admin_login, admin_password, conf.user) else: imap.connect(domain=domain, server=conf.connect_server) _folder = imap.lm(imap_utf7.encode(folder)) if _folder == None or _folder == []: log.error(_("No such folder")) sys.exit(1) if conf.user == None: imap.set_acl(folder, 'cyrus-admin', 'lrs') imap.select(imap_utf7.encode(folder)) if conf.list_deleted: typ, data = imap.search(None, 'ALL') else: typ, data = imap.search(None, '(ALL UNDELETED)') num_messages = len(data[0].split()) for num in data[0].split(): typ, flags = imap.fetch(num, 'FLAGS') flags = flags[0].split() if len(flags) >= 3: # Any flags are set if flags[2] == '(\\Deleted))': - print num, '\Deleted' + print(num, '\Deleted') elif flags[2] == '(\\Deleted': - print num, '\Deleted' + print(num, '\Deleted') elif '\\Deleted' in flags[3:]: - print num, '\Deleted' + print(num, '\Deleted') elif '\\Deleted))' in flags[3:]: - print num, '\Deleted' + print(num, '\Deleted') else: - print num + print(num) else: - print num + print(num) if conf.user == None: imap.set_acl(folder, 'cyrus-admin', '') diff --git a/pykolab/cli/cmd_list_ous.py b/pykolab/cli/cmd_list_ous.py index 670b609..61a4cb9 100644 --- a/pykolab/cli/cmd_list_ous.py +++ b/pykolab/cli/cmd_list_ous.py @@ -1,39 +1,39 @@ # -*- coding: utf-8 -*- # Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import commands import pykolab from pykolab import utils from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_ous', execute, description="List organizational units.") def execute(*args, **kw): from pykolab import wap_client wap_client.authenticate(username=conf.get("ldap", "bind_dn"), password=conf.get("ldap", "bind_pw")) ous = wap_client.ous_list() - print '\n'.join(ous['list'].keys()) + print('\n'.join(ous['list'].keys())) diff --git a/pykolab/cli/cmd_list_quota.py b/pykolab/cli/cmd_list_quota.py index f66f580..28b0cd5 100644 --- a/pykolab/cli/cmd_list_quota.py +++ b/pykolab/cli/cmd_list_quota.py @@ -1,102 +1,104 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_quota', execute, description=description(), aliases=['lq']) def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--server', dest = "connect_server", action = "store", default = None, metavar = "SERVER", help = _("List mailboxes on server SERVER only.")) def description(): return """List quota for a folder.""" def execute(*args, **kw): """ List quota for a mailbox """ try: quota_folder = conf.cli_args.pop(0) except IndexError, e: quota_folder = '*' imap = IMAP() if not conf.connect_server == None: imap.connect(server=conf.connect_server) else: imap.connect() folders = [] quota_folders = imap.list_folders(quota_folder) for quota_folder in quota_folders: try: (used, quota) = imap.get_quota(quota_folder) - print "Folder: %s" % (quota_folder) + print("Folder: %s" % (quota_folder)) if not used == None and not quota == None: if quota == 0: - print >> sys.stderr, _("The quota for folder %s is set to literally allow 0KB of storage.") % (quota_folder) - print "%d (Used: %d, Percentage: %s)" % (quota, used, u'\u221E') + print(_("The quota for folder %s is set to literally allow 0KB of storage.") % (quota_folder), file=sys.stderr) + print("%d (Used: %d, Percentage: %s)" % (quota, used, u'\u221E')) else: percentage = round(((float)(used)/(float)(quota)) * 100.0, 1) - print "%d (Used: %d, Percentage: %d)" % (quota, used, percentage) + print("%d (Used: %d, Percentage: %d)" % (quota, used, percentage)) else: if used == None: - print "%d (Used: %d, Percentage: %d)" % (quota, 0, 0) + print("%d (Used: %d, Percentage: %d)" % (quota, 0, 0)) else: - print "No quota" + print("No quota") except: try: (quota_root, used, quota) = imap.get_quota_root(quota_folder) - print "Folder: %s" % (quota_folder) + print("Folder: %s" % (quota_folder)) if not quota_root == None and not used == None and not quota == None: if quota == 0: - print >> sys.stderr, _("The quota for folder %s is set to literally allow 0KB of storage.") % (quota_folder) - print "%d (Used: %d, Percentage: %d)" % (quota, used, u'\u221E') + print(_("The quota for folder %s is set to literally allow 0KB of storage.") % (quota_folder), file=sys.stderr) + print("%d (Used: %d, Percentage: %d)" % (quota, used, u'\u221E')) else: percentage = round(((float)(used)/(float)(quota)) * 100.0, 1) - print "%d (Root: %s, Used: %d, Percentage: %d)" % (quota, quota_root, used, percentage) + print("%d (Root: %s, Used: %d, Percentage: %d)" % (quota, quota_root, used, percentage)) else: if used == None and not quota_root == None: - print "%d (Root: %s, Used: %d, Percentage: %d)" % (quota, quota_root, 0, 0) + print("%d (Root: %s, Used: %d, Percentage: %d)" % (quota, quota_root, 0, 0)) else: - print "No quota" + print("No quota") except: - print "Folder: %s" % (quota_folder) - print "No quota root" + print("Folder: %s" % (quota_folder)) + print("No quota root") diff --git a/pykolab/cli/cmd_list_user_subscriptions.py b/pykolab/cli/cmd_list_user_subscriptions.py index 9095604..2d8e4f3 100644 --- a/pykolab/cli/cmd_list_user_subscriptions.py +++ b/pykolab/cli/cmd_list_user_subscriptions.py @@ -1,104 +1,104 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import commands import pykolab from pykolab import imap_utf7 from pykolab.imap import IMAP from pykolab.translate import _ from pykolab import utils log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_user_subscriptions', execute, aliases='lus', description=description()) def cli_options(*args, **kw): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--raw', dest = "raw", action = "store_true", default = False, help = _("Display raw IMAP UTF-7 folder names")) my_option_group.add_option( '--unsubscribed', dest = "unsubscribed", action = "store_true", default = False, help = _("List unsubscribed folders")) def description(): return _("List the folders a user is subscribed to.") def execute(*args, **kw): folder_pattern = "*" try: user = conf.cli_args.pop(0) try: folder_pattern = conf.cli_args.pop(0) except IndexError, errmsg: pass except IndexError, errmsg: user = utils.ask_question(_("User ID")) if len(user.split('@')) > 1: domain = user.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') imap = IMAP() imap.connect(domain=domain, login=False) backend = conf.get(domain, 'imap_backend') if backend == None: backend = conf.get('kolab', 'imap_backend') admin_login = conf.get(backend, 'admin_login') admin_password = conf.get(backend, 'admin_password') imap.login_plain(admin_login, admin_password, user) subscribed_folders = imap.lsub(folder_pattern) if conf.unsubscribed: unsubscribed_folders = [] all_folders = imap.lm(folder_pattern) for folder in all_folders: if not folder in subscribed_folders: unsubscribed_folders.append(folder) if len(unsubscribed_folders) > 0: if not conf.raw: - print "\n".join([imap_utf7.decode(x) for x in unsubscribed_folders]) + print("\n".join([imap_utf7.decode(x) for x in unsubscribed_folders])) else: - print "\n".join(unsubscribed_folders) + print("\n".join(unsubscribed_folders)) else: - print _("No unsubscribed folders for user %s") % (user) + print(_("No unsubscribed folders for user %s") % (user)) else: if not conf.raw: - print "\n".join([imap_utf7.decode(x) for x in subscribed_folders]) + print("\n".join([imap_utf7.decode(x) for x in subscribed_folders])) else: - print "\n".join(subscribed_folders) + print("\n".join(subscribed_folders)) diff --git a/pykolab/cli/cmd_list_users.py b/pykolab/cli/cmd_list_users.py index ff1ddef..a17eeba 100644 --- a/pykolab/cli/cmd_list_users.py +++ b/pykolab/cli/cmd_list_users.py @@ -1,39 +1,39 @@ # -*- coding: utf-8 -*- # Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import commands import pykolab from pykolab import utils from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_users', execute, description="List organizational units.") def execute(*args, **kw): from pykolab import wap_client wap_client.authenticate(username=conf.get("ldap", "bind_dn"), password=conf.get("ldap", "bind_pw")) users = wap_client.users_list() - print '\n'.join(users['list'].keys()) + print('\n'.join(users['list'].keys())) diff --git a/pykolab/cli/cmd_remove_mailaddress.py b/pykolab/cli/cmd_remove_mailaddress.py index af64609..bc94bb1 100644 --- a/pykolab/cli/cmd_remove_mailaddress.py +++ b/pykolab/cli/cmd_remove_mailaddress.py @@ -1,93 +1,95 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.auth import Auth from pykolab import utils from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('remove_mail', execute, description=description()) def description(): return """Remove a recipient's mail address.""" def execute(*args, **kw): try: email_address = conf.cli_args.pop(0) except IndexError, errmsg: email_address = utils.ask_question("Email address to remove") # Get the domain from the email address if len(email_address.split('@')) > 1: domain = email_address.split('@')[1] else: log.error(_("Invalid or unqualified email address.")) sys.exit(1) auth = Auth() auth.connect(domain=domain) recipients = auth.find_recipient(email_address) if len(recipients) == 0: log.error(_("No recipient found for email address %r") % (email_address)) sys.exit(1) log.debug(_("Found the following recipient(s): %r") % (recipients), level=8) mail_attributes = conf.get_list(domain, 'mail_attributes') if mail_attributes == None or len(mail_attributes) < 1: mail_attributes = conf.get_list(conf.get('kolab', 'auth_mechanism'), 'mail_attributes') log.debug(_("Using the following mail attributes: %r") % (mail_attributes), level=8) if isinstance(recipients, basestring): recipient = recipients # Only a single recipient found, remove the address attributes = auth.get_entry_attributes(domain, recipient, mail_attributes) # See which attribute holds the value we're trying to remove for attribute in attributes.keys(): if isinstance(attributes[attribute], list): if email_address in attributes[attribute]: attributes[attribute].pop(attributes[attribute].index(email_address)) replace_attributes = { attribute: attributes[attribute] } auth.set_entry_attributes(domain, recipient, replace_attributes) else: if email_address == attributes[attribute]: auth.set_entry_attributes(domain, recipient, {attribute: None}) pass else: - print >> sys.stderr, _("Found the following recipients:") + print(_("Found the following recipients:"), file=sys.stderr) for recipient in recipients: - print recipient + print(recipient) diff --git a/pykolab/cli/cmd_remove_user_subscription.py b/pykolab/cli/cmd_remove_user_subscription.py index 59975c8..343ee68 100644 --- a/pykolab/cli/cmd_remove_user_subscription.py +++ b/pykolab/cli/cmd_remove_user_subscription.py @@ -1,101 +1,103 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ from pykolab import utils log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register( 'remove_user_subscription', execute, aliases=['rus', 'unsubscribe'], description=description() ) def description(): return _("Unsubscribe a user from a folder.") def execute(*args, **kw): folder_pattern = "*" try: user = conf.cli_args.pop(0) try: folder_pattern = conf.cli_args.pop(0) except IndexError, errmsg: folder_pattern = utils.ask_question(_("Folder pattern")) except IndexError, errmsg: user = utils.ask_question(_("User ID")) folder_pattern = utils.ask_question(_("Folder pattern")) if len(user.split('@')) > 1: domain = user.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') imap = IMAP() imap.connect(domain=domain, login=False) backend = conf.get(domain, 'imap_backend') if backend == None: backend = conf.get('kolab', 'imap_backend') admin_login = conf.get(backend, 'admin_login') admin_password = conf.get(backend, 'admin_password') imap.login_plain(admin_login, admin_password, user) if not imap.has_folder(folder_pattern): - print >> sys.stderr, \ - _("Cannot subscribe user to folder %r:") % (folder_pattern), \ - _("No such folder") + print(_("Cannot subscribe user to folder %r:") % (folder_pattern), \ + _("No such folder"), \ + file=sys.stderr) sys.exit(1) _folders = imap.lm(folder_pattern) _subscribed_folders = imap.lsub() unsubscribed_folders = [] for _folder in _folders: if _folder in _subscribed_folders: imap.unsubscribe(_folder) unsubscribed_folders.append(_folder) if len(unsubscribed_folders) > 0: - print _("Successfully unsubscribed user %s from the following folders:") % ( + print(_("Successfully unsubscribed user %s from the following folders:") % ( user - ) + )) - print "\n".join(unsubscribed_folders) + print("\n".join(unsubscribed_folders)) else: - print >> sys.stderr, _("User %s was not unsubscribed from any folders.") % ( + print(_("User %s was not unsubscribed from any folders.") % ( user - ) + ), file=sys.stderr) sys.exit(1) diff --git a/pykolab/cli/cmd_rename_mailbox.py b/pykolab/cli/cmd_rename_mailbox.py index 7432e08..c693211 100644 --- a/pykolab/cli/cmd_rename_mailbox.py +++ b/pykolab/cli/cmd_rename_mailbox.py @@ -1,74 +1,76 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('rename_mailbox', execute, description=description(), aliases=['rm']) def description(): return """Rename a mailbox or sub-folder.""" def execute(*args, **kw): """ Rename mailbox """ try: source_folder = conf.cli_args.pop(0) try: target_folder = conf.cli_args.pop(0) try: partition = conf.cli_args.pop(0) except IndexError, errmsg: partition = None except IndexError, errmsg: - print >> sys.stderr, _("No target mailbox name specified") + print(_("No target mailbox name specified"), file=sys.stderr) except IndexError, errmsg: - print >> sys.stderr, _("No source mailbox name specified") + print(_("No source mailbox name specified"), file=sys.stderr) sys.exit(1) if len(source_folder.split('@')) > 1: domain = source_folder.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') imap = IMAP() imap.connect(domain=domain) if not imap.has_folder(source_folder): - print >> sys.stderr, _("Source folder %r does not exist") % (source_folder) + print(_("Source folder %r does not exist") % (source_folder), file=sys.stderr) sys.exit(1) if imap.has_folder(target_folder) and partition == None: - print >> sys.stderr, _("Target folder %r already exists") % (target_folder) + print(_("Target folder %r already exists") % (target_folder), file=sys.stderr) sys.exit(1) imap.imap.rename(imap.folder_utf7(source_folder), imap.folder_utf7(target_folder), partition) diff --git a/pykolab/cli/cmd_server_info.py b/pykolab/cli/cmd_server_info.py index e512fcc..0ae0c0e 100644 --- a/pykolab/cli/cmd_server_info.py +++ b/pykolab/cli/cmd_server_info.py @@ -1,58 +1,58 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('server_info', execute, description=description()) def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--server', dest = "connect_server", action = "store", default = None, metavar = "SERVER", help = _("List mailboxes on server SERVER only.")) def description(): return "Display server info.\n" def execute(*args, **kw): """ List mailboxes """ imap = IMAP() if not conf.connect_server == None: imap.connect(server=conf.connect_server) else: imap.connect() - print imap.get_metadata("") + print(imap.get_metadata("")) diff --git a/pykolab/cli/cmd_set_mailbox_acl.py b/pykolab/cli/cmd_set_mailbox_acl.py index 379a8c3..249c1ee 100644 --- a/pykolab/cli/cmd_set_mailbox_acl.py +++ b/pykolab/cli/cmd_set_mailbox_acl.py @@ -1,72 +1,74 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ from pykolab import utils log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('set_mailbox_acl', execute, description=description(), aliases=['sam']) def description(): return """Set an ACL for a identifier on a folder.""" def execute(*args, **kw): try: folder = conf.cli_args.pop(0) try: identifier = conf.cli_args.pop(0) try: acl = conf.cli_args.pop(0) except IndexError, errmsg: acl = utils.ask_question(_("ACI Permissions")) except IndexError, errmsg: identifier = utils.ask_question(_("ACI Subject")) acl = utils.ask_question(_("ACI Permissions")) except IndexError, errmsg: folder = utils.ask_question(_("Folder name")) identifier = utils.ask_question(_("ACI Subject")) acl = utils.ask_question(_("ACI Permissions")) if len(folder.split('@')) > 1: domain = folder.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') imap = IMAP() imap.connect(domain=domain) if not imap.has_folder(folder): - print >> sys.stderr, _("No such folder %r") % (folder) + print(_("No such folder %r") % (folder), file=sys.stderr) else: folders = imap.list_folders(folder) for folder in folders: imap.set_acl(folder, identifier, acl) diff --git a/pykolab/cli/cmd_set_mailbox_metadata.py b/pykolab/cli/cmd_set_mailbox_metadata.py index 0b7b1fe..d63e6a6 100644 --- a/pykolab/cli/cmd_set_mailbox_metadata.py +++ b/pykolab/cli/cmd_set_mailbox_metadata.py @@ -1,99 +1,101 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ from pykolab import imap_utf7 from pykolab import utils log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('set_mailbox_metadata', execute, description=description()) def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--user', dest = "user", action = "store", default = None, metavar = "USER", help = _("Set annotation as user USER") ) def description(): return """Set an metadata entry on a folder.""" def execute(*args, **kw): try: folder = conf.cli_args.pop(0) try: metadata_path = conf.cli_args.pop(0) try: metadata_value = conf.cli_args.pop(0) except IndexError, errmsg: metadata_value = utils.ask_question(_("Metadata value")) except IndexError, errmsg: metadata_path = utils.ask_question(_("Metadata path")) metadata_value = utils.ask_question(_("Metadata value")) except IndexError, errmsg: folder = utils.ask_question(_("Folder name")) metadata_path = utils.ask_question(_("Metadata path")) metadata_value = utils.ask_question(_("Metadata value")) if len(folder.split('@')) > 1: domain = folder.split('@')[1] elif not conf.user == None and len(conf.user.split('@')) > 1: domain = conf.user.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') imap = IMAP() if not conf.user == None: imap.connect(domain=domain, login=False) backend = conf.get(domain, 'imap_backend') if backend == None: backend = conf.get('kolab', 'imap_backend') admin_login = conf.get(backend, 'admin_login') admin_password = conf.get(backend, 'admin_password') imap.login_plain(admin_login, admin_password, conf.user) else: imap.connect(domain=domain) if not imap.has_folder(folder): - print >> sys.stderr, _("No such folder %r") % (folder) + print(_("No such folder %r") % (folder), file=sys.stderr) else: folders = imap.lm(imap_utf7.encode(folder)) for folder in folders: imap.set_metadata(imap_utf7.decode(folder), metadata_path, metadata_value) diff --git a/pykolab/cli/cmd_set_quota.py b/pykolab/cli/cmd_set_quota.py index 2fa9ba3..5b7b249 100644 --- a/pykolab/cli/cmd_set_quota.py +++ b/pykolab/cli/cmd_set_quota.py @@ -1,66 +1,68 @@ # -*- coding: utf-8 -*- # Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 or, at your option, any later version # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +from __future__ import print_function + import sys import commands import pykolab from pykolab.imap import IMAP from pykolab.translate import _ from pykolab import utils log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('set_quota', execute, description=description(), aliases=['sq']) def description(): return """Configure quota for a folder.""" def execute(*args, **kw): try: folder = conf.cli_args.pop(0) try: quota = conf.cli_args.pop(0) except IndexError, errmsg: quota = utils.ask_question(_("New quota")) except IndexError, errmsg: folder = utils.ask_question(_("Folder name")) quota = utils.ask_question(_("New quota")) if len(folder.split('@')) > 1: domain = folder.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') imap = IMAP() imap.connect(domain=domain) if not imap.has_folder(folder): - print >> sys.stderr, _("No such folder %r") % (folder) + print(_("No such folder %r") % (folder), file=sys.stderr) sys.exit(1) for _folder in imap.lm(imap.folder_utf7(folder)): imap.set_quota(_folder, quota) - print >> sys.stdout, "Quota for folder '%s' set to %d" % (_folder, int(quota)) + print("Quota for folder '%s' set to %d" % (_folder, int(quota)), file=sys.stdout) diff --git a/pykolab/cli/cmd_sync_mailhost_attrs.py b/pykolab/cli/cmd_sync_mailhost_attrs.py index 3f5a731..512265b 100644 --- a/pykolab/cli/cmd_sync_mailhost_attrs.py +++ b/pykolab/cli/cmd_sync_mailhost_attrs.py @@ -1,196 +1,196 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import commands import pykolab from pykolab import imap_utf7 from pykolab.auth import Auth from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('sync_mailhost_attrs', execute, description=description()) def description(): return "Synchronize mailHost attribute values with the actual mailserver in a Cyrus IMAP Murder.\n" def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("CLI Options")) my_option_group.add_option( '--delete', dest = "delete", action = "store_true", default = False, help = _("Delete mailboxes for recipients that do not appear to exist in LDAP.")) my_option_group.add_option( '--dry-run', dest = "dry_run", action = "store_true", default = False, help = _("Display changes, do not apply them.")) my_option_group.add_option( '--server', dest = "connect_server", action = "store", default = None, metavar = "SERVER", help = _("List mailboxes on server SERVER only.")) def execute(*args, **kw): """ Synchronize or display changes """ imap = IMAP() if not conf.connect_server == None: imap.connect(server=conf.connect_server) else: imap.connect() auth = Auth() auth.connect() result_attribute = conf.get('cyrus-sasl', 'result_attribute') if result_attribute is None: result_attribute = 'mail' domains = auth.list_domains() folders = imap.lm() imap_domains_not_domains = [] for folder in folders: if len(folder.split('@')) > 1 and not folder.startswith('DELETED'): _folder_domain = folder.split('@')[-1] if not _folder_domain in list(set(domains.keys() + domains.values())): imap_domains_not_domains.append(folder.split('@')[-1]) imap_domains_not_domains = list(set(imap_domains_not_domains)) log.debug(_("Domains in IMAP not in LDAP: %r") % (imap_domains_not_domains), level=8) if len(imap_domains_not_domains) > 0: for domain in imap_domains_not_domains: folders = [] folders.extend(imap.lm('shared/%%@%s' % (domain))) folders.extend(imap.lm('user/%%@%s' % (domain))) for folder in folders: r_folder = folder if not folder.startswith('shared/'): r_folder = '/'.join(folder.split('/')[1:]) if conf.delete: if conf.dry_run: if not folder.startswith('shared/'): log.warning(_("No recipients for '%s' (would have deleted the mailbox if not for --dry-run)!") % (r_folder)) else: continue else: if not folder.startswith('shared/'): log.info(_("Deleting mailbox '%s' because it has no recipients") % (folder)) try: imap.dm(folder) except Exception, errmsg: log.error(_("An error occurred removing mailbox %r: %r") % (folder, errmsg)) else: log.info(_("Not automatically deleting shared folder '%s'") % (folder)) else: log.warning(_("No recipients for '%s' (use --delete to delete)!") % (r_folder)) for primary in list(set(domains.values())): secondaries = [x for x in domains.keys() if domains[x] == primary] folders = [] folders.extend(imap.lm('shared/%%@%s' % (primary))) folders.extend(imap.lm('user/%%@%s' % (primary))) for secondary in secondaries: folders.extend(imap.lm('shared/%%@%s' % (secondary))) folders.extend(imap.lm('user/%%@%s' % (secondary))) auth = Auth(domain=primary) auth.connect() for folder in folders: server = imap.user_mailbox_server(folder) r_folder = folder if folder.startswith('shared/'): recipient = auth.find_folder_resource(folder) else: r_folder = '/'.join(folder.split('/')[1:]) recipient = auth.find_recipient(r_folder, search_attrs=[result_attribute]) if (isinstance(recipient, list)): if len(recipient) > 1: log.warning(_("Multiple recipients for '%s'!") % (r_folder)) continue elif len(recipient) == 0: if conf.delete: if conf.dry_run: if not folder.startswith('shared/'): log.warning(_("No recipients for '%s' (would have deleted the mailbox if not for --dry-run)!") % (r_folder)) else: continue else: if not folder.startswith('shared/'): log.info(_("Deleting mailbox '%s' because it has no recipients") % (folder)) try: imap.dm(folder) except Exception, errmsg: log.error(_("An error occurred removing mailbox %r: %r") % (folder, errmsg)) else: log.info(_("Not automatically deleting shared folder '%s'") % (folder)) else: log.warning(_("No recipients for '%s' (use --delete to delete)!") % (r_folder)) continue else: mailhost = auth.get_entry_attribute(primary, recipient, 'mailhost') if not server == mailhost: if conf.dry_run: - print folder, server, mailhost + print(folder, server, mailhost) else: auth.set_entry_attribute(primary, recipient, 'mailhost', server) folders = [] folders.extend(imap.lm("shared/%%")) folders.extend(imap.lm("user/%%")) auth = Auth() auth.connect() for folder in folders: server = imap.user_mailbox_server(folder) if folder.startswith('shared/'): recipient = auth.find_folder_resource(folder) else: recipient = auth.find_recipient('/'.join(folder.split('/')[1:]), search_attrs=[result_attribute]) - print folder, server, recipient + print(folder, server, recipient) diff --git a/pykolab/cli/cmd_user_info.py b/pykolab/cli/cmd_user_info.py index d7b83a6..a7879d1 100644 --- a/pykolab/cli/cmd_user_info.py +++ b/pykolab/cli/cmd_user_info.py @@ -1,60 +1,62 @@ # -*- coding: utf-8 -*- # Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys import commands import pykolab from pykolab import utils from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('user_info', execute, description="Display user information.") def execute(*args, **kw): from pykolab import wap_client try: user = conf.cli_args.pop(0) except IndexError, errmsg: user = utils.ask_question(_("Email address")) result = wap_client.authenticate(username=conf.get("ldap", "bind_dn"), password=conf.get("ldap", "bind_pw")) if len(user.split('@')) > 1: wap_client.system_select_domain(user.split('@')[1]) user_info = wap_client.user_find({'mail':user}) if user_info == None or not user_info: - print >> sys.stderr, _("No such user %s") % (user) + print(_("No such user %s") % (user), file=sys.stderr) sys.exit(0) unic_attrs = ['displayname', 'givenname', 'cn', 'sn', 'ou', 'entrydn'] for (k,v) in user_info.iteritems(): if k in unic_attrs: - print "%s: %s" % (k,v) + print("%s: %s" % (k,v)) else: - print "%s: %r" % (k,v) + print("%s: %r" % (k,v)) diff --git a/pykolab/cli/commands.py b/pykolab/cli/commands.py index 515d40c..2f05bb6 100644 --- a/pykolab/cli/commands.py +++ b/pykolab/cli/commands.py @@ -1,201 +1,201 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import os import sys import pykolab from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() commands = {} command_groups = {} def __init__(): # We only want the base path commands_base_path = os.path.dirname(__file__) for commands_path, dirnames, filenames in os.walk(commands_base_path): if not commands_path == commands_base_path: continue for filename in filenames: if filename.startswith('cmd_') and filename.endswith('.py'): module_name = filename.replace('.py','') cmd_name = module_name.replace('cmd_', '') #print "exec(\"from %s import __init__ as %s_register\"" % (module_name,cmd_name) try: exec("from %s import __init__ as %s_register" % (module_name,cmd_name)) except ImportError, errmsg: pass exec("%s_register()" % (cmd_name)) for dirname in dirnames: register_group(commands_path, dirname) register('help', list_commands) register('delete_user', not_yet_implemented, description="Not yet implemented") register('list_groups', not_yet_implemented, description="Not yet implemented") register('add_group', not_yet_implemented, description="Not yet implemented") register('delete_group', not_yet_implemented, description="Not yet implemented") def list_commands(*args, **kw): """ List commands """ __commands = {} for command in commands.keys(): if isinstance(command, tuple): command_group, command = command __commands[command_group] = { command: commands[(command_group,command)] } else: __commands[command] = commands[command] _commands = __commands.keys() _commands.sort() for _command in _commands: if __commands[_command].has_key('group'): continue if __commands[_command].has_key('function'): # This is a top-level command if not __commands[_command]['description'] == None: - print "%-25s - %s" % (_command.replace('_','-'),__commands[_command]['description']) + print("%-25s - %s" % (_command.replace('_','-'),__commands[_command]['description'])) else: - print "%-25s" % (_command.replace('_','-')) + print("%-25s" % (_command.replace('_','-'))) for _command in _commands: if not __commands[_command].has_key('function'): # This is a nested command - print "\n" + _("Command Group: %s") % (_command) + "\n" + print("\n" + _("Command Group: %s") % (_command) + "\n") ___commands = __commands[_command].keys() ___commands.sort() for __command in ___commands: if not __commands[_command][__command]['description'] == None: - print "%-4s%-21s - %s" % ('',__command.replace('_','-'),__commands[_command][__command]['description']) + print("%-4s%-21s - %s" % ('',__command.replace('_','-'),__commands[_command][__command]['description'])) else: - print "%-4s%-21s" % ('',__command.replace('_','-')) + print("%-4s%-21s" % ('',__command.replace('_','-'))) def execute(cmd_name, *args, **kw): if cmd_name == "": execute("help") sys.exit(0) if not commands.has_key(cmd_name): log.error(_("No such command.")) sys.exit(1) if not commands[cmd_name].has_key('function') and \ not commands[cmd_name].has_key('group'): log.error(_("No such command.")) sys.exit(1) if commands[cmd_name].has_key('group'): group = commands[cmd_name]['group'] command_name = commands[cmd_name]['cmd_name'] try: exec("from %s.cmd_%s import cli_options as %s_%s_cli_options" % (group,command_name,group,command_name)) exec("%s_%s_cli_options()" % (group,command_name)) except ImportError, e: pass else: command_name = commands[cmd_name]['cmd_name'] try: exec("from cmd_%s import cli_options as %s_cli_options" % (command_name,command_name)) exec("%s_cli_options()" % (command_name)) except ImportError, errmsg: pass conf.finalize_conf() commands[cmd_name]['function'](conf.cli_args, kw) def register_group(dirname, module): commands_base_path = os.path.join(os.path.dirname(__file__), module) commands[module] = {} for commands_path, dirnames, filenames in os.walk(commands_base_path): if not commands_path == commands_base_path: continue for filename in filenames: if filename.startswith('cmd_') and filename.endswith('.py'): module_name = filename.replace('.py','') cmd_name = module_name.replace('cmd_', '') #print "exec(\"from %s.%s import __init__ as %s_%s_register\"" % (module,module_name,module,cmd_name) exec("from %s.%s import __init__ as %s_%s_register" % (module,module_name,module,cmd_name)) exec("%s_%s_register()" % (module,cmd_name)) def register(cmd_name, func, group=None, description=None, aliases=[]): if not group == None: command = "%s_%s" % (group,cmd_name) else: command = cmd_name if isinstance(aliases, basestring): aliases = [aliases] if commands.has_key(command): log.fatal(_("Command '%s' already registered") % (command)) sys.exit(1) if callable(func): if group == None: commands[cmd_name] = { 'cmd_name': cmd_name, 'function': func, 'description': description } else: commands[group][cmd_name] = { 'cmd_name': cmd_name, 'function': func, 'description': description } commands[command] = commands[group][cmd_name] commands[command]['group'] = group commands[command]['cmd_name'] = cmd_name for alias in aliases: commands[alias] = { 'cmd_name': cmd_name, 'function': func, 'description': _("Alias for %s") % (cmd_name.replace('_','-')) } ## ## Commands not yet implemented ## def not_yet_implemented(*args, **kw): - print _("Not yet implemented") + print(_("Not yet implemented")) sys.exit(1) diff --git a/pykolab/cli/sieve/cmd_list.py b/pykolab/cli/sieve/cmd_list.py index d10517c..20c967a 100644 --- a/pykolab/cli/sieve/cmd_list.py +++ b/pykolab/cli/sieve/cmd_list.py @@ -1,121 +1,121 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import sys import time from urlparse import urlparse import pykolab from pykolab import utils from pykolab.auth import Auth from pykolab.cli import commands from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register( 'list', execute, group='sieve', description=description() ) def description(): return """List a user's sieve scripts.""" def execute(*args, **kw): try: address = conf.cli_args.pop(0) except: address = utils.ask_question(_("Email Address")) auth = Auth() auth.connect() user = auth.find_recipient(address) # Get the main, default backend backend = conf.get('kolab', 'imap_backend') if len(address.split('@')) > 1: domain = address.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') if conf.has_section(domain) and conf.has_option(domain, 'imap_backend'): backend = conf.get(domain, 'imap_backend') if conf.has_section(domain) and conf.has_option(domain, 'imap_uri'): uri = conf.get(domain, 'imap_uri') else: uri = conf.get(backend, 'uri') hostname = None port = None result = urlparse(uri) if hasattr(result, 'hostname'): hostname = result.hostname else: scheme = uri.split(':')[0] (hostname, port) = uri.split('/')[2].split(':') port = 4190 # Get the credentials admin_login = conf.get(backend, 'admin_login') admin_password = conf.get(backend, 'admin_password') import sievelib.managesieve sieveclient = sievelib.managesieve.Client( hostname, port, conf.debuglevel > 8 ) sieveclient.connect(None, None, True) result = sieveclient._plain_authentication( admin_login, admin_password, address ) if not result: - print "LOGIN FAILED??" + print("LOGIN FAILED??") sieveclient.authenticated = True result = sieveclient.listscripts() if result == None: - print "No scripts" + print("No scripts") sys.exit(0) (active, scripts) = result - print "%s (active)" % (active) + print("%s (active)" % (active)) for script in scripts: - print script + print(script) diff --git a/pykolab/cli/sieve/cmd_test.py b/pykolab/cli/sieve/cmd_test.py index 0c9421e..7906570 100644 --- a/pykolab/cli/sieve/cmd_test.py +++ b/pykolab/cli/sieve/cmd_test.py @@ -1,129 +1,129 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import pykolab from pykolab.auth import Auth from pykolab.cli import commands from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() import time from urlparse import urlparse def __init__(): commands.register('test', execute, group='sieve', description=description()) def description(): return """Syntactically check a user's sieve scripts.""" def execute(*args, **kw): try: address = conf.cli_args.pop(0) except: address = utils.ask_question(_("Email Address")) auth = Auth() auth.connect() user = auth.find_recipient(address) # Get the main, default backend backend = conf.get('kolab', 'imap_backend') if len(address.split('@')) > 1: domain = address.split('@')[1] else: domain = conf.get('kolab', 'primary_domain') if conf.has_section(domain) and conf.has_option(domain, 'imap_backend'): backend = conf.get(domain, 'imap_backend') if conf.has_section(domain) and conf.has_option(domain, 'imap_uri'): uri = conf.get(domain, 'imap_uri') else: uri = conf.get(backend, 'uri') hostname = None port = None result = urlparse(uri) if hasattr(result, 'hostname'): hostname = result.hostname else: scheme = uri.split(':')[0] (hostname, port) = uri.split('/')[2].split(':') port = 4190 # Get the credentials admin_login = conf.get(backend, 'admin_login') admin_password = conf.get(backend, 'admin_password') import sievelib.managesieve sieveclient = sievelib.managesieve.Client(hostname, port, True) sieveclient.connect(None, None, True) sieveclient._plain_authentication(admin_login, admin_password, address) sieveclient.authenticated = True active, scripts = sieveclient.listscripts() - print "%s (active)" % (active) + print("%s (active)" % (active)) _all_scripts = [ active ] + scripts _used_scripts = [ active ] _included_scripts = [] _a_script = sieveclient.getscript(active) - print _a_script + print(_a_script) import sievelib.parser _a_parser = sievelib.parser.Parser(debug=True) _a_parsed = _a_parser.parse(_a_script) #print "%r" % (_a_parsed) if not _a_parsed: - print _a_parser.error + print(_a_parser.error) - print "%r" % (_a_parser.result) + print("%r" % (_a_parser.result)) for _a_command in _a_parser.result: - print _a_command.name, _a_command.arguments + print(_a_command.name, _a_command.arguments) if len(_a_command.children) > 0: for _a_child in _a_command.children: - print " ", _a_child.name, _a_child.arguments + print(" ", _a_child.name, _a_child.arguments) if _a_command.name == "include": if _a_command.arguments["script"].strip('"') in scripts: - print "OK" + print("OK") _used_scripts.append(_a_command.arguments["script"].strip('"')) else: - print "Not OK" + print("Not OK") for script in scripts: - print script + print(script) diff --git a/pykolab/cli/telemetry/cmd_examine_command_issue.py b/pykolab/cli/telemetry/cmd_examine_command_issue.py index 9563dbc..15d8673 100644 --- a/pykolab/cli/telemetry/cmd_examine_command_issue.py +++ b/pykolab/cli/telemetry/cmd_examine_command_issue.py @@ -1,123 +1,123 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import pykolab from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() from pykolab import telemetry from pykolab.cli import commands def __init__(): commands.register('examine_command_issue', execute, group='telemetry', description="Examine a particular telemetry command issue.") def execute(*args, **kw): db = telemetry.init_db() try: wanted = conf.cli_args.pop(0) except: log.error(_("Unspecified command issue identifier")) sys.exit(1) command_issue = db.query( telemetry.TelemetryCommandIssue ).filter_by( id=wanted ).first() if command_issue == None: log.error(_("Invalid command issue identifier")) sys.exit(1) session = db.query( telemetry.TelemetrySession ).filter_by( id=command_issue.session_id ).first() if session == None: log.error(_("Invalid session identifier")) sys.exit(1) user = db.query( telemetry.TelemetryUser ).filter_by( id=session.user_id ).first() server = db.query( telemetry.TelemetryServer ).filter_by( id=session.server_id ).first() - print _("Session by %s on server %s") % (user.sasl_username,server.fqdn) + print(_("Session by %s on server %s") % (user.sasl_username,server.fqdn)) command_issues = db.query( telemetry.TelemetryCommandIssue ).filter_by( session_id=session.id ) for _command_issue in command_issues: command = db.query( telemetry.TelemetryCommand ).filter_by( id=_command_issue.command_id ).first() command_arg = db.query( telemetry.TelemetryCommandArg ).filter_by( id=_command_issue.command_arg_id ).first() if command_issue.id == _command_issue.id: - print "=========" + print("=========") - print "Client(%d): %s %s %s" % ( + print("Client(%d): %s %s %s" % ( _command_issue.id, _command_issue.command_tag, command.command, command_arg.command_arg - ) + )) server_responses = db.query( telemetry.TelemetryServerResponse ).filter_by( command_issue_id=_command_issue.id ) for server_response in server_responses: server_response_lines = server_response.response.split('\n'); for server_response_line in server_response_lines: - print "Server(%d): %s" % ( + print("Server(%d): %s" % ( server_response.id, server_response_line - ) + )) if command_issue.id == _command_issue.id: - print "=========" + print("=========") diff --git a/pykolab/cli/telemetry/cmd_examine_session.py b/pykolab/cli/telemetry/cmd_examine_session.py index cf09b74..d6851d5 100644 --- a/pykolab/cli/telemetry/cmd_examine_session.py +++ b/pykolab/cli/telemetry/cmd_examine_session.py @@ -1,141 +1,141 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import pykolab from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() from pykolab import telemetry from pykolab.cli import commands def __init__(): commands.register('examine_session', execute, group='telemetry', description="Examine a Telemetry session.") def execute(*args, **kw): db = telemetry.init_db() wanted = False if session_id == None: try: wanted = conf.cli_args.pop(0) except: log.error(_("Unspecified session identifier")) sys.exit(1) if not wanted: wanted = session_id session_wanted = None try: _wanted = (int)(wanted) session_wanted = _wanted except: user_wanted = wanted if not session_wanted == None: session = db.query( telemetry.TelemetrySession ).filter_by( id=session_wanted ).first() if session == None: log.error(_("Invalid session identifier")) sys.exit(1) user = db.query( telemetry.TelemetryUser ).filter_by( id=session.user_id ).first() server = db.query( telemetry.TelemetryServer ).filter_by( id=session.server_id ).first() else: user = db.query( telemetry.TelemetryUser ).filter_by( sasl_username=user_wanted ).first() sessions = db.query( telemetry.TelemetrySession ).filter_by( user_id=user.id ).order_by( telemetry.telemetry_session_table.c.start ) for session in sessions: self.action_telemetry_examine_session(session_id=session.id) return - print _("Session by %s on server %s") % (user.sasl_username,server.fqdn) + print(_("Session by %s on server %s") % (user.sasl_username,server.fqdn)) command_issues = db.query( telemetry.TelemetryCommandIssue ).filter_by( session_id=session.id ) for command_issue in command_issues: command = db.query( telemetry.TelemetryCommand ).filter_by( id=command_issue.command_id ).first() command_arg = db.query( telemetry.TelemetryCommandArg ).filter_by( id=command_issue.command_arg_id ).first() - print "Client(%d): %s %s %s" % ( + print("Client(%d): %s %s %s" % ( command_issue.id, command_issue.command_tag, command.command, command_arg.command_arg - ) + )) server_responses = db.query( telemetry.TelemetryServerResponse ).filter_by( command_issue_id=command_issue.id ) for server_response in server_responses: server_response_lines = server_response.response.split('\n'); for server_response_line in server_response_lines: - print "Server(%d): %s" % ( + print("Server(%d): %s" % ( server_response.id, server_response_line - ) + )) diff --git a/pykolab/cli/telemetry/cmd_list_sessions.py b/pykolab/cli/telemetry/cmd_list_sessions.py index 6ed0ab7..773856f 100644 --- a/pykolab/cli/telemetry/cmd_list_sessions.py +++ b/pykolab/cli/telemetry/cmd_list_sessions.py @@ -1,63 +1,63 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import pykolab from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() from pykolab import telemetry from pykolab.cli import commands def __init__(): commands.register('list_sessions', execute, group='telemetry', description="List IMAP sessions using Telemetry.") def cli_options(): my_option_group = conf.add_cli_parser_option_group(_("List Options")) my_option_group.add_option( '--since', dest = "since", action = "store", default = 0, help = _("Display sessions since ...")) def execute(*args, **kw): db = telemetry.init_db() sessions = db.query( telemetry.TelemetrySession ).order_by( telemetry.telemetry_session_table.c.start ) for session in sessions: user = db.query( telemetry.TelemetryUser ).filter_by( id=session.user_id ).first() - print _("Session for user %s started at %s with ID %s") % ( + print(_("Session for user %s started at %s with ID %s") % ( user.sasl_username, session.start, session.id - ) + )) diff --git a/pykolab/cli/wap/cmd_system_capabilities.py b/pykolab/cli/wap/cmd_system_capabilities.py index 3cabdd7..6e8a06e 100644 --- a/pykolab/cli/wap/cmd_system_capabilities.py +++ b/pykolab/cli/wap/cmd_system_capabilities.py @@ -1,50 +1,50 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import sys import pykolab from pykolab.cli import commands from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('system_capabilities', execute, group='wap', description="Display the system capabilities.") def execute(*args, **kw): from pykolab import wap_client # Create the authentication object. # TODO: Binds with superuser credentials! wap_client.authenticate() system_capabilities = wap_client.system_capabilities() if system_capabilities['count'] < 1: - print "No system capabilities" + print("No system capabilities") sys.exit(1) for domain in system_capabilities['list'].keys(): - print "Domain capabilities for %s" % (domain) + print("Domain capabilities for %s" % (domain)) domain_capabilities = system_capabilities['list'][domain] for service in domain_capabilities['actions'].keys(): - print " %-15s - %r" % (service, domain_capabilities['actions'][service]['type']) + print(" %-15s - %r" % (service, domain_capabilities['actions'][service]['type'])) diff --git a/pykolab/cli/wap/cmd_user_types_list.py b/pykolab/cli/wap/cmd_user_types_list.py index ee1e7f9..0136781 100644 --- a/pykolab/cli/wap/cmd_user_types_list.py +++ b/pykolab/cli/wap/cmd_user_types_list.py @@ -1,40 +1,40 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import pykolab from pykolab.cli import commands from pykolab.translate import _ log = pykolab.getLogger('pykolab.cli') conf = pykolab.getConf() def __init__(): commands.register('list_user_types', execute, group='wap', description="List WAP user types.") def execute(*args, **kw): from pykolab import wap_client # Create the authentication object. # TODO: Binds with superuser credentials! wap_client.authenticate() user_types = wap_client.user_types_list() for user_type in user_types['list']: type = user_types['list'][user_type] - print "%-15s - %s" % (type['key'], type['description']) + print("%-15s - %s" % (type['key'], type['description'])) diff --git a/pykolab/constants.py.in b/pykolab/constants.py.in index 9b06666..0cd1f6d 100644 --- a/pykolab/constants.py.in +++ b/pykolab/constants.py.in @@ -1,126 +1,126 @@ # -*- coding: utf-8 -*- # Copyright 2010 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import math import socket import sys from pykolab.translate import _ __license__ = "GPLv2+" __copyright__ = "Kolab Systems AG" __version__ = '@VERSION@' __release__ = '@RELEASE@' if @RELEASE@ < 1: __status__ = 'snapshot' elif math.floor((float)(@RELEASE@)) < (float)(@RELEASE@): __status__ = 'prerelease' else: __status__ = 'stable' domain = 'pykolab' epilog = _("PyKolab is a Kolab Systems product. For more information about Kolab or PyKolab, visit http://www.kolabsys.com") COMPONENTS = [ 'imap', 'ldap', 'mta' ] hostname = socket.gethostname() fqdn = socket.getfqdn() try: domain_parts = fqdn.split('.') if len(domain_parts) < 3: - print >> sys.stderr, _("WARNING") + ": " + _("The Fully Qualified Domain Name or FQDN for this system is incorrect. Falling back to 'localdomain'.") + print(_("WARNING") + ": " + _("The Fully Qualified Domain Name or FQDN for this system is incorrect. Falling back to 'localdomain'."), file=sys.stderr) domainname = "localdomain" else: domainname = '.'.join(domain_parts[1:]) except IndexError: domainname = "localdomain" # The system RC directory RC_DIR = "/etc/rc.d/init.d/" KOLAB_LIB_PATH = '/var/lib/kolab/' # Service map; # # Convert names of registered system services to their type. For example, # on Red Hat, OpenLDAP is 'slapd', whereas on Debian, OpenLDAP is 'ldap'. # SERVICE_MAP = { 'dirsrv': { 'type': '389ds', 'description': _('389 Directory Server or Red Hat Directory Server') }, 'ldap': { 'type': 'openldap', 'description': _('OpenLDAP or compatible') }, 'slapd': { 'type': 'openldap', 'description': _('OpenLDAP or compatible') }, } import ldap LDAP_SCOPE = { 'base': ldap.SCOPE_BASE, 'sub': ldap.SCOPE_SUBTREE, 'one': ldap.SCOPE_ONELEVEL } SUPPORTED_LDAP_CONTROLS = { 0: { 'desc': 'Persistent Search Control', 'oid': '2.16.840.1.113730.3.4.3', 'func': '_persistent_search' }, 1: { 'desc': 'OpenLDAP Syncrepl (RFC4533)', 'oid': '1.3.6.1.4.1.4203.1.9.1.1', 'func': '_sync_repl' }, 2: { 'desc': 'Simple Paged Results Control', 'oid': '1.2.840.113556.1.4.319', 'func': '_paged_search' }, 3: { 'desc': 'Virtual List View Control', 'oid': '2.16.840.1.113730.3.4.9', 'func': '_vlv_search' }, } #SUPPORTED_LDAP_CONTROLS = { # 0: { # 'desc': 'OpenLDAP Syncrepl (RFC4533)', # 'oid': '1.3.6.1.4.1.4203.1.9.1.1', # 'func': '_sync_repl' # } # } # Binay attributes which should not be stripped BINARY_ATTRS = ( 'objectguid', 'objectsid' ) diff --git a/pykolab/imap/cyrus.py b/pykolab/imap/cyrus.py index 15a7d00..3f4c338 100644 --- a/pykolab/imap/cyrus.py +++ b/pykolab/imap/cyrus.py @@ -1,631 +1,631 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import cyruslib import sys import time from urlparse import urlparse import pykolab from pykolab import constants from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.imap') conf = pykolab.getConf() class Cyrus(cyruslib.CYRUS): """ Abstraction class for some common actions to do exclusively in Cyrus. For example, the following functions require the commands to be executed against the backend server if a murder is being used. - Setting quota - Renaming the top-level mailbox - Setting annotations """ setquota = cyruslib.CYRUS.sq def __init__(self, uri): """ Initialize this class, but do not connect yet. """ port = None result = urlparse(uri) if hasattr(result, 'hostname'): scheme = result.scheme hostname = result.hostname port = result.port else: scheme = uri.split(':')[0] (hostname, port) = uri.split('/')[2].split(':') if not port: if scheme == 'imap': port = 143 else: port = 993 self.server = hostname self.uri = "%s://%s:%s" % (scheme, hostname, port) while 1: try: cyruslib.CYRUS.__init__(self, self.uri) break except cyruslib.CYRUSError: log.warning( _("Could not connect to Cyrus IMAP server %r") % ( self.uri ) ) time.sleep(10) if conf.debuglevel > 8: self.VERBOSE = True self.m.debug = 5 sl = pykolab.logger.StderrToLogger(log) # imaplib debug outputs everything to stderr. Redirect to Logger sys.stderr = sl # cyruslib debug outputs everything to LOGFD. Redirect to Logger self.LOGFD = sl # Initialize our variables self.separator = self.SEP # Placeholder for known mailboxes on known servers self.mbox = {} def __del__(self): pass def connect(self, uri): """ Dummy connect function that checks if the server that we want to connect to is actually the server we are connected to. Uses pykolab.imap.IMAP.connect() in the background. """ port = None result = urlparse(uri) if hasattr(result, 'hostname'): scheme = result.scheme hostname = result.hostname port = result.port else: scheme = uri.split(':')[0] (hostname, port) = uri.split('/')[2].split(':') if not port: if scheme == 'imap': port = 143 else: port = 993 if hostname == self.server: return imap = IMAP() imap.connect(uri=uri) if not self.SEP == self.separator: self.separator = self.SEP def login(self, *args, **kw): """ Login to the Cyrus IMAP server through cyruslib.CYRUS, but set our hierarchy separator. """ try: cyruslib.CYRUS.login(self, *args, **kw) except cyruslib.CYRUSError, errmsg: log.error("Login to Cyrus IMAP server failed: %r", errmsg) except Exception, errmsg: log.exception(errmsg) self.separator = self.SEP try: self._id() except Exception, errmsg: pass log.debug( _("Continuing with separator: %r") % (self.separator), level=8 ) self.murder = False for capability in self.m.capabilities: if capability.startswith("MUPDATE="): log.debug( _("Detected we are running in a Murder topology"), level=8 ) self.murder = True if not self.murder: log.debug( _("This system is not part of a murder topology"), level=8 ) def find_mailfolder_server(self, mailfolder): annotations = {} _mailfolder = self.parse_mailfolder(mailfolder) prefix = _mailfolder['path_parts'][0] mbox = _mailfolder['path_parts'][1] if _mailfolder['domain'] is not None: mailfolder = "%s%s%s@%s" % ( prefix, self.separator, mbox, _mailfolder['domain'] ) # TODO: Workaround for undelete if len(self.lm(mailfolder)) < 1 and 'hex_timestamp' in _mailfolder: mailfolder = self.folder_utf7("DELETED/%s%s%s@%s" % ( self.separator.join(_mailfolder['path_parts']), self.separator, _mailfolder['hex_timestamp'], _mailfolder['domain']) ) # TODO: Murder capabilities may have been suppressed using Cyrus IMAP # configuration. if not self.murder: return self.server log.debug( _("Checking actual backend server for folder %s " + "through annotations") % ( mailfolder ), level=8 ) if self.mbox.has_key(mailfolder): log.debug( _( "Possibly reproducing the find " + "mailfolder server answer from " + "previously detected and stored " + "annotation value: %r" ) % ( self.mbox[mailfolder] ), level=8 ) if not self.mbox[mailfolder] == self.server: return self.mbox[mailfolder] max_tries = 20 num_try = 0 ann_path = "/vendor/cmu/cyrus-imapd/server" s_ann_path = "/shared%s" % (ann_path) while 1: num_try += 1 annotations = self._getannotation( '"%s"' % (mailfolder), ann_path ) if annotations.has_key(mailfolder): if annotations[mailfolder].has_key(s_ann_path): break if max_tries <= num_try: log.error( _("Could not get the annotations after %s tries.") % ( num_try ) ) annotations = { mailfolder: { s_ann_path: self.server } } break log.warning( _("No annotations for %s: %r") % ( mailfolder, annotations ) ) time.sleep(1) server = annotations[mailfolder][s_ann_path] self.mbox[mailfolder] = server log.debug( _("Server for INBOX folder %s is %s") % ( mailfolder, server ), level=8 ) return server def folder_utf7(self, folder): from pykolab import imap_utf7 return imap_utf7.encode(folder) def folder_utf8(self, folder): from pykolab import imap_utf7 return imap_utf7.decode(folder) def _id(self, identity=None): if identity is None: identity = '("name" "Python/Kolab" "version" "%s")' % (constants.__version__) typ, dat = self.m._simple_command('ID', identity) res, dat = self.m._untagged_response(typ, dat, 'ID') def _setquota(self, mailfolder, quota): """ Login to the actual backend server. """ server = self.find_mailfolder_server(mailfolder) self.connect(self.uri.replace(self.server, server)) log.debug( _("Setting quota for folder %s to %s") % ( mailfolder, quota ), level=8 ) try: self.m.setquota(mailfolder, quota) except: log.error( _("Could not set quota for mailfolder %s") % ( mailfolder ) ) def _rename(self, from_mailfolder, to_mailfolder, partition=None): """ Login to the actual backend server, then rename. """ server = self.find_mailfolder_server(from_mailfolder) self.connect(self.uri.replace(self.server, server)) if partition is not None: log.debug( _("Moving INBOX folder %s to %s on partition %s") % ( from_mailfolder, to_mailfolder, partition ), level=8 ) else: log.debug( _("Moving INBOX folder %s to %s") % ( from_mailfolder, to_mailfolder ), level=8 ) self.m.rename( self.folder_utf7(from_mailfolder), self.folder_utf7(to_mailfolder), partition ) def _getannotation(self, *args, **kw): return self.getannotation(*args, **kw) def _setannotation(self, mailfolder, annotation, value, shared=False): """ Login to the actual backend server, then set annotation. """ try: server = self.find_mailfolder_server(mailfolder) except: server = self.server log.debug( _("Setting annotation %s on folder %s") % ( annotation, mailfolder ), level=8 ) try: self.setannotation(mailfolder, annotation, value, shared) except cyruslib.CYRUSError, errmsg: log.error( _("Could not set annotation %r on mail folder %r: %r") % ( annotation, mailfolder, errmsg ) ) def _xfer(self, mailfolder, current_server, new_server): self.connect(self.uri.replace(self.server, current_server)) log.debug( _("Transferring folder %s from %s to %s") % ( mailfolder, current_server, new_server ), level=8 ) self.xfer(mailfolder, new_server) def undelete_mailfolder( self, mailfolder, to_mailfolder=None, recursive=True ): """ Login to the actual backend server, then "undelete" the mailfolder. 'mailfolder' may be a string representing either of the following two options; - the fully qualified pathof the deleted folder in its current location, such as, for a deleted INBOX folder originally known as "user/userid[@domain]"; "DELETED/user/userid/hex[@domain]" - the original folder name, such as; "user/userid[@domain]" 'to_mailfolder' may be the target folder to "undelete" the deleted folder to. If not specified, the original folder name is used. """ # Placeholder for folders we have recovered already. target_folders = [] mailfolder = self.parse_mailfolder(mailfolder) undelete_folders = self._find_deleted_folder(mailfolder) if to_mailfolder is not None: target_mbox = self.parse_mailfolder(to_mailfolder) else: target_mbox = mailfolder for undelete_folder in undelete_folders: undelete_mbox = self.parse_mailfolder(undelete_folder) prefix = undelete_mbox['path_parts'].pop(0) mbox = undelete_mbox['path_parts'].pop(0) if to_mailfolder is None: target_folder = self.separator.join([prefix, mbox]) else: target_folder = self.separator.join(target_mbox['path_parts']) if to_mailfolder is not None: target_folder = "%s%s%s" % ( target_folder, self.separator, mbox ) if not len(undelete_mbox['path_parts']) == 0: target_folder = "%s%s%s" % ( target_folder, self.separator, self.separator.join(undelete_mbox['path_parts']) ) if target_folder in target_folders: target_folder = "%s%s%s" % ( target_folder, self.separator, undelete_mbox['hex_timestamp'] ) target_folders.append(target_folder) if target_mbox['domain'] is not None: target_folder = "%s@%s" % ( target_folder, target_mbox['domain'] ) log.info( _("Undeleting %s to %s") % ( undelete_folder, target_folder ) ) target_server = self.find_mailfolder_server(target_folder) source_server = self.find_mailfolder_server(undelete_folder) if hasattr(conf, 'dry_run') and not conf.dry_run: undelete_folder = self.folder_utf7(undelete_folder) target_folder = self.folder_utf7(target_folder) if not target_server == source_server: self.xfer(undelete_folder, target_server) self.rename(undelete_folder, target_folder) else: if not target_server == source_server: - print >> sys.stdout, \ - _("Would have transferred %s from %s to %s") % ( + print(_("Would have transferred %s from %s to %s") % ( undelete_folder, source_server, target_server - ) + ), file=sys.stdout) - print >> sys.stdout, \ - _("Would have renamed %s to %s") % ( + print(_("Would have renamed %s to %s") % ( undelete_folder, target_folder - ) + ), file=sys.stdout) def parse_mailfolder(self, mailfolder): """ Parse a mailfolder name to it's parts. Takes a fully qualified mailfolder or mailfolder sub-folder. """ mbox = { 'domain': None } if len(mailfolder.split('/')) > 1: self.separator = '/' # Split off the virtual domain identifier, if any if len(mailfolder.split('@')) > 1: mbox['domain'] = mailfolder.split('@')[1] mbox['path_parts'] = mailfolder.split('@')[0].split(self.separator) else: mbox['path_parts'] = mailfolder.split(self.separator) # See if the path that has been specified is the current location for # the deleted folder, or the original location, we have to find the # deleted folder for. if not mbox['path_parts'][0] in ['user', 'shared']: deleted_prefix = mbox['path_parts'].pop(0) # See if the hexadecimal timestamp is actually hexadecimal. # This prevents "DELETED/user/userid/Sent", but not # "DELETED/user/userid/FFFFFF" from being specified. try: hexstamp = mbox['path_parts'][(len(mbox['path_parts'])-1)] epoch = int(hexstamp, 16) try: timestamp = time.asctime(time.gmtime(epoch)) except: return None except: return None # Verify that the input for the deleted folder is actually a # deleted folder. verify_folder_search = "%(dp)s%(sep)s%(mailfolder)s" % { 'dp': deleted_prefix, 'sep': self.separator, 'mailfolder': self.separator.join(mbox['path_parts']) } if mbox['domain'] is not None: verify_folder_search = "%s@%s" % ( verify_folder_search, mbox['domain'] ) if ' ' in verify_folder_search: folders = self.lm( '"%s"' % self.folder_utf7(verify_folder_search) ) else: folders = self.lm(self.folder_utf7(verify_folder_search)) # NOTE: Case also covered is valid hexadecimal folders; won't be # the actual check as intended, but doesn't give you anyone else's # data unless... See the following: # # TODO: Case not covered is usernames that are hexadecimal. # # We could probably attempt to convert the int(hex) into a # time.gmtime(), but it still would not cover all cases. # # If no folders were found... well... then there you go. if len(folders) < 1: return None # Pop off the hex timestamp, which turned out to be valid mbox['hex_timestamp'] = mbox['path_parts'].pop() return mbox def _find_deleted_folder(self, mbox): """ Give me the parts that are in an original mailfolder name and I'll find the deleted folder name. TODO: It finds virtdomain folders for non-virtdomain searches. """ deleted_folder_search = "%(deleted_prefix)s%(separator)s%(mailfolder)s%(separator)s*" % { # TODO: The prefix used is configurable 'deleted_prefix': "DELETED", 'mailfolder': self.separator.join(mbox['path_parts']), 'separator': self.separator, } if mbox['domain'] is not None: deleted_folder_search = "%s@%s" % ( deleted_folder_search, mbox['domain'] ) folders = self.lm(self.folder_utf7(deleted_folder_search)) # The folders we have found at this stage include virtdomain folders. # # For example, having searched for user/userid, it will also find # user/userid@example.org # # Here, we explicitly remove any virtdomain folders. if mbox['domain'] is None: _folders = [] for folder in folders: if len(folder.split('@')) < 2: _folders.append(folder) folders = _folders return [self.folder_utf8(x) for x in folders] diff --git a/pykolab/imap/dovecot.py b/pykolab/imap/dovecot.py index 8d5c0a5..eb854ab 100644 --- a/pykolab/imap/dovecot.py +++ b/pykolab/imap/dovecot.py @@ -1,634 +1,636 @@ # -*- coding: utf-8 -*- # Copyright 2015 Instituto Tecnológico de Informática (http://www.iti.es) # # Sergio Talens-Oliag (ITI) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # ----- # Note: # # This file is based on the original cyrus.py driver from Kolab, # replacing annotation related functions with metadata functions; to use it # on a debian installation it can be copied to the path: # # /usr/share/pyshared/pykolab/imap/dovecot.py # # The file needs some review, as some functions have been modified to behave # as we want, but the real changes should be done on other places. # # As an example, with annotations you can get all existing annotations with # one call, but if we use metadatata we have to ask for specific variables, # there is no function to get all of them at once (at least on the RFC); in # our case when a pattern like '*' is received we look for fields of the form # 'vendor/kolab/folder-type', as we know they are the fields the functions we # are using need. # ----- +from __future__ import print_function + import cyruslib import imaplib import sys import time from urlparse import urlparse import pykolab from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.imap') conf = pykolab.getConf() # BEG: Add GETMETADATA and SETMETADATA support to the cyruslib IMAP objects Commands = { 'GETMETADATA': ('AUTH',), 'SETMETADATA': ('AUTH',), } imaplib.Commands.update(Commands) def imap_getmetadata(self, mailbox, pattern='*', shared=None): # If pattern is '*' clean pattern and search all entries under /shared # and/or /private (depens on the shared parameter value) to emulate the # ANNOTATEMORE behaviour if pattern == '*': pattern = '' options = '(DEPTH infinity)' else: options = '(DEPTH 0)' if shared == None: entries = '( /shared%s /private%s )' % (pattern, pattern) elif shared: entries = "/shared%s" % pattern else: entries = " /private%s" % pattern typ, dat = self._simple_command('GETMETADATA', options, mailbox, entries) return self._untagged_response(typ, dat, 'METADATA') def imap_setmetadata(self, mailbox, desc, value, shared=False): if value: value = value.join(['"', '"']) else: value = "NIL" if shared: typ, dat = self._simple_command('SETMETADATA', mailbox, "(/shared%s %s)" % (desc,value)) else: typ, dat = self._simple_command('SETMETADATA', mailbox, "(/private%s %s)" % (desc,value)) return self._untagged_response(typ, dat, 'METADATA') # Bind the new methods to the cyruslib IMAP4 and IMAP4_SSL objects from types import MethodType cyruslib.IMAP4.getmetadata = MethodType(imap_getmetadata, None, cyruslib.IMAP4) cyruslib.IMAP4.setmetadata = MethodType(imap_setmetadata, None, cyruslib.IMAP4) cyruslib.IMAP4_SSL.getmetadata = MethodType(imap_getmetadata, None, cyruslib.IMAP4_SSL) cyruslib.IMAP4_SSL.setmetadata = MethodType(imap_setmetadata, None, cyruslib.IMAP4_SSL) # END: Add GETMETADATA and SETMETADATA support to the cyruslib IMAP objects # Auxiliary functions def _get_line_entries(lines): """Function to get metadata entries """ entries = {} name = None value = "" vlen = 0 for line in lines: line_len = len(line) i = 0 while i < line_len: if name == None: if line[i] == '/': j = i while j < line_len: if line[j] == ' ': break j += 1 name = line[i:j] i = j elif vlen != 0: j = i + vlen if j > line_len: value += line[i:line_len] vlen -= line_len - i else: value += line[i:i+vlen] if value in ('', 'NIL'): entries[name] = "" else: entries[name] = value name = None value = "" vlen = 0 elif line[i] == '{': j = i while j < line_len: if line[j] == '}': vlen = int(line[i+1:j]) break j += 1 i = j elif line[i] != ' ': j = i if line[i] == '"': while j < line_len: # Skip quoted text if line[j] == '\\': j += 2 continue elif line[j] == '"': break j += 1 else: while j < line_len: if line[j] == ' ' or line[j] == ')': break j += 1 value = line[i:j] if value in ('', 'NIL'): entries[name] = "" else: entries[name] = value name = None value = "" i = j i += 1 return entries class Dovecot(cyruslib.CYRUS): """ Abstraction class for some common actions to do exclusively in Dovecot. Initially based on the Cyrus driver, will remove dependencies on cyruslib later; right now this module has only been tested to use the dovecot metadata support (no quota or folder operations tests have been performed). """ setquota = cyruslib.CYRUS.sq def __init__(self, uri): """ Initialize this class, but do not connect yet. """ port = None result = urlparse(uri) if hasattr(result, 'hostname'): scheme = result.scheme hostname = result.hostname port = result.port else: scheme = uri.split(':')[0] (hostname, port) = uri.split('/')[2].split(':') if not port: if scheme == 'imap': port = 143 else: port = 993 self.server = hostname self.uri = "%s://%s:%s" % (scheme,hostname,port) while 1: try: cyruslib.CYRUS.__init__(self, self.uri) break except cyruslib.CYRUSError: log.warning(_("Could not connect to Dovecot IMAP server %r") % (self.uri)) time.sleep(10) if conf.debuglevel > 8: self.VERBOSE = True self.m.debug = 5 # Initialize our variables self.separator = self.SEP # Placeholder for known mailboxes on known servers self.mbox = {} # By default don't assume that we have metadata support self.metadata = False def __del__(self): pass def __verbose(self, msg): if self.VERBOSE: - print >> self.LOGFD, msg + print(msg, file=self.LOGFD) def connect(self, uri): """ Dummy connect function that checks if the server that we want to connect to is actually the server we are connected to. Uses pykolab.imap.IMAP.connect() in the background. """ port = None result = urlparse(uri) if hasattr(result, 'hostname'): scheme = result.scheme hostname = result.hostname port = result.port else: scheme = uri.split(':')[0] (hostname, port) = uri.split('/')[2].split(':') if not port: if scheme == 'imap': port = 143 else: port = 993 if hostname == self.server: return imap = IMAP() imap.connect(uri=uri) if not self.SEP == self.separator: self.separator = self.SEP def login(self, *args, **kw): """ Login to the Dovecot IMAP server through cyruslib.CYRUS, but set our hierarchy separator. """ cyruslib.CYRUS.login(self, *args, **kw) self.separator = self.SEP log.debug(_("Continuing with separator: %r") % (self.separator), level=8) # Check if we have metadata support or not self.metadata = False typ, dat = self.m.capability() for capability in tuple(dat[-1].upper().split()): if capability.startswith("METADATA"): log.debug(_("Detected METADATA support"), level=8) self.metadata = True if not self.metadata: log.debug(_("This system does not support METADATA: '%s'" % ','.join(self.m.capabilities)), level=8) def find_mailfolder_server(self, mailfolder): # Nothing to do in dovecot, returns the current server return self.server def folder_utf7(self, folder): from pykolab import imap_utf7 return imap_utf7.encode(folder) def folder_utf8(self, folder): from pykolab import imap_utf7 return imap_utf7.decode(folder) def _setquota(self, mailfolder, quota): # Removed server reconnection for dovecot, we only have one server log.debug(_("Setting quota for folder %s to %s") % (mailfolder,quota), level=8) try: self.m.setquota(mailfolder, quota) except: log.error(_("Could not set quota for mailfolder %s") % (mailfolder)) def _rename(self, from_mailfolder, to_mailfolder, partition=None): # Removed server reconnection for dovecot, we only have one server if not partition == None: log.debug(_("Moving INBOX folder %s to %s on partition %s") % (from_mailfolder,to_mailfolder, partition), level=8) else: log.debug(_("Moving INBOX folder %s to %s") % (from_mailfolder,to_mailfolder), level=8) self.m.rename(self.folder_utf7(from_mailfolder), self.folder_utf7(to_mailfolder), '"%s"' % (partition)) # BEG: METADATA support functions ... quite similar to annotations, really def _getmetadata(self, mailbox, pattern='*', shared=None): """Get Metadata""" # This test needs to be reviewed #if not self.metadata: # return {} # Annotations vs. Metadata fix ... we set a pattern that we know is # good enough for our purposes for now, but the fact is that the # calling programs should be fixed instead. res, data = self.m.getmetadata(self.decode(mailbox), pattern, shared) if (len(data) == 1) and data[0] is None: self.__verbose( '[GETMETADATA %s] No results' % (mailbox) ) return {} # Get the first response line (it can be a string or a tuple) if isinstance(data[0], tuple): fline = data[0][0] else: fline = data[0] # Find the folder name fbeg = 0 fend = -1 if fline[0] == '"': # Quoted name fbeg = 1 i = 1 while i < len(fline): if fline[i] == '"': # folder name ended unless the previous char is \ (we # should test more, this test would fail if we had a \ # at the end of the folder name, but we leave it at that # right now if fline[i-1] != '\\': fend = i break i += 1 else: # For unquoted names the first word is the folder name fend = fline.find(' ') # No mailbox found if fend < 0: self.__verbose( '[GETMETADATA %s] Mailbox not found in results' % (mailbox) ) return {} # Folder name folder = fline[fbeg:fend] # Check mailbox name against the folder name if folder != mailbox: quoted_mailbox = "\"%s\"" % (mailbox) if folder != quoted_mailbox: self.__verbose( '[GETMETADATA %s] Mailbox \'%s\' is not the same as \'%s\'' \ % (mailbox, quoted_mailbox, folder) ) return {} # Process the rest of the first line, the first value will be # available after the first '(' found i=fend ebeg = -1 while i < len(fline): if fline[i] == '(': ebeg = i+1 break i += 1 if ebeg < 0: self.__verbose( '[GETMETADATA %s] Mailbox has no values, skipping' % (mailbox) ) return {} # This variable will start with an entry name and will continue with # the value lenght or the value nfline = fline[ebeg:] if isinstance(data[0], tuple): entries = _get_line_entries((nfline,) + data[0][1:]) else: entries = _get_line_entries((nfline,)) for line in data[1:]: if isinstance(line, tuple): lentries = _get_line_entries(line) else: lentries = _get_line_entries([line,]) if lentries != None and lentries != {}: entries.update(lentries) mdat = { mailbox: entries }; return mdat def _setmetadata(self, mailbox, desc, value, shared=False): """Set METADADATA""" res, msg = self.m.setmetadata(self.decode(mailbox), desc, value, shared) self.__verbose( '[SETMETADATA %s] %s: %s' % (mailbox, res, msg[0]) ) # Use metadata instead of annotations def _getannotation(self, *args, **kw): return self._getmetadata(*args, **kw) def getannotation(self, *args, **kw): return self._getmetadata(*args, **kw) # Use metadata instead of annotations def _setannotation(self, *args, **kw): return self._setmetadata(*args, **kw) def setannotation(self, *args, **kw): return self._setmetadata(*args, **kw) # END: METADATA / Annotations # The functions that follow are the same ones used with Cyrus, probably a # review is needed def _xfer(self, mailfolder, current_server, new_server): self.connect(self.uri.replace(self.server,current_server)) log.debug(_("Transferring folder %s from %s to %s") % (mailfolder, current_server, new_server), level=8) self.xfer(mailfolder, new_server) def undelete_mailfolder(self, mailfolder, to_mailfolder=None, recursive=True): """ Login to the actual backend server, then "undelete" the mailfolder. 'mailfolder' may be a string representing either of the following two options; - the fully qualified pathof the deleted folder in its current location, such as, for a deleted INBOX folder originally known as "user/userid[@domain]"; "DELETED/user/userid/hex[@domain]" - the original folder name, such as; "user/userid[@domain]" 'to_mailfolder' may be the target folder to "undelete" the deleted folder to. If not specified, the original folder name is used. """ # Placeholder for folders we have recovered already. target_folders = [] mailfolder = self.parse_mailfolder(mailfolder) undelete_folders = self._find_deleted_folder(mailfolder) if not to_mailfolder == None: target_mbox = self.parse_mailfolder(to_mailfolder) else: target_mbox = mailfolder for undelete_folder in undelete_folders: undelete_mbox = self.parse_mailfolder(undelete_folder) prefix = undelete_mbox['path_parts'].pop(0) mbox = undelete_mbox['path_parts'].pop(0) if to_mailfolder == None: target_folder = self.separator.join([prefix,mbox]) else: target_folder = self.separator.join(target_mbox['path_parts']) if not to_mailfolder == None: target_folder = "%s%s%s" % (target_folder,self.separator,mbox) if not len(undelete_mbox['path_parts']) == 0: target_folder = "%s%s%s" % (target_folder,self.separator,self.separator.join(undelete_mbox['path_parts'])) if target_folder in target_folders: target_folder = "%s%s%s" % (target_folder,self.separator,undelete_mbox['hex_timestamp']) target_folders.append(target_folder) if not target_mbox['domain'] == None: target_folder = "%s@%s" % (target_folder,target_mbox['domain']) log.info(_("Undeleting %s to %s") % (undelete_folder,target_folder)) target_server = self.find_mailfolder_server(target_folder) if hasattr(conf,'dry_run') and not conf.dry_run: if not target_server == self.server: self.xfer(undelete_folder,target_server) self.rename(undelete_folder,target_folder) else: if not target_server == self.server: - print >> sys.stdout, _("Would have transfered %s from %s to %s") % (undelete_folder, self.server, target_server) + print(_("Would have transfered %s from %s to %s") % (undelete_folder, self.server, target_server), file=sys.stdout) - print >> sys.stdout, _("Would have renamed %s to %s") % (undelete_folder, target_folder) + print(_("Would have renamed %s to %s") % (undelete_folder, target_folder), file=sys.stdout) def parse_mailfolder(self, mailfolder): """ Parse a mailfolder name to it's parts. Takes a fully qualified mailfolder or mailfolder sub-folder. """ mbox = { 'domain': None } if len(mailfolder.split('/')) > 1: self.separator = '/' # Split off the virtual domain identifier, if any if len(mailfolder.split('@')) > 1: mbox['domain'] = mailfolder.split('@')[1] mbox['path_parts'] = mailfolder.split('@')[0].split(self.separator) else: mbox['path_parts'] = mailfolder.split(self.separator) # See if the path that has been specified is the current location for # the deleted folder, or the original location, we have to find the deleted # folder for. if not mbox['path_parts'][0] in [ 'user', 'shared' ]: deleted_prefix = mbox['path_parts'].pop(0) # See if the hexadecimal timestamp is actually hexadecimal. # This prevents "DELETED/user/userid/Sent", but not # "DELETED/user/userid/FFFFFF" from being specified. try: epoch = int(mbox['path_parts'][(len(mbox['path_parts'])-1)], 16) try: timestamp = time.asctime(time.gmtime(epoch)) except: return None except: return None # Verify that the input for the deleted folder is actually a # deleted folder. verify_folder_search = "%(dp)s%(sep)s%(mailfolder)s" % { 'dp': deleted_prefix, 'sep': self.separator, 'mailfolder': self.separator.join(mbox['path_parts']) } if not mbox['domain'] == None: verify_folder_search = "%s@%s" % (verify_folder_search, mbox['domain']) if ' ' in verify_folder_search: folders = self.lm('"%s"' % self.folder_utf7(verify_folder_search)) else: folders = self.lm(self.folder_utf7(verify_folder_search)) # NOTE: Case also covered is valid hexadecimal folders; won't be the # actual check as intended, but doesn't give you anyone else's data # unless... See the following: # # TODO: Case not covered is usernames that are hexadecimal. # # We could probably attempt to convert the int(hex) into a time.gmtime(), # but it still would not cover all cases. # # If no folders were found... well... then there you go. if len(folders) < 1: return None # Pop off the hex timestamp, which turned out to be valid mbox['hex_timestamp'] = mbox['path_parts'].pop() return mbox def _find_deleted_folder(self, mbox): """ Give me the parts that are in an original mailfolder name and I'll find the deleted folder name. TODO: It finds virtdomain folders for non-virtdomain searches. """ deleted_folder_search = "%(deleted_prefix)s%(separator)s%(mailfolder)s%(separator)s*" % { # TODO: The prefix used is configurable 'deleted_prefix': "DELETED", 'mailfolder': self.separator.join(mbox['path_parts']), 'separator': self.separator, } if not mbox['domain'] == None: deleted_folder_search = "%s@%s" % (deleted_folder_search,mbox['domain']) folders = self.lm(deleted_folder_search) # The folders we have found at this stage include virtdomain folders. # # For example, having searched for user/userid, it will also find # user/userid@example.org # # Here, we explicitely remove any virtdomain folders. if mbox['domain'] == None: _folders = [] for folder in folders: if len(folder.split('@')) < 2: _folders.append(folder) folders = _folders return folders diff --git a/pykolab/setup/components.py b/pykolab/setup/components.py index a9adbfa..8e9f346 100644 --- a/pykolab/setup/components.py +++ b/pykolab/setup/components.py @@ -1,266 +1,266 @@ # -*- coding: utf-8 -*- # # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import os import pykolab from pykolab.constants import * from pykolab.translate import _ log = pykolab.getLogger('pykolab.setup') conf = pykolab.getConf() components = {} component_groups = {} executed_components = [] components_included_in_cli = [] finalize_conf_ok = None def __init__(): # We only want the base path components_base_path = os.path.dirname(__file__) for components_path, dirnames, filenames in os.walk(components_base_path): if not components_path == components_base_path: continue for filename in filenames: if filename.startswith('setup_') and filename.endswith('.py'): module_name = filename.replace('.py','') component_name = module_name.replace('setup_', '') #print "exec(\"from %s import __init__ as %s_register\"" % (module_name,component_name) exec("from %s import __init__ as %s_register" % (module_name,component_name)) exec("%s_register()" % (component_name)) for dirname in dirnames: register_group(components_path, dirname) register('help', list_components, description=_("Display this help.")) def list_components(*args, **kw): """ List components """ __components = {} for component in components.keys(): if isinstance(component, tuple): component_group, component = component __components[component_group] = { component: components[(component_group,component)] } else: __components[component] = components[component] _components = __components.keys() _components.sort() for _component in _components: if __components[_component].has_key('function'): # This is a top-level component if not __components[_component]['description'] == None: - print "%-25s - %s" % (_component.replace('_','-'),__components[_component]['description']) + print("%-25s - %s" % (_component.replace('_','-'),__components[_component]['description'])) else: - print "%-25s" % (_component.replace('_','-')) + print("%-25s" % (_component.replace('_','-'))) for _component in _components: if not __components[_component].has_key('function'): # This is a nested component - print "\n" + _("Command Group: %s") % (_component) + "\n" + print("\n" + _("Command Group: %s") % (_component) + "\n") ___components = __components[_component].keys() ___components.sort() for __component in ___components: if not __components[_component][__component]['description'] == None: - print "%-4s%-21s - %s" % ('',__component.replace('_','-'),__components[_component][__component]['description']) + print("%-4s%-21s - %s" % ('',__component.replace('_','-'),__components[_component][__component]['description'])) else: - print "%-4s%-21s" % ('',__component.replace('_','-')) + print("%-4s%-21s" % ('',__component.replace('_','-'))) def _list_components(*args, **kw): """ List components and return API compatible, parseable lists and dictionaries. """ __components = {} for component in components.keys(): if isinstance(component, tuple): component_group, component = component __components[component_group] = { component: components[(component_group,component)] } else: __components[component] = components[component] _components = __components.keys() _components.sort() return _components def cli_options_from_component(component_name, *args, **kw): global components_included_in_cli if component_name in components_included_in_cli: return if components[component_name].has_key('group'): group = components[component_name]['group'] component_name = components[component_name]['component_name'] try: exec("from %s.setup_%s import cli_options as %s_%s_cli_options" % (group,component_name,group,component_name)) exec("%s_%s_cli_options()" % (group,component_name)) except ImportError, e: pass else: try: exec("from setup_%s import cli_options as %s_cli_options" % (component_name,component_name)) exec("%s_cli_options()" % (component_name)) except ImportError, e: pass components_included_in_cli.append(component_name) def execute(component_name, *args, **kw): if component_name == '': log.debug( _("No component selected, continuing for all components"), level=8 ) while 1: for component in _list_components(): execute_this = True if component in executed_components: execute_this = False if component == "help": execute_this = False if execute_this: if components[component].has_key('after'): for _component in components[component]['after']: if not _component in executed_components: execute_this = False if execute_this: execute(component) executed_components.append(component) executed_all = True for component in _list_components(): if not component in executed_components and not component == "help": executed_all = False if executed_all: break return else: for component in _list_components(): cli_options_from_component(component) if not components.has_key(component_name): log.error(_("No such component.")) sys.exit(1) if not components[component_name].has_key('function') and \ not components[component_name].has_key('group'): log.error(_("No such component.")) sys.exit(1) conf.finalize_conf() if len(conf.cli_args) >= 1: _component_name = conf.cli_args.pop(0) else: _component_name = component_name components[component_name]['function'](conf.cli_args, kw) def register_group(dirname, module): components_base_path = os.path.join(os.path.dirname(__file__), module) components[module] = {} for components_path, dirnames, filenames in os.walk(components_base_path): if not components_path == components_base_path: continue for filename in filenames: if filename.startswith('setup_') and filename.endswith('.py'): module_name = filename.replace('.py','') component_name = module_name.replace('setup_', '') #print "exec(\"from %s.%s import __init__ as %s_%s_register\"" % (module,module_name,module,component_name) exec("from %s.%s import __init__ as %s_%s_register" % (module,module_name,module,component_name)) exec("%s_%s_register()" % (module,component_name)) def register(component_name, func, group=None, description=None, aliases=[], after=[], before=[]): if not group == None: component = "%s_%s" % (group,component_name) else: component = component_name if isinstance(aliases, basestring): aliases = [aliases] if components.has_key(component): log.fatal(_("Command '%s' already registered") % (component)) sys.exit(1) if callable(func): if group == None: components[component_name] = { 'function': func, 'description': description, 'after': after, 'before': before, } else: components[group][component_name] = { 'function': func, 'description': description, 'after': after, 'before': before, } components[component] = components[group][component_name] components[component]['group'] = group components[component]['component_name'] = component_name for alias in aliases: components[alias] = { 'function': func, 'description': _("Alias for %s") % (component_name) } ## ## Commands not yet implemented ## def not_yet_implemented(*args, **kw): - print _("Not yet implemented") + print(_("Not yet implemented")) sys.exit(1) diff --git a/pykolab/setup/setup_ldap.py b/pykolab/setup/setup_ldap.py index 8dc6327..fb865e9 100644 --- a/pykolab/setup/setup_ldap.py +++ b/pykolab/setup/setup_ldap.py @@ -1,688 +1,690 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import ldap import ldap.modlist import os import pwd import shutil import subprocess import tempfile import time import components import pykolab from pykolab import utils from pykolab.auth import Auth from pykolab.constants import * from pykolab.translate import _ log = pykolab.getLogger('pykolab.setup') conf = pykolab.getConf() def __init__(): components.register('ldap', execute, description=description()) def cli_options(): ldap_group = conf.add_cli_parser_option_group(_("LDAP Options")) ldap_group.add_option( "--fqdn", dest = "fqdn", action = "store", default = fqdn, help = _("Specify FQDN (overriding defaults).") ) ldap_group.add_option( "--allow-anonymous", dest = "anonymous", action = "store_true", default = False, help = _("Allow anonymous binds (default: no).") ) ldap_group.add_option( "--without-ldap", dest = "without_ldap", action = "store_true", default = False, help = _("Skip setting up the LDAP server.") ) ldap_group.add_option( "--with-openldap", dest = "with_openldap", action = "store_true", default = False, help = _("Setup configuration for OpenLDAP compatibility.") ) ldap_group.add_option( "--with-ad", dest = "with_ad", action = "store_true", default = False, help = _("Setup configuration for Active Directory compatibility.") ) ldap_group.add_option( "--directory-manager-pwd", dest = "directory_manager_pwd", action = "store", default = None, help = _("Specify password for the Domain Manager.") ) def description(): return _("Setup LDAP.") def execute(*args, **kw): ask_questions = True if not conf.config_file == conf.defaults.config_file: ask_questions = False if conf.without_ldap: - print >> sys.stderr, _("Skipping setup of LDAP, as specified") + print(_("Skipping setup of LDAP, as specified"), file=sys.stderr) return _input = {} if conf.with_openldap and not conf.with_ad: conf.command_set('ldap', 'unique_attribute', 'entryuuid') fp = open(conf.defaults.config_file, "w+") conf.cfg_parser.write(fp) fp.close() return elif conf.with_ad and not conf.with_openldap: conf.command_set('ldap', 'auth_attributes', 'samaccountname') conf.command_set('ldap', 'modifytimestamp_format', '%%Y%%m%%d%%H%%M%%S.0Z') conf.command_set('ldap', 'unique_attribute', 'userprincipalname') # TODO: These attributes need to be checked conf.command_set('ldap', 'mail_attributes', 'mail') conf.command_set('ldap', 'mailserver_attributes', 'mailhost') conf.command_set('ldap', 'quota_attribute', 'mailquota') return elif conf.with_ad and conf.with_openldap: - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" You can not configure Kolab to run against OpenLDAP and Active Directory simultaneously. """) - ) + ), file=sys.stderr) sys.exit(1) # Pre-execution checks for path, directories, files in os.walk('/etc/dirsrv/'): for direct in directories: if direct.startswith('slapd-'): - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" It seems 389 Directory Server has an existing instance configured. This setup script does not intend to destroy or overwrite your data. Please make sure /etc/dirsrv/ and /var/lib/dirsrv/ are clean so that this setup does not have to worry. """) - ) + ), file=sys.stderr) sys.exit(1) _input = {} if ask_questions: - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Please supply a password for the LDAP administrator user 'admin', used to login to the graphical console of 389 Directory server. """) - ) + ), file=sys.stderr) _input['admin_pass'] = utils.ask_question( _("Administrator password"), default=utils.generate_password(), password=True, confirm=True ) if conf.directory_manager_pwd is not None: _input['dirmgr_pass'] = conf.directory_manager_pwd else: - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Please supply a password for the LDAP Directory Manager user, which is the administrator user you will be using to at least initially log in to the Web Admin, and that Kolab uses to perform administrative tasks. """) - ) + ), file=sys.stderr) _input['dirmgr_pass'] = utils.ask_question( _("Directory Manager password"), default=utils.generate_password(), password=True, confirm=True ) - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Please choose the system user and group the service should use to run under. These should be existing, unprivileged, local system POSIX accounts with no shell. """) - ) + ), file=sys.stderr) try: pw = pwd.getpwnam("dirsrv") except: _input['userid'] = utils.ask_question(_("User"), default="nobody") _input['group'] = utils.ask_question(_("Group"), default="nobody") else: _input['userid'] = utils.ask_question(_("User"), default="dirsrv") _input['group'] = utils.ask_question(_("Group"), default="dirsrv") else: _input['admin_pass'] = conf.get('ldap', 'bind_pw') _input['dirmgr_pass'] = conf.get('ldap', 'bind_pw') try: pw = pwd.getpwnam("dirsrv") except: _input['userid'] = "nobody" _input['group'] = "nobody" else: _input['userid'] = "dirsrv" _input['group'] = "dirsrv" # TODO: Verify the user and group exist. # TODO: This takes the system fqdn, domainname and hostname, rather then # the desired fqdn, domainname and hostname. # # TODO^2: This should be confirmed. if conf.fqdn: _input['fqdn'] = conf.fqdn _input['hostname'] = conf.fqdn.split('.')[0] _input['domain'] = '.'.join(conf.fqdn.split('.')[1:]) else: _input['fqdn'] = fqdn _input['hostname'] = hostname.split('.')[0] _input['domain'] = domainname _input['nodotdomain'] = _input['domain'].replace('.','_') _input['rootdn'] = utils.standard_root_dn(_input['domain']) if ask_questions: - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" This setup procedure plans to set up Kolab Groupware for the following domain name space. This domain name is obtained from the reverse DNS entry on your network interface. Please confirm this is the appropriate domain name space. """) - ) + ), file=sys.stderr) answer = utils.ask_confirmation("%s" % (_input['domain'])) if not answer: positive_answer = False while not positive_answer: _input['domain'] = utils.ask_question(_("Domain name to use")) if not _input['domain'] == None and not _input['domain'] == "": positive_answer = True else: - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Invalid input. Please try again. """) - ) + ), file=sys.stderr) _input['nodotdomain'] = _input['domain'].replace('.','_') _input['rootdn'] = utils.standard_root_dn(_input['domain']) - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" The standard root dn we composed for you follows. Please confirm this is the root dn you wish to use. """) - ) + ), file=sys.stderr) answer = utils.ask_confirmation("%s" % (_input['rootdn'])) if not answer: positive_answer = False while not positive_answer: _input['rootdn'] = utils.ask_question(_("Root DN to use")) if not _input['rootdn'] == None and not _input['rootdn'] == "": positive_answer = True else: - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Invalid input. Please try again. """) - ) + ), file=sys.stderr) # TODO: Loudly complain if the fqdn does not resolve back to this system. data = """ [General] FullMachineName = %(fqdn)s SuiteSpotUserID = %(userid)s SuiteSpotGroup = %(group)s AdminDomain = %(domain)s ConfigDirectoryLdapURL = ldap://%(fqdn)s:389/o=NetscapeRoot ConfigDirectoryAdminID = admin ConfigDirectoryAdminPwd = %(admin_pass)s [slapd] SlapdConfigForMC = Yes UseExistingMC = 0 ServerPort = 389 ServerIdentifier = %(hostname)s Suffix = %(rootdn)s RootDN = cn=Directory Manager RootDNPwd = %(dirmgr_pass)s ds_bename = %(nodotdomain)s AddSampleEntries = No [admin] Port = 9830 ServerAdminID = admin ServerAdminPwd = %(admin_pass)s """ % (_input) (fp, filename) = tempfile.mkstemp(dir="/tmp/") os.write(fp, data) os.close(fp) if os.path.isfile("/usr/sbin/setup-ds-admin.pl"): setup_ds_admin = "/usr/sbin/setup-ds-admin.pl" elif os.path.isfile("/usr/sbin/setup-ds-admin"): setup_ds_admin = "/usr/sbin/setup-ds-admin" elif os.path.isfile("/usr/sbin/setup-ds.pl"): setup_ds_admin = "/usr/sbin/setup-ds.pl" elif os.path.isfile("/usr/sbin/setup-ds"): setup_ds_admin = "/usr/sbin/setup-ds" else: log.error(_("No directory server setup tool available.")) sys.exit(1) command = [ setup_ds_admin, '--debug', '--silent', '--force', '--file=%s' % (filename) ] - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Setup is now going to set up the 389 Directory Server. This may take a little while (during which period there is no output and no progress indication). """) - ) + ), file=sys.stderr) log.info(_("Setting up 389 Directory Server")) setup_389 = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) (stdoutdata, stderrdata) = setup_389.communicate() if not setup_389.returncode == 0: - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" An error was detected in the setup procedure for 389 Directory Server. This setup will write out stderr and stdout to /var/log/kolab/setup.error.log and /var/log/kolab/setup.out.log respectively, before it exits. """) - ) + ), file=sys.stderr) fp = open('/var/log/kolab/setup.error.log', 'w') fp.write(stderrdata) fp.close() fp = open('/var/log/kolab/setup.out.log', 'w') fp.write(stdoutdata) fp.close() log.debug(_("Setup DS stdout:"), level=8) log.debug(stdoutdata, level=8) log.debug(_("Setup DS stderr:"), level=8) log.debug(stderrdata, level=8) if not setup_389.returncode == 0: sys.exit(1) # Find the kolab schema. It's installed as %doc in the kolab-schema package. # TODO: Chown nobody, nobody, chmod 440 schema_file = None for root, directories, filenames in os.walk('/usr/share/doc/'): for filename in filenames: if filename.startswith('kolab') and filename.endswith('.ldif') and schema_file == None: schema_file = os.path.join(root,filename) if not schema_file == None: try: shutil.copy( schema_file, '/etc/dirsrv/slapd-%s/schema/99%s' % ( _input['hostname'], os.path.basename(schema_file) ) ) schema_error = False except: log.error(_("Could not copy the LDAP extensions for Kolab")) schema_error = True else: log.error(_("Could not find the ldap Kolab schema file")) schema_error = True if os.path.isfile('/bin/systemctl'): subprocess.call(['/bin/systemctl', 'restart', 'dirsrv.target']) subprocess.call(['/bin/systemctl', 'restart', 'dirsrv@' + _input['hostname']]) time.sleep(20) elif os.path.isfile('/sbin/service'): subprocess.call(['/sbin/service', 'dirsrv', 'restart']) elif os.path.isfile('/usr/sbin/service'): subprocess.call(['/usr/sbin/service','dirsrv','stop']) time.sleep(20) subprocess.call(['/usr/sbin/service','dirsrv','start']) else: log.error(_("Could not start the directory server service.")) if os.path.isfile('/bin/systemctl'): subprocess.call(['/bin/systemctl', 'enable', 'dirsrv.target']) subprocess.call(['/bin/systemctl', 'enable', 'dirsrv@' + _input['hostname']]) elif os.path.isfile('/sbin/chkconfig'): subprocess.call(['/sbin/chkconfig', 'dirsrv', 'on']) elif os.path.isfile('/usr/sbin/update-rc.d'): subprocess.call(['/usr/sbin/update-rc.d', 'dirsrv', 'defaults']) else: log.error(_("Could not configure to start on boot, the " + \ "directory server service.")) if ask_questions: - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Please supply a Cyrus Administrator password. This password is used by Kolab to execute administrative tasks in Cyrus IMAP. You may also need the password yourself to troubleshoot Cyrus IMAP and/or perform other administrative tasks against Cyrus IMAP directly. """) - ) + ), file=sys.stderr) _input['cyrus_admin_pass'] = utils.ask_question( _("Cyrus Administrator password"), default=utils.generate_password(), password=True, confirm=True ) - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Please supply a Kolab Service account password. This account is used by various services such as Postfix, and Roundcube, as anonymous binds to the LDAP server will not be allowed. """) - ) + ), file=sys.stderr) _input['kolab_service_pass'] = utils.ask_question( _("Kolab Service password"), default=utils.generate_password(), password=True, confirm=True ) else: _input['cyrus_admin_pass'] = conf.get('cyrus-imap', 'admin_password') _input['kolab_service_pass'] = conf.get('ldap', 'service_bind_pw') log.info(_("Writing out configuration to kolab.conf")) # Write out kolab configuration conf.command_set('kolab', 'primary_domain', _input['domain']) conf.command_set('ldap', 'base_dn', _input['rootdn']) conf.command_set('ldap', 'bind_dn', 'cn=Directory Manager') conf.command_set('ldap', 'bind_pw', _input['dirmgr_pass']) conf.command_set('ldap', 'service_bind_dn', 'uid=kolab-service,ou=Special Users,%s' % (_input['rootdn'])) conf.command_set('ldap', 'service_bind_pw', _input['kolab_service_pass']) fp = open(conf.defaults.config_file, "w+") conf.cfg_parser.write(fp) fp.close() log.info(_("Inserting service users into LDAP.")) # Insert service users auth = Auth(_input['domain']) auth.connect() auth._auth.connect() auth._auth._bind(bind_dn='cn=Directory Manager', bind_pw=_input['dirmgr_pass']) dn = 'uid=%s,ou=Special Users,%s' % (conf.get('cyrus-imap', 'admin_login'), _input['rootdn']) # A dict to help build the "body" of the object attrs = {} attrs['objectclass'] = ['top','person','inetorgperson','organizationalperson'] attrs['uid'] = conf.get('cyrus-imap', 'admin_login') attrs['givenname'] = "Cyrus" attrs['surname'] = "Administrator" attrs['cn'] = "Cyrus Administrator" attrs['userPassword'] = _input['cyrus_admin_pass'] # Convert our dict to nice syntax for the add-function using modlist-module ldif = ldap.modlist.addModlist(attrs) # Do the actual synchronous add-operation to the ldapserver auth._auth.ldap.add_s(dn, ldif) conf.command_set('cyrus-imap', 'admin_password', _input['cyrus_admin_pass']) dn = 'uid=kolab-service,ou=Special Users,%s' % (_input['rootdn']) # A dict to help build the "body" of the object attrs = {} attrs['objectclass'] = ['top','person','inetorgperson','organizationalperson'] attrs['uid'] = "kolab-service" attrs['givenname'] = "Kolab" attrs['surname'] = "Service" attrs['cn'] = "Kolab Service" attrs['userPassword'] = _input['kolab_service_pass'] attrs['nslookthroughlimit'] = '-1' attrs['nssizelimit'] = '-1' attrs['nstimelimit'] = '-1' attrs['nsidletimeout'] = '-1' # Convert our dict to nice syntax for the add-function using modlist-module ldif = ldap.modlist.addModlist(attrs) # Do the actual synchronous add-operation to the ldapserver auth._auth.ldap.add_s(dn, ldif) dn = 'ou=Resources,%s' % (_input['rootdn']) # A dict to help build the "body" of the object attrs = {} attrs['objectclass'] = ['top','organizationalunit'] attrs['ou'] = "Resources" # Convert our dict to nice syntax for the add-function using modlist-module ldif = ldap.modlist.addModlist(attrs) # Do the actual synchronous add-operation to the ldapserver auth._auth.ldap.add_s(dn, ldif) dn = 'ou=Shared Folders,%s' % (_input['rootdn']) # A dict to help build the "body" of the object attrs = {} attrs['objectclass'] = ['top','organizationalunit'] attrs['ou'] = "Shared Folders" # Convert our dict to nice syntax for the add-function using modlist-module ldif = ldap.modlist.addModlist(attrs) # Do the actual synchronous add-operation to the ldapserver auth._auth.ldap.add_s(dn, ldif) log.info(_("Writing out cn=kolab,cn=config")) dn = 'cn=kolab,cn=config' # A dict to help build the "body" of the object attrs = {} attrs['objectclass'] = ['top','extensibleobject'] attrs['cn'] = "kolab" attrs['aci'] = '(targetattr = "*") (version 3.0;acl "Kolab Services";allow (read,compare,search)(userdn = "ldap:///uid=kolab-service,ou=Special Users,%s");)' % (_input['rootdn']) # Convert our dict to nice syntax for the add-function using modlist-module ldif = ldap.modlist.addModlist(attrs) # Do the actual synchronous add-operation to the ldapserver auth._auth.ldap.add_s(dn, ldif) log.info(_("Adding domain %s to list of domains for this deployment") % (_input['domain'])) dn = "associateddomain=%s,cn=kolab,cn=config" % (_input['domain']) attrs = {} attrs['objectclass'] = ['top','domainrelatedobject'] attrs['associateddomain'] = [ '%s' % (_input['domain']), '%s' % (_input['fqdn']), 'localhost.localdomain', 'localhost' ] # De-duplicate attribute values before attempting to insert the object (#2205) attrs['associateddomain'] = list(set(attrs['associateddomain'])) attrs['associateddomain'].pop(attrs['associateddomain'].index(_input['domain'])) attrs['associateddomain'] = [ _input['domain'] ] + attrs['associateddomain'] attrs['aci'] = '(targetattr = "*") (version 3.0;acl "Read Access for %(domain)s Users";allow (read,compare,search)(userdn = "ldap:///%(rootdn)s??sub?(objectclass=*)");)' % (_input) # Add inetdomainbasedn in case the configured root dn is not the same as the # standard root dn for the domain name configured if not _input['rootdn'] == utils.standard_root_dn(_input['domain']): attrs['objectclass'].append('inetdomain') attrs['inetdomainbasedn'] = _input['rootdn'] ldif = ldap.modlist.addModlist(attrs) auth._auth.ldap.add_s(dn, ldif) if not conf.anonymous: log.info(_("Disabling anonymous binds")) dn = "cn=config" modlist = [] modlist.append((ldap.MOD_REPLACE, "nsslapd-allow-anonymous-access", "off")) auth._auth.ldap.modify_s(dn, modlist) # TODO: Ensure the uid attribute is unique # TODO^2: Consider renaming the general "attribute uniqueness to "uid attribute uniqueness" log.info(_("Enabling attribute uniqueness plugin")) dn = "cn=attribute uniqueness,cn=plugins,cn=config" modlist = [] modlist.append((ldap.MOD_REPLACE, "nsslapd-pluginEnabled", "on")) auth._auth.ldap.modify_s(dn, modlist) log.info(_("Enabling referential integrity plugin")) dn = "cn=referential integrity postoperation,cn=plugins,cn=config" modlist = [] modlist.append((ldap.MOD_REPLACE, "nsslapd-pluginEnabled", "on")) auth._auth.ldap.modify_s(dn, modlist) log.info(_("Enabling and configuring account policy plugin")) dn = "cn=Account Policy Plugin,cn=plugins,cn=config" modlist = [] modlist.append((ldap.MOD_REPLACE, "nsslapd-pluginEnabled", "on")) modlist.append((ldap.MOD_ADD, "nsslapd-pluginarg0", "cn=config,cn=Account Policy Plugin,cn=plugins,cn=config")) auth._auth.ldap.modify_s(dn, modlist) dn = "cn=config,cn=Account Policy Plugin,cn=plugins,cn=config" modlist = [] modlist.append((ldap.MOD_REPLACE, "alwaysrecordlogin", "yes")) modlist.append((ldap.MOD_ADD, "stateattrname", "lastLoginTime")) modlist.append((ldap.MOD_ADD, "altstateattrname", "createTimestamp")) auth._auth.ldap.modify_s(dn, modlist) # Add kolab-admin role log.info(_("Adding the kolab-admin role")) dn = "cn=kolab-admin,%s" % (_input['rootdn']) attrs = {} attrs['description'] = "Kolab Administrator" attrs['objectClass'] = ['top','ldapsubentry','nsroledefinition','nssimpleroledefinition','nsmanagedroledefinition'] attrs['cn'] = "kolab-admin" ldif = ldap.modlist.addModlist(attrs) auth._auth.ldap.add_s(dn, ldif) # User writeable attributes on root_dn log.info(_("Setting access control to %s") % (_input['rootdn'])) dn = _input['rootdn'] aci = [] if schema_error: aci.append('(targetattr = "carLicense || description || displayName || facsimileTelephoneNumber || homePhone || homePostalAddress || initials || jpegPhoto || l || labeledURI || mobile || o || pager || photo || postOfficeBox || postalAddress || postalCode || preferredDeliveryMethod || preferredLanguage || registeredAddress || roomNumber || secretary || seeAlso || st || street || telephoneNumber || telexNumber || title || userCertificate || userPassword || userSMIMECertificate || x500UniqueIdentifier") (version 3.0; acl "Enable self write for common attributes"; allow (read,compare,search,write)(userdn = "ldap:///self");)') else: aci.append('(targetattr = "carLicense || description || displayName || facsimileTelephoneNumber || homePhone || homePostalAddress || initials || jpegPhoto || l || labeledURI || mobile || o || pager || photo || postOfficeBox || postalAddress || postalCode || preferredDeliveryMethod || preferredLanguage || registeredAddress || roomNumber || secretary || seeAlso || st || street || telephoneNumber || telexNumber || title || userCertificate || userPassword || userSMIMECertificate || x500UniqueIdentifier || kolabDelegate || kolabInvitationPolicy || kolabAllowSMTPSender") (version 3.0; acl "Enable self write for common attributes"; allow (read,compare,search,write)(userdn = "ldap:///self");)') aci.append('(targetattr = "*") (version 3.0;acl "Directory Administrators Group";allow (all)(groupdn = "ldap:///cn=Directory Administrators,%(rootdn)s" or roledn = "ldap:///cn=kolab-admin,%(rootdn)s");)' % (_input)) aci.append('(targetattr="*")(version 3.0; acl "Configuration Administrators Group"; allow (all) groupdn="ldap:///cn=Configuration Administrators,ou=Groups,ou=TopologyManagement,o=NetscapeRoot";)') aci.append('(targetattr="*")(version 3.0; acl "Configuration Administrator"; allow (all) userdn="ldap:///uid=admin,ou=Administrators,ou=TopologyManagement,o=NetscapeRoot";)') aci.append('(targetattr = "*")(version 3.0; acl "SIE Group"; allow (all) groupdn = "ldap:///cn=slapd-%(hostname)s,cn=389 Directory Server,cn=Server Group,cn=%(fqdn)s,ou=%(domain)s,o=NetscapeRoot";)' % (_input)) aci.append('(targetattr != "userPassword") (version 3.0;acl "Search Access";allow (read,compare,search)(userdn = "ldap:///all");)') modlist = [] modlist.append((ldap.MOD_REPLACE, "aci", aci)) auth._auth.ldap.modify_s(dn, modlist) if os.path.isfile('/bin/systemctl'): if not os.path.isfile('/usr/lib/systemd/system/dirsrv-admin.service'): log.info(_("directory server admin service not available")) else: subprocess.call(['/bin/systemctl', 'enable', 'dirsrv-admin.service']) elif os.path.isfile('/sbin/chkconfig'): subprocess.call(['/sbin/chkconfig', 'dirsrv-admin', 'on']) elif os.path.isfile('/usr/sbin/update-rc.d'): subprocess.call(['/usr/sbin/update-rc.d', 'dirsrv-admin', 'defaults']) else: log.error(_("Could not start and configure to start on boot, the " + \ "directory server admin service.")) diff --git a/pykolab/setup/setup_mysql.py b/pykolab/setup/setup_mysql.py index 73c2245..335c353 100644 --- a/pykolab/setup/setup_mysql.py +++ b/pykolab/setup/setup_mysql.py @@ -1,341 +1,343 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import os import subprocess import tempfile import time import components import pykolab from pykolab import utils from pykolab.constants import * from pykolab.translate import _ log = pykolab.getLogger('pykolab.setup') conf = pykolab.getConf() def __init__(): components.register('mysql', execute, description=description()) def cli_options(): mysql_group = conf.add_cli_parser_option_group(_("MySQL Options")) mysql_group.add_option( "--mysqlserver", dest="mysqlserver", action="store", help=_("Specify whether to use an (existing), (unix_socket) or (new) MySQL server.") ) mysql_group.add_option( "--mysqlhost", dest="mysqlhost", action="store", default='127.0.0.1', help=_("The MySQL host address.") ) mysql_group.add_option( "--mysqlrootpw", dest="mysqlrootpw", action="store", help=_("The MySQL root user password.") ) def description(): return _("Setup MySQL.") def execute(*args, **kw): # noqa: C901 socket_paths = [ "/var/lib/mysql/mysql.sock", "/var/run/mysqld/mysqld.sock", "/var/run/mysql/mysql.sock", "/var/run/mysqld/mysqld.pid" ] # on CentOS7, there is MariaDB instead of MySQL if conf.mysqlserver != 'existing': mysqlservice = 'mysqld.service' if os.path.isfile('/usr/lib/systemd/system/mariadb.service'): mysqlservice = 'mariadb.service' elif os.path.isfile('/usr/lib/systemd/system/mysql.service'): mysqlservice = 'mysql.service' if not os.path.isfile('/usr/lib/systemd/system/' + mysqlservice): # on Debian Jessie, systemctl restart mysql mysqlservice = 'mysql' if os.path.isfile('/bin/systemctl'): subprocess.call(['/bin/systemctl', 'restart', mysqlservice]) elif os.path.isfile('/sbin/service'): subprocess.call(['/sbin/service', 'mysqld', 'restart']) elif os.path.isfile('/usr/sbin/service'): subprocess.call(['/usr/sbin/service', 'mysql', 'restart']) else: log.error(_("Could not start the MySQL database service.")) if os.path.isfile('/bin/systemctl'): subprocess.call(['/bin/systemctl', 'enable', mysqlservice]) elif os.path.isfile('/sbin/chkconfig'): subprocess.call(['/sbin/chkconfig', 'mysqld', 'on']) elif os.path.isfile('/usr/sbin/update-rc.d'): subprocess.call(['/usr/sbin/update-rc.d', 'mysql', 'defaults']) else: log.error( _("Could not configure to start on boot, the MySQL database service.") ) log.info(_("Waiting for at most 30 seconds for MySQL/MariaDB to settle...")) max_wait = 30 while max_wait > 0: for socket_path in socket_paths: if os.path.exists(socket_path): max_wait = 0 if max_wait > 0: max_wait = max_wait - 1 time.sleep(1) options = { 1: "Existing MySQL server (with root password already set).", 2: "Existing MySQL server (with unix_socket authentication plugin).", 3: "New MySQL server (needs to be initialized)." } answer = 0 if conf.mysqlserver != 'existing': if len([x for x in socket_paths if os.path.exists(x)]) > 0: if conf.mysqlserver: if conf.mysqlserver == 'existing': answer = 1 elif conf.mysqlserver == 'unix_socket': answer = 2 elif conf.mysqlserver == 'new': answer = 3 if answer == 0: answer = utils.ask_menu(_("What MySQL server are we setting up?"), options) else: answer = 1 if answer == "1" or answer == 1: if not conf.mysqlrootpw: - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Please supply the root password for MySQL, so we can set up user accounts for other components that use MySQL. """) - ) + ), file=sys.stderr) mysql_root_password = utils.ask_question( _("MySQL root password"), password=True ) else: mysql_root_password = conf.mysqlrootpw elif answer == "2" or answer == 2: mysql_root_password = 'unix_socket' else: - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Please supply a root password for MySQL. This password will be the administrative user for this MySQL server, and it should be kept a secret. After this setup process has completed, Kolab is going to discard and forget about this password, but you will need it for administrative tasks in MySQL. """) - ) + ), file=sys.stderr) mysql_root_password = utils.ask_question( _("MySQL root password"), default=utils.generate_password(), password=True, confirm=True ) p1 = subprocess.Popen( [ 'echo', 'UPDATE mysql.user SET Password=PASSWORD(\'%s\') WHERE User=\'root\';' % ( mysql_root_password ) ], stdout=subprocess.PIPE ) p2 = subprocess.Popen(['mysql'], stdin=p1.stdout) p1.stdout.close() p2.communicate() p1 = subprocess.Popen( [ 'echo', "UPDATE mysql.user SET authentication_string=PASSWORD('%s') WHERE User='root';" % ( mysql_root_password ) ], stdout=subprocess.PIPE ) p2 = subprocess.Popen(['mysql'], stdin=p1.stdout) p1.stdout.close() p2.communicate() p1 = subprocess.Popen( [ 'echo', """ UPDATE mysql.user SET plugin='mysql_native_password' WHERE User='root' AND plugin='auth_socket'; """ ], stdout=subprocess.PIPE ) p2 = subprocess.Popen(['mysql'], stdin=p1.stdout) p1.stdout.close() p2.communicate() p1 = subprocess.Popen(['echo', 'FLUSH PRIVILEGES;'], stdout=subprocess.PIPE) p2 = subprocess.Popen(['mysql'], stdin=p1.stdout) p1.stdout.close() p2.communicate() socket_path = None socket_paths = [ "/var/lib/mysql/mysql.sock", "/var/run/mysqld/mysqld.sock", "/var/run/mysql/mysql.sock" ] for sp in socket_paths: if os.path.exists(sp): socket_path = sp if mysql_root_password == "unix_socket" and socket_path is not None: data = """ [mysql] user=root password= host=localhost socket=%s """ % (socket_path) else: data = """ [mysql] user=root password='%s' host=%s """ % (mysql_root_password, conf.mysqlhost) fp = open('/tmp/kolab-setup-my.cnf', 'w') os.chmod('/tmp/kolab-setup-my.cnf', 0o600) fp.write(data) fp.close() schema_file = None for root, directories, filenames in os.walk('/usr/share/doc/'): for filename in filenames: if filename.startswith('kolab_wap') and filename.endswith('.sql'): # Skip the Oracle file if filename.endswith('oracle.sql'): continue schema_file = os.path.join(root, filename) if schema_file is not None: p1 = subprocess.Popen(['echo', 'create database kolab;'], stdout=subprocess.PIPE) p2 = subprocess.Popen(['mysql', '--defaults-file=/tmp/kolab-setup-my.cnf'], stdin=p1.stdout) p1.stdout.close() p2.communicate() - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Please supply a password for the MySQL user 'kolab'. This password will be used by Kolab services, such as the Web Administration Panel. """) - ) + ), file=sys.stderr) mysql_kolab_password = utils.ask_question( _("MySQL kolab password"), default=utils.generate_password(), password=True, confirm=True ) p1 = subprocess.Popen( [ 'echo', "GRANT ALL PRIVILEGES ON kolab.* TO 'kolab'@'localhost' IDENTIFIED BY '%s';" % ( mysql_kolab_password ) ], stdout=subprocess.PIPE ) p2 = subprocess.Popen( [ 'mysql', '--defaults-file=/tmp/kolab-setup-my.cnf' ], stdin=p1.stdout ) p1.stdout.close() p2.communicate() p1 = subprocess.Popen(['cat', schema_file], stdout=subprocess.PIPE) p2 = subprocess.Popen( [ 'mysql', '--defaults-file=/tmp/kolab-setup-my.cnf', 'kolab' ], stdin=p1.stdout ) p1.stdout.close() p2.communicate() conf.command_set( 'kolab_wap', 'sql_uri', 'mysql://kolab:%s@localhost/kolab' % (mysql_kolab_password) ) conf.command_set( 'kolab_smtp_access_policy', 'cache_uri', 'mysql://kolab:%s@localhost/kolab' % (mysql_kolab_password) ) else: log.warning(_("Could not find the MySQL Kolab schema file")) diff --git a/pykolab/setup/setup_php.py b/pykolab/setup/setup_php.py index f27dd3f..cbb93c0 100644 --- a/pykolab/setup/setup_php.py +++ b/pykolab/setup/setup_php.py @@ -1,138 +1,139 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function from augeas import Augeas import os import shutil import subprocess import tempfile import components import pykolab from pykolab import utils from pykolab.auth import Auth from pykolab.constants import * from pykolab.translate import _ log = pykolab.getLogger('pykolab.setup') conf = pykolab.getConf() def __init__(): components.register('php', execute, description=description()) def cli_options(): php_group = conf.add_cli_parser_option_group(_("PHP Options")) php_group.add_option( "--timezone", dest="timezone", action="store", default=None, help=_("Specify the timezone for PHP.") ) php_group.add_option( "--with-php-ini", dest="php_ini_path", action="store", default=None, help=_("Specify the path to the php.ini file used with the webserver.") ) def description(): return _("Setup PHP.") def execute(*args, **kw): if conf.timezone is None: - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( _(""" Please supply the timezone PHP should be using. You have to use a Continent or Country / City locality name like 'Europe/Berlin', but not just 'CEST'. """) - ) + ), file=sys.stderr) conf.timezone = utils.ask_question( _("Timezone ID"), default="UTC" ) if conf.php_ini_path is not None: if not os.path.isfile(conf.php_ini_path): log.error( _("Cannot configure PHP through %r (No such file or directory)") % ( conf.php_ini_path ) ) return php_ini = conf.php_ini_path else: # Search and destroy php_ini = "/etc/php.ini" if not os.path.isfile(php_ini): php_ini = "/etc/php/7.2/apache2/php.ini" if not os.path.isfile(php_ini): php_ini = "/etc/php/7.0/apache2/php.ini" if not os.path.isfile(php_ini): php_ini = "/etc/php5/apache2/php.ini" if not os.path.isfile(php_ini): log.error(_("Could not find PHP configuration file php.ini")) return try: myaugeas = Augeas() setting_base = '/files%s/' % (php_ini) setting = os.path.join(setting_base, 'Date', 'date.timezone') current_value = myaugeas.get(setting) if current_value is None: insert_paths = myaugeas.match('/files%s/Date/*' % (php_ini)) insert_path = insert_paths[(len(insert_paths) - 1)] myaugeas.insert(insert_path, 'date.timezone', False) log.debug(_("Setting key %r to %r") % ('Date/date.timezone', conf.timezone), level=8) myaugeas.set(setting, conf.timezone) myaugeas.save() except IndexError: subprocess.Popen( [ 'sed', '-i', '-r', '-e', r's|^(;*)date\.timezone.*$|date.timezone = %s|g' % (conf.timezone), php_ini ] ) diff --git a/pykolab/setup/setup_roundcube.py b/pykolab/setup/setup_roundcube.py index 869533f..b5567be 100644 --- a/pykolab/setup/setup_roundcube.py +++ b/pykolab/setup/setup_roundcube.py @@ -1,373 +1,375 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import codecs import grp import hashlib import os import random import re import subprocess import sys import time from Cheetah.Template import Template import components import pykolab from pykolab import utils from pykolab.constants import * from pykolab.translate import _ # pylint: disable=invalid-name log = pykolab.getLogger('pykolab.setup') conf = pykolab.getConf() def __init__(): components.register('roundcube', execute, description=description(), after=['mysql', 'ldap']) def description(): return _("Setup Roundcube.") def execute(*args, **kw): - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( """ Please supply a password for the MySQL user 'roundcube'. This password will be used by the Roundcube webmail interface. """ - ) + ), file=sys.stderr) mysql_roundcube_password = utils.ask_question( "MySQL roundcube password", default=utils.generate_password(), password=True, confirm=True ) conf.mysql_roundcube_password = mysql_roundcube_password rc_settings = { 'des_key': re.sub( r'[^a-zA-Z0-9]', "", "%s%s" % ( hashlib.md5("%s" % random.random()).digest().encode("base64"), hashlib.md5("%s" % random.random()).digest().encode("base64") ) )[:24], 'imap_admin_login': conf.get('cyrus-imap', 'admin_login'), 'imap_admin_password': conf.get('cyrus-imap', 'admin_password'), 'ldap_base_dn': conf.get('ldap', 'base_dn'), 'ldap_group_base_dn': conf.get('ldap', 'group_base_dn'), 'ldap_group_filter': conf.get('ldap', 'group_filter'), 'ldap_ldap_uri': conf.get('ldap', 'ldap_uri'), 'ldap_resource_base_dn': conf.get('ldap', 'resource_base_dn'), 'ldap_resource_filter': conf.get('ldap', 'resource_filter'), 'ldap_service_bind_dn': conf.get('ldap', 'service_bind_dn'), 'ldap_service_bind_pw': conf.get('ldap', 'service_bind_pw'), 'ldap_user_base_dn': conf.get('ldap', 'user_base_dn'), 'ldap_user_filter': conf.get('ldap', 'user_filter'), 'primary_domain': conf.get('kolab', 'primary_domain'), 'mysql_uri': 'mysqli://roundcube:%s@localhost/roundcube' % (mysql_roundcube_password), 'conf': conf } rc_paths = [ "/usr/share/roundcubemail/", "/usr/share/roundcube/", "/srv/www/roundcubemail/", "/var/www/roundcubemail/" ] rcpath = '' for rc_path in rc_paths: if os.path.isdir(rc_path): rcpath = rc_path break if not os.path.isdir(rcpath): log.error("Roundcube installation path not found.") return if os.access(rcpath + 'skins/kolab/', os.R_OK): rc_settings['skin'] = 'kolab' elif os.access(rcpath + 'skins/enterprise/', os.R_OK): rc_settings['skin'] = 'enterprise' elif os.access(rcpath + 'skins/chameleon/', os.R_OK): rc_settings['skin'] = 'chameleon' else: rc_settings['skin'] = 'larry' want_files = [ 'acl.inc.php', 'calendar.inc.php', 'config.inc.php', 'kolab_addressbook.inc.php', 'kolab_auth.inc.php', 'kolab_delegation.inc.php', 'kolab_files.inc.php', 'kolab_folders.inc.php', 'libkolab.inc.php', 'managesieve.inc.php', 'password.inc.php', 'recipient_to_contact.inc.php', 'terms.html', 'terms.inc.php' ] for want_file in want_files: template_file = None if os.path.isfile('/etc/kolab/templates/roundcubemail/%s.tpl' % (want_file)): template_file = '/etc/kolab/templates/roundcubemail/%s.tpl' % (want_file) elif os.path.isfile('/usr/share/kolab/templates/roundcubemail/%s.tpl' % (want_file)): template_file = '/usr/share/kolab/templates/roundcubemail/%s.tpl' % (want_file) if template_file is not None: # pylint: disable=logging-not-lazy log.debug("Using template file %r" % (template_file), level=8) filep = codecs.open(template_file, 'r', encoding='utf-8') template_definition = filep.read() filep.close() t = Template(template_definition, searchList=[rc_settings]) # pylint: disable=logging-not-lazy log.debug( "Successfully compiled template %r, writing out to %r" % ( template_file, want_file ), level=8 ) filep = None if os.path.isdir('/etc/roundcubemail'): filep = codecs.open('/etc/roundcubemail/%s' % (want_file), 'w', encoding='utf-8') elif os.path.isdir('/etc/roundcube'): filep = codecs.open('/etc/roundcube/%s' % (want_file), 'w', encoding='utf-8') if filep is not None: filep.write(t.respond()) filep.close() schema_files = [] # pylint: disable=too-many-nested-blocks for root, directories, filenames in os.walk('/usr/share/doc/'): directories.sort() for directory in directories: if directory.startswith("roundcubemail"): for _root, _directories, _filenames in os.walk(os.path.join(root, directory)): for filename in _filenames: if filename.startswith('mysql.initial') and filename.endswith('.sql'): schema_filepath = os.path.join(_root, filename) if schema_filepath not in schema_files: schema_files.append(schema_filepath) if schema_files: break if schema_files: break for root, directories, filenames in os.walk(rcpath + 'plugins/calendar/drivers/kolab/'): for filename in filenames: if filename.startswith('mysql') and filename.endswith('.sql'): schema_filepath = os.path.join(root, filename) if schema_filepath not in schema_files: schema_files.append(schema_filepath) for root, directories, filenames in os.walk(rcpath + 'plugins/libkolab/'): for filename in filenames: if filename.startswith('mysql') and filename.endswith('.sql'): schema_filepath = os.path.join(root, filename) if schema_filepath not in schema_files: schema_files.append(schema_filepath) for root, directories, filenames in os.walk('/usr/share/doc/'): directories.sort() for directory in directories: if directory.startswith("chwala"): for _root, _directories, _filenames in os.walk(os.path.join(root, directory)): for filename in _filenames: if filename.startswith('mysql.initial') and filename.endswith('.sql'): schema_filepath = os.path.join(_root, filename) if schema_filepath not in schema_files: schema_files.append(schema_filepath) if len(schema_files) > 0: break if len(schema_files) > 0: break if not os.path.isfile('/tmp/kolab-setup-my.cnf'): - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( """Please supply the MySQL root password (use 'unix_socket' for socket based authentication)""" - ) + ), file=sys.stderr) mysql_root_password = utils.ask_question( _("MySQL root password"), password=True ) socket_path = None socket_paths = [ "/var/lib/mysql/mysql.sock", "/var/run/mysqld/mysqld.sock", "/var/run/mysql/mysql.sock" ] for sp in socket_paths: if os.path.exists(sp): socket_path = sp if mysql_root_password == "unix_socket" and socket_path is not None: data = """ [mysql] user=root password= host=localhost socket=%s """ % (socket_path) else: data = """ [mysql] user=root password='%s' host=%s """ % (mysql_root_password, conf.mysqlhost) fp = open('/tmp/kolab-setup-my.cnf', 'w') os.chmod('/tmp/kolab-setup-my.cnf', 0o600) fp.write(data) fp.close() p1 = subprocess.Popen(['echo', 'create database roundcube;'], stdout=subprocess.PIPE) p2 = subprocess.Popen(['mysql', '--defaults-file=/tmp/kolab-setup-my.cnf'], stdin=p1.stdout) p1.stdout.close() p2.communicate() p1 = subprocess.Popen( [ 'echo', 'GRANT ALL PRIVILEGES ON roundcube.* TO \'roundcube\'@\'localhost\' IDENTIFIED BY \'%s\';' % ( mysql_roundcube_password ) ], stdout=subprocess.PIPE ) p2 = subprocess.Popen(['mysql', '--defaults-file=/tmp/kolab-setup-my.cnf'], stdin=p1.stdout) p1.stdout.close() p2.communicate() for schema_file in schema_files: p1 = subprocess.Popen(['cat', schema_file], stdout=subprocess.PIPE) p2 = subprocess.Popen( [ 'mysql', '--defaults-file=/tmp/kolab-setup-my.cnf', 'roundcube' ], stdin=p1.stdout ) p1.stdout.close() p2.communicate() p1 = subprocess.Popen(['echo', 'FLUSH PRIVILEGES;'], stdout=subprocess.PIPE) p2 = subprocess.Popen(['mysql', '--defaults-file=/tmp/kolab-setup-my.cnf'], stdin=p1.stdout) p1.stdout.close() p2.communicate() time.sleep(2) # Find Roundcube configuration that is not readable by the # webserver user/group. if os.path.isdir('/etc/roundcubemail/'): rccpath = "/etc/roundcubemail/" elif os.path.isdir('/etc/roundcube/'): rccpath = "/etc/roundcube" else: log.warning("Cannot find the configuration directory for roundcube.") rccpath = None root_uid = 0 webserver_gid = None for webserver_group in ['apache', 'www-data', 'www']: try: # pylint: disable=unused-variable (a, b, webserver_gid, d) = grp.getgrnam(webserver_group) break # pylint: disable=broad-except except Exception: pass if webserver_gid is not None: if rccpath is not None: for root, directories, filenames in os.walk(rccpath): for filename in filenames: try: os.chown( os.path.join(root, filename), root_uid, webserver_gid ) # pylint: disable=broad-except except Exception: pass httpservice = 'httpd.service' if os.path.isfile('/usr/lib/systemd/system/apache2.service'): httpservice = 'apache2.service' if os.path.isfile('/lib/systemd/system/apache2.service'): # Debian 9 httpservice = 'apache2.service' if os.path.isdir('/lib/systemd/system/apache2.service.d'): httpservice = 'apache2.service' if os.path.isfile('/bin/systemctl'): subprocess.call(['/bin/systemctl', 'restart', httpservice]) elif os.path.isfile('/sbin/service'): subprocess.call(['/sbin/service', 'httpd', 'restart']) elif os.path.isfile('/usr/sbin/service'): subprocess.call(['/usr/sbin/service', 'apache2', 'restart']) else: log.error("Could not start the webserver server service.") if os.path.isfile('/bin/systemctl'): subprocess.call(['/bin/systemctl', 'enable', httpservice]) elif os.path.isfile('/sbin/chkconfig'): subprocess.call(['/sbin/chkconfig', 'httpd', 'on']) elif os.path.isfile('/usr/sbin/update-rc.d'): subprocess.call(['/usr/sbin/update-rc.d', 'apache2', 'defaults']) else: log.error( "Could not configure to start on boot, the webserver server service." ) diff --git a/pykolab/setup/setup_syncroton.py b/pykolab/setup/setup_syncroton.py index 1cccd0e..8ad2d8c 100644 --- a/pykolab/setup/setup_syncroton.py +++ b/pykolab/setup/setup_syncroton.py @@ -1,153 +1,155 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import os import subprocess import sys import time import components import pykolab from pykolab import utils from pykolab.constants import * from pykolab.translate import _ log = pykolab.getLogger('pykolab.setup') conf = pykolab.getConf() def __init__(): components.register( 'syncroton', execute, description=description(), after=['mysql','ldap','roundcube'] ) def description(): return _("Setup Syncroton.") def execute(*args, **kw): schema_files = [] for root, directories, filenames in os.walk('/usr/share/doc/'): for directory in directories: if directory.startswith("kolab-syncroton"): for root, directories, filenames in os.walk(os.path.join(root, directory)): for filename in filenames: if filename.startswith('mysql.initial') and filename.endswith('.sql'): schema_filepath = os.path.join(root,filename) if not schema_filepath in schema_files: schema_files.append(schema_filepath) break if len(schema_files) > 0: break if len(schema_files) > 0: break if not os.path.isfile('/tmp/kolab-setup-my.cnf'): - print >> sys.stderr, utils.multiline_message( + print(utils.multiline_message( """Please supply the MySQL root password (use 'unix_socket' for socket based authentication)""" - ) + ), file=sys.stderr) mysql_root_password = utils.ask_question( _("MySQL root password"), password=True ) socket_path = None socket_paths = [ "/var/lib/mysql/mysql.sock", "/var/run/mysqld/mysqld.sock", "/var/run/mysql/mysql.sock" ] for sp in socket_paths: if os.path.exists(sp): socket_path = sp if mysql_root_password == "unix_socket" and socket_path is not None: data = """ [mysql] user=root password= host=localhost socket=%s """ % (socket_path) else: data = """ [mysql] user=root password='%s' host=%s """ % (mysql_root_password, conf.mysqlhost) data = """ [mysql] user=root password='%s' host=%s """ % (mysql_root_password, conf.mysqlhost) fp = open('/tmp/kolab-setup-my.cnf', 'w') os.chmod('/tmp/kolab-setup-my.cnf', 0o600) fp.write(data) fp.close() for schema_file in schema_files: p1 = subprocess.Popen(['cat', schema_file], stdout=subprocess.PIPE) p2 = subprocess.Popen(['mysql', '--defaults-file=/tmp/kolab-setup-my.cnf', 'roundcube'], stdin=p1.stdout) p1.stdout.close() p2.communicate() time.sleep(2) httpservice = 'httpd.service' if os.path.isfile('/usr/lib/systemd/system/apache2.service'): httpservice = 'apache2.service' if os.path.isfile('/lib/systemd/system/apache2.service'): # Debian 9 httpservice = 'apache2.service' if os.path.isdir('/lib/systemd/system/apache2.service.d'): httpservice = 'apache2.service' if os.path.isfile('/bin/systemctl'): subprocess.call(['/bin/systemctl', 'restart', httpservice]) elif os.path.isfile('/sbin/service'): subprocess.call(['/sbin/service', 'httpd', 'restart']) elif os.path.isfile('/usr/sbin/service'): subprocess.call(['/usr/sbin/service','apache2','restart']) else: log.error(_("Could not start the webserver server service.")) if os.path.isfile('/bin/systemctl'): subprocess.call(['/bin/systemctl', 'enable', httpservice]) elif os.path.isfile('/sbin/chkconfig'): subprocess.call(['/sbin/chkconfig', 'httpd', 'on']) elif os.path.isfile('/usr/sbin/update-rc.d'): subprocess.call(['/usr/sbin/update-rc.d', 'apache2', 'defaults']) else: log.error(_("Could not configure to start on boot, the " + \ "webserver server service.")) diff --git a/pykolab/wap_client/__init__.py b/pykolab/wap_client/__init__.py index 900e280..82f7ebb 100644 --- a/pykolab/wap_client/__init__.py +++ b/pykolab/wap_client/__init__.py @@ -1,661 +1,661 @@ import json import httplib import urllib import sys from urlparse import urlparse import pykolab from pykolab import utils from pykolab.translate import _ log = pykolab.getLogger('pykolab.wap_client') conf = pykolab.getConf() if not hasattr(conf, 'defaults'): conf.finalize_conf() API_HOSTNAME = "localhost" API_SCHEME = "http" API_PORT = 80 API_SSL = False API_BASE = "/kolab-webadmin/api/" kolab_wap_url = conf.get('kolab_wap', 'api_url') if not kolab_wap_url == None: result = urlparse(kolab_wap_url) else: result = None if hasattr(result, 'scheme') and result.scheme == 'https': API_SSL = True API_PORT = 443 if hasattr(result, 'hostname'): API_HOSTNAME = result.hostname if hasattr(result, 'port'): API_PORT = result.port if hasattr(result, 'path'): API_BASE = result.path session_id = None conn = None def authenticate(username=None, password=None, domain=None): global session_id if username == None: username = conf.get('ldap', 'bind_dn') if password == None: password = conf.get('ldap', 'bind_pw') if domain == None: domain = conf.get('kolab', 'primary_domain') post = json.dumps( { 'username': username, 'password': password, 'domain': domain } ) response = request('POST', "system.authenticate", post=post) if not response: return False if response.has_key('session_token'): session_id = response['session_token'] return True def connect(uri=None): global conn, API_SSL, API_PORT, API_HOSTNAME, API_BASE if not uri == None: result = urlparse(uri) if hasattr(result, 'scheme') and result.scheme == 'https': API_SSL = True API_PORT = 443 if hasattr(result, 'hostname'): API_HOSTNAME = result.hostname if hasattr(result, 'port'): API_PORT = result.port if hasattr(result, 'path'): API_BASE = result.path if conn == None: if API_SSL: conn = httplib.HTTPSConnection(API_HOSTNAME, API_PORT) else: conn = httplib.HTTPConnection(API_HOSTNAME, API_PORT) conn.connect() return conn def disconnect(quit=False): global conn, session_id if quit and session_id: request('GET', 'system.quit') session_id = None if conn: conn.close() conn = None def domain_add(domain, aliases=[]): dna = conf.get('ldap', 'domain_name_attribute') post = json.dumps({ dna: [ domain ] + aliases }) return request('POST', 'domain.add', post=post) def domain_delete(domain, force=False): domain_id, domain_attrs = domain_find(domain).popitem() param = {} param['id'] = domain_id if force: param['force'] = force post = json.dumps(param) return request('POST', 'domain.delete', post=post) def domain_find(domain): dna = conf.get('ldap', 'domain_name_attribute') get = { dna: domain } return request('GET', 'domain.find', get=get) def domain_info(domain): domain_id, domain_attrs = domain_find(domain) get = { 'id': domain_id } return request('GET', 'domain.info', get=get) def domains_capabilities(): return request('GET', 'domains.capabilities') def domains_list(): return request('GET', 'domains.list') def form_value_generate(params): post = json.dumps(params) return request('POST', 'form_value.generate', post=post) def form_value_generate_password(*args, **kw): return request('GET', 'form_value.generate_password') def form_value_list_options(object_type, object_type_id, attribute): post = json.dumps( { 'object_type': object_type, 'type_id': object_type_id, 'attribute': attribute } ) return request('POST', 'form_value.list_options', post=post) def form_value_select_options(object_type, object_type_id, attribute): post = json.dumps( { 'object_type': object_type, 'type_id': object_type_id, 'attributes': [ attribute ] } ) return request('POST', 'form_value.select_options', post=post) def get_group_input(): group_types = group_types_list() if len(group_types.keys()) > 1: for key in group_types.keys(): if not key == "status": - print "%s) %s" % (key,group_types[key]['name']) + print("%s) %s" % (key,group_types[key]['name'])) group_type_id = utils.ask_question("Please select the group type") elif len(group_types.keys()) > 0: - print "Automatically selected the only group type available" + print("Automatically selected the only group type available") group_type_id = group_types.keys()[0] else: - print "No group types available" + print("No group types available") sys.exit(1) if group_types.has_key(group_type_id): group_type_info = group_types[group_type_id]['attributes'] else: - print "No such group type" + print("No such group type") sys.exit(1) params = { 'group_type_id': group_type_id } for attribute in group_type_info['form_fields'].keys(): params[attribute] = utils.ask_question(attribute) for attribute in group_type_info['auto_form_fields'].keys(): exec("retval = group_form_value_generate_%s(params)" % (attribute)) params[attribute] = retval[attribute] return params def get_user_input(): user_types = user_types_list() if user_types['count'] > 1: - print "" + print("") for key in user_types['list'].keys(): if not key == "status": - print "%s) %s" % (key,user_types['list'][key]['name']) + print("%s) %s" % (key,user_types['list'][key]['name'])) - print "" + print("") user_type_id = utils.ask_question("Please select the user type") elif user_types['count'] > 0: - print "Automatically selected the only user type available" + print("Automatically selected the only user type available") user_type_id = user_types['list'].keys()[0] else: - print "No user types available" + print("No user types available") sys.exit(1) if user_types['list'].has_key(user_type_id): user_type_info = user_types['list'][user_type_id]['attributes'] else: - print "No such user type" + print("No such user type") sys.exit(1) params = { 'object_type': 'user', 'type_id': user_type_id } must_attrs = [] may_attrs = [] for attribute in user_type_info['form_fields'].keys(): if isinstance(user_type_info['form_fields'][attribute], dict): if user_type_info['form_fields'][attribute].has_key('optional') and user_type_info['form_fields'][attribute]['optional']: may_attrs.append(attribute) else: must_attrs.append(attribute) else: must_attrs.append(attribute) for attribute in must_attrs: if isinstance(user_type_info['form_fields'][attribute], dict) and \ user_type_info['form_fields'][attribute].has_key('type'): if user_type_info['form_fields'][attribute]['type'] == 'select': if not user_type_info['form_fields'][attribute].has_key('values'): attribute_values = form_value_select_options('user', user_type_id, attribute) default = '' if attribute_values[attribute].has_key('default'): default = attribute_values[attribute]['default'] params[attribute] = utils.ask_menu( "Choose the %s value" % (attribute), attribute_values[attribute]['list'], default=default ) else: default = '' if user_type_info['form_fields'][attribute].has_key('default'): default = user_type_info['form_fields'][attribute]['default'] params[attribute] = utils.ask_menu( "Choose the %s value" % (attribute), user_type_info['form_fields'][attribute]['values'], default=default ) else: params[attribute] = utils.ask_question(attribute) else: params[attribute] = utils.ask_question(attribute) for attribute in user_type_info['fields'].keys(): params[attribute] = user_type_info['fields'][attribute] exec("retval = user_form_value_generate(params)") - print retval + print(retval) return params def group_add(params=None): if params == None: params = get_group_input() post = json.dumps(params) return request('POST', 'group.add', post=post) def group_delete(params=None): if params == None: params = { 'id': utils.ask_question("Name of group to delete", "group") } post = json.dumps(params) return request('POST', 'group.delete', post=post) def group_form_value_generate_mail(params=None): if params == None: params = get_user_input() params = json.dumps(params) return request('POST', 'group_form_value.generate_mail', params) def group_find(params=None): post = { 'search': { 'params': {} } } for (k,v) in params.iteritems(): post['search']['params'][k] = { 'value': v, 'type': 'exact' } return request('POST', 'group.find', post=json.dumps(post)) def group_info(group=None): if group == None: group = utils.ask_question("group DN") return request('GET', 'group.info', get={ 'id': group }) def group_members_list(group=None): if group == None: group = utils.ask_question("Group email address") group = request('GET', 'group.members_list?group=%s' % (group)) return group def group_types_list(): return request('GET', 'group_types.list') def groups_list(params={}): return request('POST', 'groups.list', post=json.dumps(params)) def ou_add(params={}): return request('POST', 'ou.add', post=json.dumps(params)) def ou_delete(params={}): return request('POST', 'ou.delete', post=json.dumps(params)) def ou_edit(params={}): return request('POST', 'ou.edit', post=json.dumps(params)) def ou_find(params=None): post = { 'search': { 'params': {} } } for (k,v) in params.iteritems(): post['search']['params'][k] = { 'value': v, 'type': 'exact' } return request('POST', 'ou.find', post=json.dumps(post)) def ou_info(ou): _params = { 'id': ou } ou = request('GET', 'ou.info', get=_params) return ou def ous_list(params={}): return request('POST', 'ous.list', post=json.dumps(params)) def request(method, api_uri, get=None, post=None, headers={}): response_data = request_raw(method, api_uri, get, post, headers) if response_data['status'] == "OK": del response_data['status'] return response_data['result'] else: print("%s: %s (code %s)" % (response_data['status'], response_data['reason'], response_data['code'])) return False def request_raw(method, api_uri, get=None, post=None, headers={}, isretry=False): global session_id if not session_id == None: headers["X-Session-Token"] = session_id reconnect = False conn = connect() if conf.debuglevel > 8: conn.set_debuglevel(9) if not get == None: _get = "?%s" % (urllib.urlencode(get)) else: _get = "" log.debug(_("Requesting %r with params %r") % ("%s/%s" % (API_BASE,api_uri), (get, post)), level=8) try: conn.request(method.upper(), "%s/%s%s" % (API_BASE, api_uri, _get), post, headers) response = conn.getresponse() data = response.read() log.debug(_("Got response: %r") % (data), level=8) except (httplib.BadStatusLine, httplib.CannotSendRequest) as e: if isretry: raise e log.info(_("Connection error: %r; re-connecting..."), e) reconnect = True # retry with a new connection if reconnect: disconnect() return request_raw(method, api_uri, get, post, headers, True) try: response_data = json.loads(data) except ValueError, e: # Some data is not JSON log.error(_("Response data is not JSON")) return response_data def resource_add(params=None): if params == None: params = get_user_input() return request('POST', 'resource.add', post=json.dumps(params)) def resource_delete(params=None): if params == None: params = { 'id': utils.ask_question("Resource DN to delete", "resource") } return request('POST', 'resource.delete', post=json.dumps(params)) def resource_find(params=None): post = { 'search': { 'params': {} } } for (k,v) in params.iteritems(): post['search']['params'][k] = { 'value': v, 'type': 'exact' } return request('POST', 'resource.find', post=json.dumps(post)) def resource_info(resource=None): if resource == None: resource = utils.ask_question("Resource DN") return request('GET', 'resource.info', get={ 'id': resource }) def resource_types_list(): return request('GET', 'resource_types.list') def resources_list(params={}): return request('POST', 'resources.list', post=json.dumps(params)) def role_add(params=None): if params == None: role_name = utils.ask_question("Role name") params = { 'cn': role_name } params = json.dumps(params) return request('POST', 'role.add', params) def role_capabilities(): return request('GET', 'role.capabilities') def role_delete(params=None): if params == None: role_name = utils.ask_question("Role name") role = role_find_by_attribute({'cn': role_name}) params = { 'role': role.keys()[0] } if not params.has_key('role'): role = role_find_by_attribute(params) params = { 'role': role.keys()[0] } post = json.dumps(params) return request('POST', 'role.delete', post=post) def role_find_by_attribute(params=None): if params == None: role_name = utils.ask_question("Role name") else: role_name = params['cn'] get = { 'cn': role_name } role = request('GET', 'role.find_by_attribute', get=get) return role def role_info(role_name): role = role_find_by_attribute({'cn': role_name}) get = { 'role': role['id'] } role = request('GET', 'role.info', get=get) return role def roles_list(): return request('GET', 'roles.list') def sharedfolder_add(params=None): if params == None: params = get_user_input() return request('POST', 'sharedfolder.add', post=json.dumps(params)) def sharedfolder_delete(params=None): if params == None: params = { 'id': utils.ask_question("Shared Folder DN to delete", "sharedfolder") } return request('POST', 'sharedfolder.delete', post=json.dumps(params)) def sharedfolders_list(params={}): return request('POST', 'sharedfolders.list', post=json.dumps(params)) def system_capabilities(domain=None): return request('GET', 'system.capabilities', get={'domain':domain}) def system_get_domain(): return request('GET', 'system.get_domain') def system_select_domain(domain=None): if domain == None: domain = utils.ask_question("Domain name") get = { 'domain': domain } return request('GET', 'system.select_domain', get=get) def user_add(params=None): if params == None: params = get_user_input() params = json.dumps(params) return request('POST', 'user.add', post=params) def user_delete(params=None): if params == None: params = { 'id': utils.ask_question("Username for user to delete", "user") } post = json.dumps(params) return request('POST', 'user.delete', post=post) def user_edit(user = None, attributes={}): if user == None: get = { 'id': utils.ask_question("Username for user to edit", "user") } else: get = { 'id': user } user_info = request('GET', 'user.info', get=get) for attribute in attributes.keys(): user_info[attribute] = attributes[attribute] post = json.dumps(user_info) user_edit = request('POST', 'user.edit', get=get, post=post) return user_edit def user_find(attribs=None): if attribs == None: post = { 'search': { 'params': { utils.ask_question("Attribute") : { 'value': utils.ask_question("value"), 'type': 'exact' } } } } else: post = { 'search': { 'params': {} } } for (k,v) in attribs.iteritems(): post['search']['params'][k] = { 'value': v, 'type': 'exact' } post = json.dumps(post) user = request('POST', 'user.find', post=post) return user def user_form_value_generate(params=None): if params == None: params = get_user_input() post = json.dumps(params) return request('POST', 'form_value.generate', post=post) def user_form_value_generate_uid(params=None): if params == None: params = get_user_input() params = json.dumps(params) return request('POST', 'form_value.generate_uid', params) def user_form_value_generate_userpassword(*args, **kw): result = form_value_generate_password() return { 'userpassword': result['password'] } def user_info(user=None): if user == None: user = utils.ask_question("User email address") _params = { 'id': user } user = request('GET', 'user.info', get=_params) return user def users_list(params={}): return request('POST', 'users.list', post=json.dumps(params)) def user_types_list(): return request('GET', 'user_types.list') diff --git a/saslauthd.py b/saslauthd.py index 9f0b349..6e39ed0 100755 --- a/saslauthd.py +++ b/saslauthd.py @@ -1,41 +1,43 @@ #!/usr/bin/python # # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import logging import os import sys # For development purposes sys.path.extend(['.', '..']) from pykolab.translate import _ try: import pykolab.logger except ImportError as e: - print >> sys.stderr, _("Cannot load pykolab/logger.py:") - print >> sys.stderr, "%s" % e + print(_("Cannot load pykolab/logger.py:"), file=sys.stderr) + print("%s" % e, file=sys.stderr) sys.exit(1) import saslauthd if __name__ == "__main__": saslauthd = saslauthd.SASLAuthDaemon() saslauthd.run() diff --git a/saslauthd/__init__.py b/saslauthd/__init__.py index e11296b..6d89929 100644 --- a/saslauthd/__init__.py +++ b/saslauthd/__init__.py @@ -1,378 +1,381 @@ # Copyright 2010-2016 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # """ SASL authentication daemon for multi-domain Kolab deployments. The SASL authentication daemon can use the domain name space or realm in the login credentials to determine the backend authentication database, and authenticate the credentials supplied against that backend. """ +from __future__ import print_function from optparse import OptionParser from ConfigParser import SafeConfigParser import grp import os import pwd import shutil import sys import time import traceback import pykolab from pykolab import utils from pykolab.auth import Auth from pykolab.constants import * from pykolab.translate import _ log = pykolab.getLogger('saslauthd') conf = pykolab.getConf() class SASLAuthDaemon(object): def __init__(self): daemon_group = conf.add_cli_parser_option_group(_("Daemon Options")) daemon_group.add_option( "--fork", dest = "fork_mode", action = "store_true", default = False, help = _("Fork to the background.") ) daemon_group.add_option( "-p", "--pid-file", dest = "pidfile", action = "store", default = "/var/run/kolab-saslauthd/kolab-saslauthd.pid", help = _("Path to the PID file to use.") ) daemon_group.add_option( "-s", "--socket", dest = "socketfile", action = "store", default = "/var/run/saslauthd/mux", help = _("Socket file to bind to.") ) daemon_group.add_option( "-u", "--user", dest = "process_username", action = "store", default = "kolab", help = _("Run as user USERNAME"), metavar = "USERNAME" ) daemon_group.add_option( "-g", "--group", dest = "process_groupname", action = "store", default = "kolab", help = _("Run as group GROUPNAME"), metavar = "GROUPNAME" ) conf.finalize_conf() try: utils.ensure_directory( os.path.dirname(conf.pidfile), conf.process_username, conf.process_groupname ) except Exception, errmsg: log.error(_("Could not create %r: %r") % (os.path.dirname(conf.pidfile), errmsg)) sys.exit(1) self.thread_count = 0 def run(self): """ Run the SASL authentication daemon. """ exitcode = 0 self._ensure_socket_dir() self._drop_privileges() try: pid = os.getpid() if conf.fork_mode: pid = os.fork() if pid > 0 and not conf.fork_mode: self.do_saslauthd() elif pid > 0: sys.exit(0) else: # Give up the session, all control, # all open file descriptors, see #5151 os.chdir("/") old_umask = os.umask(0) os.setsid() pid = os.fork() if pid > 0: sys.exit(0) sys.stderr.flush() sys.stdout.flush() os.close(0) os.close(1) os.close(2) os.umask(old_umask) self.thread_count += 1 log.remove_stdout_handler() self.set_signal_handlers() self.write_pid() self.do_saslauthd() except SystemExit, e: exitcode = e except KeyboardInterrupt: exitcode = 1 log.info(_("Interrupted by user")) except AttributeError, e: exitcode = 1 traceback.print_exc() - print >> sys.stderr, _("Traceback occurred, please report a " + - "bug at https://issues.kolab.org") + print(_("Traceback occurred, please report a " + + "bug at https://issues.kolab.org"), + file=sys.stderr) except TypeError, e: exitcode = 1 traceback.print_exc() log.error(_("Type Error: %s") % e) except: exitcode = 2 traceback.print_exc() - print >> sys.stderr, _("Traceback occurred, please report a " + - "bug at https://issues.kolab.org") + print(_("Traceback occurred, please report a " + + "bug at https://issues.kolab.org"), + file=sys.stderr) sys.exit(exitcode) def do_saslauthd(self): """ Create the actual listener socket, and handle the authentication. The actual authentication handling is passed on to the appropriate backend authentication classes through the more generic Auth(). """ import binascii import socket import struct s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) # TODO: The saslauthd socket path could be a setting. try: os.remove(conf.socketfile) except: # TODO: Do the "could not remove, could not start" dance pass s.bind(conf.socketfile) os.chmod(conf.socketfile, 0o777) s.listen(5) while 1: max_tries = 20 cur_tries = 0 bound = False while not bound: cur_tries += 1 try: (clientsocket, address) = s.accept() bound = True except Exception, errmsg: log.error( _("kolab-saslauthd could not accept " + "connections on socket: %r") % (errmsg) ) if cur_tries >= max_tries: log.fatal(_("Maximum tries exceeded, exiting")) sys.exit(1) time.sleep(1) received = clientsocket.recv(4096) login = [] start = 0 end = 2 while end < len(received): (length,) = struct.unpack("!H", received[start:end]) start += 2 end += length (value,) = struct.unpack("!%ds" % (length), received[start:end]) start += length end = start + 2 login.append(value) if len(login) == 4: realm = login[3] elif len(login[0].split('@')) > 1: realm = login[0].split('@')[1] else: realm = conf.get('kolab', 'primary_domain') auth = Auth(domain=realm) auth.connect() success = False try: success = auth.authenticate(login) except: success = False if success: # #1170: Catch broken pipe error (incomplete authentication request) try: clientsocket.send(struct.pack("!H2s", 2, "OK")) except: pass else: # #1170: Catch broken pipe error (incomplete authentication request) try: clientsocket.send(struct.pack("!H2s", 2, "NO")) except: pass clientsocket.close() auth.disconnect() def reload_config(self, *args, **kw): pass def remove_pid(self, *args, **kw): if os.access(conf.pidfile, os.R_OK): os.remove(conf.pidfile) raise SystemExit def set_signal_handlers(self): import signal signal.signal(signal.SIGHUP, self.reload_config) signal.signal(signal.SIGTERM, self.remove_pid) def write_pid(self): pid = os.getpid() fp = open(conf.pidfile, 'w') fp.write("%d\n" % (pid)) fp.close() def _ensure_socket_dir(self): utils.ensure_directory( os.path.dirname(conf.socketfile), conf.process_username, conf.process_groupname ) def _drop_privileges(self): try: try: (ruid, euid, suid) = os.getresuid() (rgid, egid, sgid) = os.getresgid() except AttributeError, errmsg: ruid = os.getuid() rgid = os.getgid() if ruid == 0: # Means we can setreuid() / setregid() / setgroups() if rgid == 0: # Get group entry details try: ( group_name, group_password, group_gid, group_members ) = grp.getgrnam(conf.process_groupname) except KeyError: - print >> sys.stderr, _("Group %s does not exist") % ( + print(_("Group %s does not exist") % ( conf.process_groupname - ) + ), file=sys.stderr) sys.exit(1) # Set real and effective group if not the same as current. if not group_gid == rgid: log.debug( _("Switching real and effective group id to %d") % ( group_gid ), level=8 ) os.setregid(group_gid, group_gid) if ruid == 0: # Means we haven't switched yet. try: ( user_name, user_password, user_uid, user_gid, user_gecos, user_homedir, user_shell ) = pwd.getpwnam(conf.process_username) except KeyError: - print >> sys.stderr, _("User %s does not exist") % ( + print(_("User %s does not exist") % ( conf.process_username - ) + ), file=sys.stderr) sys.exit(1) # Set real and effective user if not the same as current. if not user_uid == ruid: log.debug( _("Switching real and effective user id to %d") % ( user_uid ), level=8 ) os.setreuid(user_uid, user_uid) except: log.error(_("Could not change real and effective uid and/or gid")) diff --git a/setup-kolab.py b/setup-kolab.py index 914be99..ef6fcb7 100755 --- a/setup-kolab.py +++ b/setup-kolab.py @@ -1,43 +1,45 @@ #!/usr/bin/python -u # -*- coding: utf-8 -*- # # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import logging import os import sys # For development purposes sys.path = ['.'] + sys.path import pykolab from pykolab.setup import Setup from pykolab.translate import _ try: from pykolab.constants import * except ImportError, e: - print >> sys.stderr, _("Cannot load pykolab/constants.py:") - print >> sys.stderr, "%s" % e + print(_("Cannot load pykolab/constants.py:"), file=sys.stderr) + print("%s" % e, file=sys.stderr) sys.exit(1) if __name__ == "__main__": setup = Setup() setup.run() diff --git a/tests/functional/test_kolabd/test_001_user_sync.py b/tests/functional/test_kolabd/test_001_user_sync.py index e8c804d..8b61893 100644 --- a/tests/functional/test_kolabd/test_001_user_sync.py +++ b/tests/functional/test_kolabd/test_001_user_sync.py @@ -1,140 +1,140 @@ import time import unittest import pykolab from pykolab import wap_client from pykolab.auth import Auth from pykolab.imap import IMAP conf = pykolab.getConf() class TestKolabDaemon(unittest.TestCase): @classmethod def setup_class(self, *args, **kw): from tests.functional.purge_users import purge_users purge_users() self.user = { 'local': 'john.doe', 'domain': 'example.org' } from tests.functional.user_add import user_add user_add("John", "Doe") @classmethod def teardown_class(self, *args, **kw): from tests.functional.purge_users import purge_users purge_users() def test_001_user_recipient_policy(self): auth = Auth() auth.connect() recipient = auth.find_recipient("%(local)s@%(domain)s" % (self.user)) if hasattr(self, 'assertIsInstance'): self.assertIsInstance(recipient, str) self.assertEqual(recipient, "uid=doe,ou=People,dc=example,dc=org") result = wap_client.user_info(recipient) self.assertEqual(result['mail'], 'john.doe@example.org') self.assertEqual(result['alias'], ['doe@example.org', 'j.doe@example.org']) def test_002_user_recipient_policy_duplicate(self): from tests.functional.user_add import user_add user = { 'local': 'jane.doe', 'domain': 'example.org' } user_add("Jane", "Doe") time.sleep(3) auth = Auth() auth.connect() recipient = auth.find_recipient("%(local)s@%(domain)s" % (user)) if hasattr(self, 'assertIsInstance'): self.assertIsInstance(recipient, str) self.assertEqual(recipient, "uid=doe2,ou=People,dc=example,dc=org") result = wap_client.user_info(recipient) if 'mailhost' not in result: from tests.functional.synchronize import synchronize_once synchronize_once() result = wap_client.user_info(recipient) self.assertEqual(result['mail'], 'jane.doe@example.org') self.assertEqual(result['alias'], ['doe2@example.org', 'j.doe2@example.org']) def test_003_user_mailbox_created(self): time.sleep(2) imap = IMAP() imap.connect() folders = imap.lm('user/%(local)s@%(domain)s' % (self.user)) self.assertEqual(len(folders), 1) def test_004_user_additional_folders_created(self): time.sleep(2) imap = IMAP() imap.connect() ac_folders = conf.get_raw('kolab', 'autocreate_folders') exec("ac_folders = %s" % (ac_folders)) folders = imap.lm('user/%(local)s/*@%(domain)s' % (self.user)) self.assertEqual(len(folders), len(ac_folders.keys())) def test_005_user_folders_metadata_set(self): imap = IMAP() imap.connect() ac_folders = conf.get_raw('kolab', 'autocreate_folders') exec("ac_folders = %s" % (ac_folders)) folders = [] folders.extend(imap.lm('user/%(local)s@%(domain)s' % (self.user))) folders.extend(imap.lm('user/%(local)s/*@%(domain)s' % (self.user))) for folder in folders: metadata = imap.get_metadata(folder) - print metadata + print(metadata) folder_name = '/'.join(folder.split('/')[2:]).split('@')[0] if folder_name in ac_folders: if 'annotations' in ac_folders[folder_name]: for _annotation in ac_folders[folder_name]['annotations'].keys(): if _annotation.startswith('/private'): continue _annotation_value = ac_folders[folder_name]['annotations'][_annotation] self.assertTrue(_annotation in metadata[metadata.keys().pop()]) self.assertEqual(_annotation_value, metadata[metadata.keys().pop()][_annotation]) def test_006_user_subscriptions(self): imap = IMAP() imap.connect(login=False) login = conf.get('cyrus-imap', 'admin_login') password = conf.get('cyrus-imap', 'admin_password') imap.login_plain(login, password, 'john.doe@example.org') folders = imap.lm() self.assertTrue("INBOX" in folders) folders = imap.imap.lsub() self.assertTrue("Calendar" in folders) def test_011_resource_add(self): pass def test_012_resource_mailbox_created(self): pass def test_013_resource_mailbox_annotation(self): pass diff --git a/tests/functional/test_kolabd/test_002_user_rename.py b/tests/functional/test_kolabd/test_002_user_rename.py index 3ac4a02..aaea7e2 100644 --- a/tests/functional/test_kolabd/test_002_user_rename.py +++ b/tests/functional/test_kolabd/test_002_user_rename.py @@ -1,80 +1,80 @@ import time import unittest import pykolab from pykolab import wap_client from pykolab.auth import Auth from pykolab.imap import IMAP conf = pykolab.getConf() class TestKolabDaemon(unittest.TestCase): @classmethod def setup_class(self, *args, **kw): from tests.functional.purge_users import purge_users purge_users() self.user = { 'local': 'john.doe', 'domain': 'example.org' } from tests.functional.user_add import user_add user_add("John", "Doe") time.sleep(2) @classmethod def teardown_class(self, *args, **kw): from tests.functional.purge_users import purge_users purge_users() def test_001_user_rename(self): """ Rename user "Doe, John" to "Sixpack, Joe" and verify the recipient policy is applied, and the IMAP INBOX folder for the user is renamed. """ auth = Auth() auth.connect() recipient = auth.find_recipient('john.doe@example.org') user_info = wap_client.user_info(recipient) if 'mailhost' not in user_info: from tests.functional.synchronize import synchronize_once synchronize_once() imap = IMAP() imap.connect() folders = imap.lm('user/john.doe@example.org') self.assertEqual(len(folders), 1) auth = Auth() auth.connect() recipient = auth.find_recipient("%(local)s@%(domain)s" % (self.user)) user_info = wap_client.user_info(recipient) user_info['sn'] = 'Sixpack' user_info['givenname'] = 'Joe' user_info['uid'] = 'sixpack' user_edit = wap_client.user_edit(recipient, user_info) time.sleep(2) - print imap.lm() + print(imap.lm()) user_info = wap_client.user_info('uid=sixpack,ou=People,dc=example,dc=org') if not user_info['mail'] == 'joe.sixpack@example.org': from tests.functional.synchronize import synchronize_once synchronize_once() user_info = wap_client.user_info('uid=sixpack,ou=People,dc=example,dc=org') self.assertEqual(user_info['mail'], 'joe.sixpack@example.org') - print imap.lm() + print(imap.lm()) folders = imap.lm('user/john.doe@example.org') self.assertEqual(len(folders), 0, "INBOX for john.doe still exists") folders = imap.lm('user/joe.sixpack@example.org') self.assertEqual(len(folders), 1, "INBOX for joe.sixpack does not exist") diff --git a/tests/functional/test_wallace/test_002_footer.py b/tests/functional/test_wallace/test_002_footer.py index d6e5836..5da044a 100644 --- a/tests/functional/test_wallace/test_002_footer.py +++ b/tests/functional/test_wallace/test_002_footer.py @@ -1,294 +1,294 @@ from email import message_from_string from email.MIMEMultipart import MIMEMultipart from email.MIMEBase import MIMEBase from email.MIMEImage import MIMEImage from email.MIMEText import MIMEText from email.Utils import COMMASPACE, formatdate from email import Encoders import os import smtplib import time import unittest import pykolab from pykolab import wap_client from pykolab.auth import Auth from pykolab.imap import IMAP conf = pykolab.getConf() class TestWallaceFooter(unittest.TestCase): user = None @classmethod def setUp(self): """ Compatibility for twisted.trial.unittest """ if not self.user: self.setup_class() @classmethod def setup_class(self, *args, **kw): from tests.functional.purge_users import purge_users purge_users() self.user = { 'local': 'john.doe', 'domain': 'example.org' } self.footer = {} footer_html_file = conf.get('wallace', 'footer_html') footer_text_file = conf.get('wallace', 'footer_text') if os.path.isfile(footer_text_file): self.footer['plain'] = open(footer_text_file, 'r').read() if not os.path.isfile(footer_html_file): self.footer['html'] = '

' + self.footer['plain'] + '

' else: self.footer['html'] = open(footer_html_file, 'r').read() self.send_to = 'john.doe@example.org' self.send_from = 'john.doe@example.org' self.message_to = '"Doe, John" <%s>' % (self.send_to) self.message_from = '"Doe, John" <%s>' % (self.send_from) from tests.functional.user_add import user_add user_add("John", "Doe") time.sleep(2) from tests.functional.synchronize import synchronize_once synchronize_once() @classmethod def teardown_class(self, *args, **kw): from tests.functional.purge_users import purge_users purge_users() def check_message_delivered(self, subject): imap = IMAP() imap.connect() imap.set_acl("user/john.doe@example.org", "cyrus-admin", "lrs") imap.imap.m.select("user/john.doe@example.org") found = False max_tries = 20 while not found and max_tries > 0: max_tries -= 1 typ, data = imap.imap.m.search(None, 'ALL') for num in data[0].split(): typ, msg = imap.imap.m.fetch(num, '(RFC822)') _msg = message_from_string(msg[0][1]) if _msg['Subject'] == subject: found = True time.sleep(1) return found def html_attachment(self): html_body = "

This is an HTML attachment

" html_part = MIMEBase("text", "html") html_part.add_header("Content-Disposition", "attachment", filename="html_attachment.html") html_part.set_payload(html_body) return html_part def image_attachment(self): image_file = '/usr/share/kolab-webadmin/public_html/skins/default/images/logo_kolab.png' image_part = MIMEImage(open(image_file, 'r').read()) image_part.add_header("Content-Disposition", "attachment", filename=os.path.basename(image_file)) return image_part def message_standard_params(self, subject, msg): msg['From'] = self.message_from msg['To'] = self.message_to msg['Subject'] = subject msg['Date'] = formatdate(localtime=True) return msg def send_message(self, msg, _to=None, _from=None): smtp = smtplib.SMTP('localhost', 10026) if _to is None: _to = self.send_to if _from is None: _from = self.send_from smtp.sendmail(_from, _to, msg.as_string()) def test_001_inbox_created(self): imap = IMAP() imap.connect() folders = imap.lm('user/%(local)s@%(domain)s' % (self.user)) self.assertEqual(len(folders), 1) def test_002_send_plaintext(self): subject = "test_002_send_plaintext" body = "This is a test message" msg = MIMEBase("text", "plain") msg = self.message_standard_params(subject, msg) msg.set_payload(body) self.send_message(msg) if not self.check_message_delivered(subject): raise Exception def test_003_send_plaintext_with_attachment(self): subject = "test_003_send_plaintext_with_attachment" body = "This is a test message" msg = MIMEMultipart() msg = self.message_standard_params(subject, msg) msg.attach(MIMEText(body)) msg.attach(self.image_attachment()) self.send_message(msg) if not self.check_message_delivered(subject): raise Exception def test_004_send_html(self): subject = "test_004_send_html" body = "

This is a test message

" msg = MIMEBase("text", "html") msg = self.message_standard_params(subject, msg) msg.set_payload(body) self.send_message(msg) if not self.check_message_delivered(subject): raise Exception def test_005_send_html_with_plaintext_alternative(self): subject = "test_005_send_html_with_plaintext_alternative" html_body = "

This is the HTML part

" plain_body = "This is the plaintext part" msg = MIMEMultipart("alternative") msg = self.message_standard_params(subject, msg) html_part = MIMEBase("text", "html") html_part.set_payload(html_body) msg.attach(html_part) plain_part = MIMEText(plain_body) msg.attach(plain_part) self.send_message(msg) if not self.check_message_delivered(subject): raise Exception def test_006_send_html_with_attachment(self): subject = "test_006_send_html_with_attachment" html_body = "

This is the HTML part

" plain_body = "This is the plaintext part" msg = MIMEMultipart() msg = self.message_standard_params(subject, msg) html_part = MIMEBase("text", "html") html_part.set_payload(html_body) msg.attach(html_part) msg.attach(self.image_attachment()) self.send_message(msg) if not self.check_message_delivered(subject): raise Exception def test_007_send_html_with_plaintext_alternative_and_attachment(self): subject = "test_007_send_html_with_plaintext_alternative_and_attachment" html_body = "

This is the HTML part

" plain_body = "This is the plaintext part" msg = MIMEMultipart("mixed") msg = self.message_standard_params(subject, msg) message_part = MIMEMultipart("alternative") html_part = MIMEBase("text", "html") html_part.set_payload(html_body) message_part.attach(html_part) plain_part = MIMEText(plain_body) message_part.attach(plain_part) msg.attach(message_part) msg.attach(self.image_attachment()) self.send_message(msg) if not self.check_message_delivered(subject): raise Exception def test_008_send_plaintext_with_html_attachment(self): subject = "test_008_send_plaintext_with_html_attachment" body = "This is a plaintext message" msg = MIMEMultipart() msg = self.message_standard_params(subject, msg) msg.attach(MIMEText(body)) msg.attach(self.html_attachment()) self.send_message(msg) if not self.check_message_delivered(subject): raise Exception def test_009_send_plaintext_forwarded(self): subject = "test_009_send_plaintext_forwarded" body = "This is a plaintext message" from tests.functional.user_add import user_add user_add("Jane", "Doe") from tests.functional.synchronize import synchronize_once synchronize_once() admin_login = conf.get('cyrus-imap', 'admin_login') admin_password = conf.get('cyrus-imap', 'admin_password') import sievelib.factory script = sievelib.factory.FiltersSet("test_wallace_test_009_forward") script.require("copy") script.addfilter("forward", ["true"], [("redirect", ":copy", "john.doe@example.org")]) import sievelib.managesieve sieveclient = sievelib.managesieve.Client('localhost', 4190, True) sieveclient.connect(None, None, True) sieveclient._plain_authentication(admin_login, admin_password, 'jane.doe@example.org') sieveclient.authenticated = True script_str = script.__str__() - print script_str + print(script_str) sieveclient.putscript("test_wallace_test_009_forward", script_str) sieveclient.setactive("test_wallace_test_009_forward") msg = MIMEText(body) msg['From'] = self.message_from msg['To'] = '"Doe, Jane" ' msg['Subject'] = subject msg['Date'] = formatdate(localtime=True) self.send_message(msg, _to='jane.doe@example.org', _from='john.doe@example.org') raise Exception diff --git a/tests/functional/test_wallace/test_006_resource_performance.py b/tests/functional/test_wallace/test_006_resource_performance.py index 814a111..39162c4 100644 --- a/tests/functional/test_wallace/test_006_resource_performance.py +++ b/tests/functional/test_wallace/test_006_resource_performance.py @@ -1,142 +1,142 @@ import time import datetime import pykolab import pytz import uuid import string import random from pykolab.xml import Event from pykolab.xml import Attendee from pykolab.imap import IMAP from wallace import module_resources from twisted.trial import unittest from tests.functional import resource_func as funcs from tests.functional.synchronize import synchronize_once conf = pykolab.getConf() class TestWallacePerformance(unittest.TestCase): rooms = None @classmethod def setUp(self): """ Compatibility for twisted.trial.unittest """ if not self.rooms: self.setup_class() @classmethod def setup_class(self, *args, **kw): funcs.purge_resources() self.room1 = funcs.resource_add("confroom", "Room 101") self.room2 = funcs.resource_add("confroom", "Conference Room B-222") self.rooms = funcs.resource_add("collection", "Rooms", [self.room1['dn'], self.room2['dn']]) time.sleep(1) synchronize_once() module_resources.imap = IMAP() module_resources.imap.connect() def purge_mailbox(self, mailbox): imap = IMAP() imap.connect() imap.set_acl(mailbox, "cyrus-admin", "lrwcdest") imap.imap.m.select(imap.folder_quote(mailbox)) typ, data = imap.imap.m.search(None, 'ALL') for num in data[0].split(): imap.imap.m.store(num, '+FLAGS', '\\Deleted') imap.imap.m.expunge() imap.disconnect() def populate_calendar(self, resource, num=10, date=None): if date is None: date = datetime.datetime.now(pytz.timezone("Europe/London")) i = 0 while i < num: offset = random.randint(-3200, 3200) * 10 duration = random.randint(3, 72) * 10 summary = ''.join(random.sample((string.ascii_uppercase + string.digits) * 12, random.randint(6, 18))) start = date + datetime.timedelta(minutes=offset) event = Event() event.set_summary(summary) event.set_start(start) event.set_end(start + datetime.timedelta(minutes=duration)) saved = module_resources.save_resource_event(dict(xml=event), resource) i += 1 def test_001_save_resource_event(self): event = Event() event.set_summary("test") date = datetime.datetime.now(pytz.timezone("Europe/London")) event.set_start(date) event.set_end(date + datetime.timedelta(hours=2)) saved = module_resources.save_resource_event(dict(xml=event), self.room1) self.assertTrue(saved) def test_002_read_resource_calendar(self): self.purge_mailbox(self.room1['kolabtargetfolder']) event = Event() event.set_summary("test") event.set_start(datetime.datetime(2014, 4, 1, 12, 0, 0, tzinfo=pytz.timezone("Europe/London"))) event.set_end(datetime.datetime(2014, 4, 1, 14, 0, 0, tzinfo=pytz.timezone("Europe/London"))) saved = module_resources.save_resource_event(dict(xml=event), self.room1) self.assertTrue(saved) uid = event.get_uid() itip = dict( uid=str(uuid.uuid4()), sequence=0, start=datetime.datetime(2014, 4, 1, 13, 0, 0, tzinfo=pytz.timezone("Europe/London")), end=datetime.datetime(2014, 4, 1, 14, 30, 0, tzinfo=pytz.timezone("Europe/London")) ) event.set_uid(itip['uid']) event.set_start(itip['start']) event.set_end(itip['end']) itip['xml'] = event res = module_resources.read_resource_calendar(self.room1, [itip]) self.assertEqual(res, 1) self.assertTrue(self.room1['conflict']) self.assertIn(uid, self.room1['conflicting_events']) def test_003_read_time(self): self.purge_mailbox(self.room1['kolabtargetfolder']) # populate 5K random events num = 5000 date = datetime.datetime.now(pytz.timezone("Europe/London")).replace(hour=10, minute=0, second=0, microsecond=0) self.populate_calendar(self.room1, num, date) itip = dict( uid=str(uuid.uuid4()), sequence=0, start=date, end=date + datetime.timedelta(minutes=90) ) event = Event() event.set_uid(itip['uid']) event.set_start(itip['start']) event.set_end(itip['end']) itip['xml'] = event start = time.time() res = module_resources.read_resource_calendar(self.room1, [itip]) self.assertEqual(res, num) - print "\nREAD TIME:", time.time() - start - print "CONFLICTS:", self.room1['conflicting_events'] + print("\nREAD TIME:", time.time() - start) + print("CONFLICTS:", self.room1['conflicting_events']) diff --git a/tests/functional/test_wap_client/test_002_user_add.py b/tests/functional/test_wap_client/test_002_user_add.py index 771e700..b4d4752 100644 --- a/tests/functional/test_wap_client/test_002_user_add.py +++ b/tests/functional/test_wap_client/test_002_user_add.py @@ -1,78 +1,78 @@ import time import unittest import pykolab from pykolab import wap_client from pykolab.auth import Auth from pykolab.imap import IMAP conf = pykolab.getConf() class TestUserAdd(unittest.TestCase): @classmethod def setup_class(self, *args, **kw): from tests.functional.purge_users import purge_users purge_users() self.user = { 'local': 'john.doe', 'domain': 'example.org' } from tests.functional.user_add import user_add user_add("John", "Doe") from tests.functional.synchronize import synchronize_once synchronize_once() @classmethod def teardown_class(self, *args, **kw): from tests.functional.purge_users import purge_users purge_users() def test_001_inbox_created(self): time.sleep(2) imap = IMAP() imap.connect() folders = imap.lm('user/%(local)s@%(domain)s' % (self.user)) self.assertEqual(len(folders), 1) def test_002_autocreate_folders_created(self): time.sleep(2) imap = IMAP() imap.connect() exec("ac_folders = %s" % (conf.get_raw(conf.get('kolab', 'primary_domain'), 'autocreate_folders'))) folders = imap.lm('user/%(local)s/*@%(domain)s' % (self.user)) - print folders - print ac_folders.keys() + print(folders) + print(ac_folders.keys()) self.assertEqual(len(folders), len(ac_folders.keys())) def test_003_folders_metadata_set(self): imap = IMAP() imap.connect() exec("ac_folders = %s" % (conf.get_raw(conf.get('kolab', 'primary_domain'), 'autocreate_folders'))) folders = [] folders.extend(imap.lm('user/%(local)s@%(domain)s' % (self.user))) folders.extend(imap.lm('user/%(local)s/*@%(domain)s' % (self.user))) for folder in folders: metadata = imap.get_metadata(folder) folder_name = '/'.join(folder.split('/')[2:]).split('@')[0] if folder_name in ac_folders: if 'annotations' in ac_folders[folder_name]: for _annotation in ac_folders[folder_name]['annotations']: if _annotation.startswith('/private/'): continue _annotation_value = ac_folders[folder_name]['annotations'][_annotation] self.assertTrue(_annotation in metadata[metadata.keys().pop()]) self.assertEqual(_annotation_value, metadata[metadata.keys().pop()][_annotation]) diff --git a/tests/functional/test_wap_client/test_003_user_add_fr_FR.py b/tests/functional/test_wap_client/test_003_user_add_fr_FR.py index 72cf84b..497d26c 100644 --- a/tests/functional/test_wap_client/test_003_user_add_fr_FR.py +++ b/tests/functional/test_wap_client/test_003_user_add_fr_FR.py @@ -1,56 +1,56 @@ # -*- coding: utf-8 -*- import time import unittest import pykolab from pykolab import wap_client from pykolab.auth import Auth from pykolab.imap import IMAP conf = pykolab.getConf() class TestUserAddFrFR(unittest.TestCase): @classmethod def setup_class(self, *args, **kw): from tests.functional.purge_users import purge_users purge_users() self.user = { 'local': 'etienne-nicolas.mehul', 'domain': 'example.org' } from tests.functional.user_add import user_add user_add("Étienne-Nicolas", "Méhul", 'fr_FR') from tests.functional.synchronize import synchronize_once synchronize_once() @classmethod def teardown_class(self, *args, **kw): from tests.functional.purge_users import purge_users purge_users() def test_001_inbox_created(self): time.sleep(2) imap = IMAP() imap.connect() folders = imap.lm('user/%(local)s@%(domain)s' % (self.user)) self.assertEqual(len(folders), 1) def test_002_fr_FR_user_recipient_policy(self): auth = Auth() auth.connect() recipient = auth.find_recipient("%(local)s@%(domain)s" % (self.user)) if hasattr(self, 'assertIsInstance'): self.assertIsInstance(recipient, str) self.assertEqual(recipient, "uid=mehul,ou=People,dc=example,dc=org") result = wap_client.user_info(recipient) - print result + print(result) self.assertEqual(result['mail'], 'etienne-nicolas.mehul@example.org') self.assertEqual(sorted(result['alias']), ['e.mehul@example.org', 'mehul@example.org']) diff --git a/tests/unit/test-008-sievelib.py b/tests/unit/test-008-sievelib.py index 9226ad6..e3395e8 100644 --- a/tests/unit/test-008-sievelib.py +++ b/tests/unit/test-008-sievelib.py @@ -1,57 +1,57 @@ import sys import unittest sieve_scripts = [ # You're average vacation script. """ require [ "vacation" ]; if anyof (true) { vacation :days 1 :subject "Out of Office" "I'm out of the office"; } """, # A non-any/allof if (control) header (test) structure """ require ["fileinto"]; if header :contains "X-Spam-Flag" "YES" { fileinto "Spam"; stop; } """, # The same, all on the same line """ require ["fileinto"]; if header :contains "X-Spam-Flag" "YES" { fileinto "Spam"; stop; } """, # A little more of a complex list of tests """ require ["fileinto"]; if allof (header :contains "X-Mailer" "OTRS", header :contains "X-Powered-By" "OTRS", header :contains "Organization" "Example, Inc.") { fileinto "OTRS"; stop; } """, ] class TestSievelib(unittest.TestCase): def test_001_import_sievelib(self): from sievelib.parser import Parser def test_002_parse_tests(self): from sievelib.parser import Parser sieve_parser = Parser(debug=True) i = 0 for sieve_str in sieve_scripts: i += 1 result = sieve_parser.parse(sieve_str) if not result: - print "Sieve line: %r" % (sieve_parser.lexer.text.split('\n')[(sieve_parser.lexer.text[:sieve_parser.lexer.pos].count('\n'))]) + print("Sieve line: %r" % (sieve_parser.lexer.text.split('\n')[(sieve_parser.lexer.text[:sieve_parser.lexer.pos].count('\n'))])) raise Exception("Failed parsing Sieve script #%d: %s" % (i, sieve_parser.error)) diff --git a/wallace.py b/wallace.py index a45f34a..6fbcadd 100755 --- a/wallace.py +++ b/wallace.py @@ -1,39 +1,41 @@ #!/usr/bin/python # # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from __future__ import print_function + import sys # For development purposes sys.path.extend(['.', '..']) from pykolab.translate import _ try: from pykolab.constants import * except ImportError, e: - print >> sys.stderr, _("Cannot load pykolab/constants.py:") - print >> sys.stderr, "%s" % e + print(_("Cannot load pykolab/constants.py:"), file=sys.stderr) + print("%s" % e, file=sys.stderr) sys.exit(1) import wallace if __name__ == "__main__": wallace = wallace.WallaceDaemon() wallace.run() diff --git a/wallace/module_optout.py b/wallace/module_optout.py index 632753f..2b9d4b2 100644 --- a/wallace/module_optout.py +++ b/wallace/module_optout.py @@ -1,192 +1,192 @@ # -*- coding: utf-8 -*- # Copyright 2010-2013 Kolab Systems AG (http://www.kolabsys.com) # # Jeroen van Meeuwen (Kolab Systems) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import json import os import random import tempfile import time from urlparse import urlparse import urllib from email import message_from_file from email.utils import formataddr from email.utils import getaddresses import modules import pykolab from pykolab.translate import _ log = pykolab.getLogger('pykolab.wallace/optout') conf = pykolab.getConf() mybasepath = '/var/spool/pykolab/wallace/optout/' def __init__(): modules.register('optout', execute, description=description()) def description(): return """Consult the opt-out service.""" def execute(*args, **kw): if not os.path.isdir(mybasepath): os.makedirs(mybasepath) for stage in ['incoming', 'ACCEPT', 'REJECT', 'HOLD', 'DEFER' ]: if not os.path.isdir(os.path.join(mybasepath, stage)): os.makedirs(os.path.join(mybasepath, stage)) # TODO: Test for correct call. filepath = args[0] if kw.has_key('stage'): log.debug(_("Issuing callback after processing to stage %s") % (kw['stage']), level=8) log.debug(_("Testing cb_action_%s()") % (kw['stage']), level=8) if hasattr(modules, 'cb_action_%s' % (kw['stage'])): log.debug(_("Attempting to execute cb_action_%s()") % (kw['stage']), level=8) exec('modules.cb_action_%s(%r, %r)' % (kw['stage'],'optout',filepath)) return #modules.next_module('optout') log.debug(_("Consulting opt-out service for %r, %r") % (args, kw), level=8) message = message_from_file(open(filepath, 'r')) envelope_sender = getaddresses(message.get_all('From', [])) recipients = { "To": getaddresses(message.get_all('To', [])), "Cc": getaddresses(message.get_all('Cc', [])) # TODO: Are those all recipient addresses? } # optout answers are ACCEPT, REJECT, HOLD or DEFER answers = [ 'ACCEPT', 'REJECT', 'HOLD', 'DEFER' ] # Initialize our results placeholders. _recipients = {} for answer in answers: _recipients[answer] = { "To": [], "Cc": [] } for recipient_type in recipients.keys(): for recipient in recipients[recipient_type]: log.debug( _("Running opt-out consult from envelope sender '%s " + \ "<%s>' to recipient %s <%s>") % ( envelope_sender[0][0], envelope_sender[0][1], recipient[0], recipient[1] ), level=8 ) optout_answer = request( { 'unique-message-id': 'bogus', 'envelope_sender': envelope_sender[0][1], 'recipient': recipient[1] } ) _recipients[optout_answer][recipient_type].append(recipient) #print _recipients ## ## TODO ## ## If one of them all is DEFER, DEFER the entire message and discard the ## other results. ## for answer in answers: # Create the directory for the answer if not os.path.isdir(os.path.join(mybasepath, answer)): os.makedirs(os.path.join(mybasepath, answer)) # Consider using a new mktemp()-like call new_filepath = os.path.join(mybasepath, answer, os.path.basename(filepath)) # Write out a message file representing the new contents for the message # use formataddr(recipient) _message = message_from_file(open(filepath, 'r')) use_this = False for recipient_type in _recipients[answer].keys(): _message.__delitem__(recipient_type) if not len(_recipients[answer][recipient_type]) == 0: _message.__setitem__( recipient_type, ',\n '.join( [formataddr(x) for x in _recipients[answer][recipient_type]] ) ) use_this = True if use_this: # TODO: Do not set items with an empty list. (fp, filename) = tempfile.mkstemp(dir="/var/spool/pykolab/wallace/optout/%s" % (answer)) os.write(fp, _message.__str__()) os.close(fp) # Callback with new filename if hasattr(modules, 'cb_action_%s' % (answer)): log.debug(_("Attempting to execute cb_action_%s(%r, %r)") % (answer, 'optout', filename), level=8) exec('modules.cb_action_%s(%r, %r)' % (answer,'optout', filename)) os.unlink(filepath) #print "Moving filepath %s to new_filepath %s" % (filepath, new_filepath) #os.rename(filepath, new_filepath) #if hasattr(modules, 'cb_action_%s' % (optout_answer)): #log.debug(_("Attempting to execute cb_action_%s()") % (optout_answer), level=8) #exec('modules.cb_action_%s(%r, %r)' % (optout_answer,'optout', new_filepath)) #return def request(params=None): params = json.dumps(params) optout_url = conf.get('wallace_optout', 'optout_url') try: f = urllib.urlopen(optout_url, params) except Exception, e: log.error(_("Could not send request to optout_url %s") % (optout_url)) return "DEFER" response = f.read() try: response_data = json.loads(response) except ValueError, e: # Some data is not JSON - print "Response data is not JSON" + print("Response data is not JSON") return response_data['result']