diff --git a/pykolab/auth/ldap/syncrepl.py b/pykolab/auth/ldap/syncrepl.py index 03ab5ae..72f5f37 100644 --- a/pykolab/auth/ldap/syncrepl.py +++ b/pykolab/auth/ldap/syncrepl.py @@ -1,119 +1,119 @@ #!/usr/bin/python import anydbm import ldap import ldap.syncrepl import ldapurl import pykolab from pykolab import utils log = pykolab.getLogger('pykolab.syncrepl') conf = pykolab.getConf() class DNSync(ldap.ldapobject.LDAPObject,ldap.syncrepl.SyncreplConsumer): callback = None def __init__(self, filename, *args, **kwargs): if kwargs.has_key('callback'): self.callback = kwargs['callback'] del kwargs['callback'] ldap.ldapobject.LDAPObject.__init__(self, *args, **kwargs) - self.__db = anydbm.open(filename, 'c', 0640) + self.__db = anydbm.open(filename, 'c', 0o640) self.__presentUUIDs = {} def syncrepl_set_cookie(self,cookie): self.__db['cookie'] = cookie def syncrepl_get_cookie(self): if 'cookie' in self.__db: return self.__db['cookie'] def syncrepl_delete(self, uuids): log.debug("syncrepl_delete uuids: %r" % (uuids), level=8) # Get the unique_attribute name to issue along with our # callback (if any) unique_attr = conf.get('ldap', 'unique_attribute') if unique_attr == None: unique_attr = 'entryuuid' if unique_attr == 'nsuniqueid': log.warning( _("The name of the persistent, unique attribute " + \ "is very probably not compatible with the use of " + \ "syncrepl.") ) for uuid in uuids: dn = self.__db[uuid] log.debug("syncrepl_delete dn: %r" % (dn), level=8) if not self.callback == None: self.callback( change_type='delete', previous_dn=None, change_number=None, dn=dn, entry={ unique_attr: uuid } ) del self.__db[uuid] def syncrepl_present(self, uuids, refreshDeletes=False): if uuids is None: if refreshDeletes is False: nonpresent = [] for uuid in self.__db.keys(): if uuid == 'cookie': continue if uuid in self.__presentUUIDs: continue nonpresent.append(uuid) self.syncrepl_delete(nonpresent) self.__presentUUIDs = {} else: for uuid in uuids: self.__presentUUIDs[uuid] = True def syncrepl_entry(self, dn, attrs, uuid): attrs = utils.normalize(attrs) if uuid in self.__db: odn = self.__db[uuid] if odn != dn: if not self.callback == None: self.callback( change_type='moddn', previous_dn=odn, change_number=None, dn=dn, entry=attrs ) else: if not self.callback == None: self.callback( change_type='modify', previous_dn=None, change_number=None, dn=self.__db[uuid], entry=attrs ) else: if not self.callback == None: self.callback( change_type='add', previous_dn=None, change_number=None, dn=dn, entry=attrs ) self.__db[uuid] = dn diff --git a/pykolab/logger.py b/pykolab/logger.py index c2237cb..8e9c9b6 100644 --- a/pykolab/logger.py +++ b/pykolab/logger.py @@ -1,274 +1,274 @@ # -*- 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 grp import logging import logging.handlers import os import pwd import sys class StderrToLogger: """ Fake file-like stream object that redirects writes to a logger instance. """ def __init__(self, logger, log_level=logging.DEBUG): self.logger = logger self.log_level = log_level self.linebuf = '' self.skip_next = False def write(self, buf): # ugly patch to make smtplib and smtpd debug logging records appear on one line in log file # smtplib uses "print>>stderr, var, var" statements for debug logging. These # statements are splited into separate lines on separating whitespace. for line in buf.rstrip().splitlines(): if self.skip_next: self.skip_next = False continue if buf != '\n': linestarts = line.split(':')[0] if linestarts in ['send', 'reply', 'Data', 'recips', 'Peer', 'sender']: self.linebuf = line elif linestarts.startswith('===>'): # Do not log lines starting with ====> self.linebuf = '' self.skip_next = True continue else: self.logger.log(self.log_level, '%s %s', self.linebuf, line.rstrip()[:150]) self.linebuf = '' def flush(self): pass class LoggerAdapter(logging.LoggerAdapter): """ Custom LoggingAdapter to log Wallace mail message Queue ID """ def process(self, msg, kwargs): return '%s %s' % (self.extra['qid'], msg), kwargs class Logger(logging.Logger): """ The PyKolab version of a logger. This class wraps the Python native logging library, adding to the loglevel capabilities, a debuglevel capability. """ debuglevel = 0 fork = False loglevel = logging.CRITICAL process_username = 'kolab' process_groupname = 'kolab-n' if hasattr(sys, 'argv'): for arg in sys.argv: if debuglevel == -1: try: debuglevel = (int)(arg) except ValueError: continue loglevel = logging.DEBUG break if arg == '-d': debuglevel = -1 continue if arg == '-l': loglevel = -1 continue if arg == '--fork': fork = True if loglevel == -1: if hasattr(logging, arg.upper()): loglevel = getattr(logging, arg.upper()) else: loglevel = logging.DEBUG if arg in ['-u', '--user']: process_username = -1 continue if arg.startswith('--user='): process_username = arg.split('=')[1] if process_username == -1: process_username = arg if arg in ['-g', '--group']: process_groupname = -1 continue if arg.startswith('--group='): process_groupname = arg.split('=')[1] if process_groupname == -1: process_groupname = arg # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-statements def __init__(self, *args, **kw): if 'name' in kw: name = kw['name'] elif len(args) == 1: name = args[0] else: name = 'pykolab' logging.Logger.__init__(self, name) plaintextformatter = logging.Formatter( "%(asctime)s %(name)s %(levelname)s [%(process)d] %(message)s" ) if not self.fork: self.console_stdout = logging.StreamHandler(sys.stdout) self.console_stdout.setFormatter(plaintextformatter) self.addHandler(self.console_stdout) if 'logfile' in kw: self.logfile = kw['logfile'] else: self.logfile = '/var/log/kolab/pykolab.log' group_gid = 0 user_uid = 0 # Make sure (read: attempt to change) the permissions try: try: (ruid, _, _) = os.getresuid() (rgid, _, _) = os.getresgid() except AttributeError: ruid = os.getuid() rgid = os.getgid() if ruid == 0: # Means we can setreuid() / setregid() / setgroups() if rgid == 0: # Get group entry details try: ( _, _, group_gid, _ ) = grp.getgrnam(self.process_groupname) except KeyError: group_gid = False if ruid == 0: # Means we haven't switched yet. try: ( _, _, user_uid, _, _, _, _ ) = pwd.getpwnam(self.process_username) except KeyError: user_uid = False if os.path.isfile(self.logfile): try: if user_uid > 0 or group_gid > 0: os.chown( self.logfile, user_uid, group_gid ) - os.chmod(self.logfile, '0660') + os.chmod(self.logfile, 0o660) except Exception as errmsg: self.error( _("Could not change permissions on %s: %r") % (self.logfile, errmsg) ) if self.debuglevel > 8: import traceback traceback.print_exc() except Exception as errmsg: if os.path.isfile(self.logfile): self.error(_("Could not change permissions on %s: %r") % (self.logfile, errmsg)) if self.debuglevel > 8: import traceback traceback.print_exc() # Make sure the log file exists try: fhandle = open(self.logfile, 'a') try: os.utime(self.logfile, None) finally: fhandle.close() try: filelog_handler = logging.FileHandler(filename=self.logfile) filelog_handler.setFormatter(plaintextformatter) except IOError as errmsg: print(_("Cannot log to file %s: %s") % (self.logfile, errmsg), file=sys.stderr) if len(self.handlers) <= 1: try: self.addHandler(filelog_handler) except Exception: pass except IOError: pass def remove_stdout_handler(self): if not self.fork: self.console_stdout.close() self.removeHandler(self.console_stdout) # pylint: disable=arguments-differ # pylint: disable=keyword-arg-before-vararg def debug(self, msg, level=1, *args, **kw): self.setLevel(self.loglevel) # Work around other applications not using various levels of debugging if not self.name.startswith('pykolab') and self.debuglevel != 9: return if level <= self.debuglevel: self.log(logging.DEBUG, msg) logging.setLoggerClass(Logger) diff --git a/pykolab/setup/setup_mta.py b/pykolab/setup/setup_mta.py index c3c4154..903acd7 100644 --- a/pykolab/setup/setup_mta.py +++ b/pykolab/setup/setup_mta.py @@ -1,540 +1,540 @@ # -*- 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 augeas import Augeas from Cheetah.Template import Template import os import shutil import subprocess 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('mta', execute, description=description(), after=['ldap']) def description(): return _("Setup MTA.") def execute(*args, **kw): group_filter = conf.get('ldap','kolab_group_filter') if group_filter == None: group_filter = conf.get('ldap','group_filter') user_filter = conf.get('ldap','kolab_user_filter') if user_filter == None: user_filter = conf.get('ldap','user_filter') resource_filter = conf.get('ldap', 'resource_filter') sharedfolder_filter = conf.get('ldap', 'sharedfolder_filter') server_host = utils.parse_ldap_uri(conf.get('ldap', 'ldap_uri'))[1] files = { "/etc/postfix/ldap/local_recipient_maps.cf": """ server_host = %(server_host)s server_port = 389 version = 3 search_base = %(base_dn)s scope = sub domain = ldap:/etc/postfix/ldap/mydestination.cf bind_dn = %(service_bind_dn)s bind_pw = %(service_bind_pw)s query_filter = (&(|(mail=%%s)(alias=%%s))(|%(kolab_user_filter)s%(kolab_group_filter)s%(resource_filter)s%(sharedfolder_filter)s)) result_attribute = mail """ % { "base_dn": conf.get('ldap', 'base_dn'), "server_host": server_host, "service_bind_dn": conf.get('ldap', 'service_bind_dn'), "service_bind_pw": conf.get('ldap', 'service_bind_pw'), "kolab_user_filter": user_filter, "kolab_group_filter": group_filter, "resource_filter": resource_filter, "sharedfolder_filter": sharedfolder_filter, }, "/etc/postfix/ldap/mydestination.cf": """ server_host = %(server_host)s server_port = 389 version = 3 search_base = %(domain_base_dn)s scope = sub bind_dn = %(service_bind_dn)s bind_pw = %(service_bind_pw)s query_filter = %(domain_filter)s result_attribute = %(domain_name_attribute)s """ % { "server_host": server_host, "domain_base_dn": conf.get('ldap', 'domain_base_dn'), "domain_filter": conf.get('ldap', 'domain_filter').replace('*', '%s'), "domain_name_attribute": conf.get('ldap', 'domain_name_attribute'), "service_bind_dn": conf.get('ldap', 'service_bind_dn'), "service_bind_pw": conf.get('ldap', 'service_bind_pw'), }, "/etc/postfix/ldap/mailenabled_distgroups.cf": """ server_host = %(server_host)s server_port = 389 version = 3 search_base = %(group_base_dn)s scope = sub domain = ldap:/etc/postfix/ldap/mydestination.cf bind_dn = %(service_bind_dn)s bind_pw = %(service_bind_pw)s # This finds the mail enabled distribution group LDAP entry query_filter = (&(|(mail=%%s)(alias=%%s))(objectClass=kolabgroupofuniquenames)(objectclass=groupofuniquenames)(!(objectclass=groupofurls))) # From this type of group, get all uniqueMember DNs special_result_attribute = uniqueMember # Only from those DNs, get the mail result_attribute = leaf_result_attribute = mail """ % { "server_host": server_host, "group_base_dn": conf.get('ldap', 'group_base_dn'), "service_bind_dn": conf.get('ldap', 'service_bind_dn'), "service_bind_pw": conf.get('ldap', 'service_bind_pw'), }, "/etc/postfix/ldap/mailenabled_dynamic_distgroups.cf": """ server_host = %(server_host)s server_port = 389 version = 3 search_base = %(group_base_dn)s scope = sub domain = ldap:/etc/postfix/ldap/mydestination.cf bind_dn = %(service_bind_dn)s bind_pw = %(service_bind_pw)s # This finds the mail enabled dynamic distribution group LDAP entry query_filter = (&(|(mail=%%s)(alias=%%s))(objectClass=kolabgroupofuniquenames)(objectClass=groupOfURLs)) # From this type of group, get all memberURL searches/references special_result_attribute = memberURL # Only from those DNs, get the mail result_attribute = leaf_result_attribute = mail """ % { "server_host": server_host, "group_base_dn": conf.get('ldap', 'group_base_dn'), "service_bind_dn": conf.get('ldap', 'service_bind_dn'), "service_bind_pw": conf.get('ldap', 'service_bind_pw'), }, "/etc/postfix/ldap/transport_maps.cf": """ server_host = %(server_host)s server_port = 389 version = 3 search_base = %(base_dn)s scope = sub domain = ldap:/etc/postfix/ldap/mydestination.cf bind_dn = %(service_bind_dn)s bind_pw = %(service_bind_pw)s query_filter = (&(|(mailAlternateAddress=%%s)(alias=%%s)(mail=%%s))(objectclass=kolabinetorgperson)) result_attribute = mail result_format = lmtp:unix:/var/lib/imap/socket/lmtp """ % { "base_dn": conf.get('ldap', 'base_dn'), "server_host": server_host, "service_bind_dn": conf.get('ldap', 'service_bind_dn'), "service_bind_pw": conf.get('ldap', 'service_bind_pw'), }, "/etc/postfix/ldap/virtual_alias_maps.cf": """ server_host = %(server_host)s server_port = 389 version = 3 search_base = %(base_dn)s scope = sub domain = ldap:/etc/postfix/ldap/mydestination.cf bind_dn = %(service_bind_dn)s bind_pw = %(service_bind_pw)s query_filter = (&(|(mail=%%s)(alias=%%s))(objectclass=kolabinetorgperson)) result_attribute = mail """ % { "base_dn": conf.get('ldap', 'base_dn'), "server_host": server_host, "service_bind_dn": conf.get('ldap', 'service_bind_dn'), "service_bind_pw": conf.get('ldap', 'service_bind_pw'), }, "/etc/postfix/ldap/virtual_alias_maps_mailforwarding.cf": """ server_host = %(server_host)s server_port = 389 version = 3 search_base = %(base_dn)s scope = sub domain = ldap:/etc/postfix/ldap/mydestination.cf bind_dn = %(service_bind_dn)s bind_pw = %(service_bind_pw)s query_filter = (&(|(mail=%%s)(alias=%%s))(objectclass=mailrecipient)(objectclass=inetorgperson)(mailforwardingaddress=*)) result_attribute = mailForwardingAddress """ % { "base_dn": conf.get('ldap', 'base_dn'), "server_host": server_host, "service_bind_dn": conf.get('ldap', 'service_bind_dn'), "service_bind_pw": conf.get('ldap', 'service_bind_pw'), }, "/etc/postfix/ldap/virtual_alias_maps_sharedfolders.cf": """ server_host = %(server_host)s server_port = 389 version = 3 search_base = %(base_dn)s scope = sub domain = ldap:/etc/postfix/ldap/mydestination.cf bind_dn = %(service_bind_dn)s bind_pw = %(service_bind_pw)s query_filter = (&(|(mail=%%s)(alias=%%s))(objectclass=kolabsharedfolder)(kolabFolderType=mail)) result_attribute = kolabtargetfolder result_format = "shared+%%s" """ % { "base_dn": conf.get('ldap', 'base_dn'), "server_host": server_host, "service_bind_dn": conf.get('ldap', 'service_bind_dn'), "service_bind_pw": conf.get('ldap', 'service_bind_pw'), }, } if not os.path.isfile('/etc/postfix/main.cf'): if os.path.isfile('/usr/share/postfix/main.cf.debian'): shutil.copy( '/usr/share/postfix/main.cf.debian', '/etc/postfix/main.cf' ) if not os.path.isdir('/etc/postfix/ldap'): - os.mkdir('/etc/postfix/ldap/', 0770) + os.mkdir('/etc/postfix/ldap/', 0o770) for filename in files.keys(): fp = open(filename, 'w') fp.write(files[filename]) fp.close() fp = open('/etc/postfix/transport', 'a') fp.write("\n# Shared Folder Delivery for %(domain)s:\nshared@%(domain)s\t\tlmtp:unix:/var/lib/imap/socket/lmtp\n" % {'domain': conf.get('kolab', 'primary_domain')}) fp.close() subprocess.call(["postmap", "/etc/postfix/transport"]) postfix_main_settings = { "inet_interfaces": "all", "recipient_delimiter": "+", "local_recipient_maps": "ldap:/etc/postfix/ldap/local_recipient_maps.cf", "mydestination": "ldap:/etc/postfix/ldap/mydestination.cf", "transport_maps": "ldap:/etc/postfix/ldap/transport_maps.cf, hash:/etc/postfix/transport", "virtual_alias_maps": "$alias_maps, ldap:/etc/postfix/ldap/virtual_alias_maps.cf, ldap:/etc/postfix/ldap/virtual_alias_maps_mailforwarding.cf, ldap:/etc/postfix/ldap/virtual_alias_maps_sharedfolders.cf, ldap:/etc/postfix/ldap/mailenabled_distgroups.cf, ldap:/etc/postfix/ldap/mailenabled_dynamic_distgroups.cf", "smtpd_tls_auth_only": "yes", "smtpd_tls_security_level": "may", "smtp_tls_security_level": "may", "smtpd_sasl_auth_enable": "yes", "smtpd_sender_login_maps": "$local_recipient_maps", "smtpd_data_restrictions": "permit_mynetworks, check_policy_service unix:private/recipient_policy_incoming", "smtpd_recipient_restrictions": "permit_mynetworks, reject_unauth_pipelining, reject_rbl_client zen.spamhaus.org, reject_non_fqdn_recipient, reject_invalid_helo_hostname, reject_unknown_recipient_domain, reject_unauth_destination, check_policy_service unix:private/recipient_policy_incoming, permit", "smtpd_sender_restrictions": "permit_mynetworks, reject_sender_login_mismatch, check_policy_service unix:private/sender_policy_incoming", "submission_recipient_restrictions": "check_policy_service unix:private/submission_policy, permit_sasl_authenticated, reject", "submission_sender_restrictions": "reject_non_fqdn_sender, check_policy_service unix:private/submission_policy, permit_sasl_authenticated, reject", "submission_data_restrictions": "check_policy_service unix:private/submission_policy", "content_filter": "smtp-amavis:[127.0.0.1]:10024" } if os.path.isfile('/etc/pki/tls/certs/make-dummy-cert') and not os.path.isfile('/etc/pki/tls/private/localhost.pem'): subprocess.call(['/etc/pki/tls/certs/make-dummy-cert', '/etc/pki/tls/private/localhost.pem']) if os.path.isfile('/etc/pki/tls/private/localhost.pem'): postfix_main_settings['smtpd_tls_cert_file'] = "/etc/pki/tls/private/localhost.pem" postfix_main_settings['smtpd_tls_key_file'] = "/etc/pki/tls/private/localhost.pem" elif os.path.isfile('/etc/ssl/private/cyrus-imapd.pem'): # Debian 9 postfix_main_settings['smtpd_tls_cert_file'] = "/etc/ssl/private/cyrus-imapd.pem" postfix_main_settings['smtpd_tls_key_file'] = "/etc/ssl/private/cyrus-imapd.pem" # Copy header checks files for hc_file in [ 'inbound', 'internal', 'submission' ]: if not os.path.isfile("/etc/postfix/header_checks.%s" % (hc_file)): if os.path.isfile('/etc/kolab/templates/header_checks.%s' % (hc_file)): input_file = '/etc/kolab/templates/header_checks.%s' % (hc_file) elif os.path.isfile('/usr/share/kolab/templates/header_checks.%s' % (hc_file)): input_file = '/usr/share/kolab/templates/header_checks.%s' % (hc_file) elif os.path.isfile(os.path.abspath(os.path.join(__file__, '..', '..', '..', 'share', 'templates', 'header_checks.%s' % (hc_file)))): input_file = os.path.abspath(os.path.join(__file__, '..', '..', '..', 'share', 'templates', 'header_checks.%s' % (hc_file))) shutil.copy(input_file, "/etc/postfix/header_checks.%s" % (hc_file)) subprocess.call(["postmap", "/etc/postfix/header_checks.%s" % (hc_file)]) myaugeas = Augeas() setting_base = '/files/etc/postfix/main.cf/' for setting_key in postfix_main_settings.keys(): setting = os.path.join(setting_base,setting_key) current_value = myaugeas.get(setting) if current_value == None: try: myaugeas.set(setting, postfix_main_settings[setting_key]) except: insert_paths = myaugeas.match('/files/etc/postfix/main.cf/*') insert_path = insert_paths[(len(insert_paths)-1)] myaugeas.insert(insert_path, setting_key, False) log.debug(_("Setting key %r to %r") % (setting_key, postfix_main_settings[setting_key]), level=8) myaugeas.set(setting, postfix_main_settings[setting_key]) myaugeas.save() postfix_master_settings = { } if os.path.exists('/usr/lib/postfix/kolab_smtp_access_policy'): postfix_master_settings['kolab_sap_executable_path'] = '/usr/lib/postfix/kolab_smtp_access_policy' else: postfix_master_settings['kolab_sap_executable_path'] = '/usr/libexec/postfix/kolab_smtp_access_policy' template_file = None if os.path.isfile('/etc/kolab/templates/master.cf.tpl'): template_file = '/etc/kolab/templates/master.cf.tpl' elif os.path.isfile('/usr/share/kolab/templates/master.cf.tpl'): template_file = '/usr/share/kolab/templates/master.cf.tpl' elif os.path.isfile(os.path.abspath(os.path.join(__file__, '..', '..', '..', 'share', 'templates', 'master.cf.tpl'))): template_file = os.path.abspath(os.path.join(__file__, '..', '..', '..', 'share', 'templates', 'master.cf.tpl')) if not template_file == None: fp = open(template_file, 'r') template_definition = fp.read() fp.close() t = Template(template_definition, searchList=[postfix_master_settings]) fp = open('/etc/postfix/master.cf', 'w') fp.write(t.__str__()) fp.close() else: log.error(_("Could not write out Postfix configuration file /etc/postfix/master.cf")) return if os.path.isdir('/etc/postfix/sasl/'): fp = open('/etc/postfix/sasl/smtpd.conf', 'w') fp.write("pwcheck_method: saslauthd\n") fp.write("mech_list: plain login\n") fp.close() amavisd_settings = { 'ldap_server': '%(server_host)s', 'ldap_bind_dn': conf.get('ldap', 'service_bind_dn'), 'ldap_bind_pw': conf.get('ldap', 'service_bind_pw'), 'primary_domain': conf.get('kolab', 'primary_domain'), 'ldap_filter': "(|(mail=%m)(alias=%m))", 'ldap_base_dn': conf.get('ldap', 'base_dn'), 'clamdsock': '/var/spool/amavisd/clamd.sock', } template_file = None # On RPM installations, Amavis configuration is contained within a single file. amavisconf_paths = [ "/etc/amavisd.conf", "/etc/amavis/amavisd.conf", "/etc/amavisd/amavisd.conf" ] amavis_conf = '' for amavisconf_path in amavisconf_paths: if os.path.isfile(amavisconf_path): amavis_conf = amavisconf_path break if os.path.isfile(amavis_conf): if os.path.isfile('/etc/kolab/templates/amavisd.conf.tpl'): template_file = '/etc/kolab/templates/amavisd.conf.tpl' elif os.path.isfile('/usr/share/kolab/templates/amavisd.conf.tpl'): template_file = '/usr/share/kolab/templates/amavisd.conf.tpl' elif os.path.isfile(os.path.abspath(os.path.join(__file__, '..', '..', '..', 'share', 'templates', 'amavisd.conf.tpl'))): template_file = os.path.abspath(os.path.join(__file__, '..', '..', '..', 'share', 'templates', 'amavisd.conf.tpl')) if not template_file == None: fp = open(template_file, 'r') template_definition = fp.read() fp.close() if os.path.isfile('/etc/clamd.d/amavisd.conf'): amavisdconf_content = file('/etc/clamd.d/amavisd.conf') for line in amavisdconf_content: if line.startswith('LocalSocket'): amavisd_settings['clamdsock'] = line[len('LocalSocket '):].strip() t = Template(template_definition, searchList=[amavisd_settings]) fp = None fp = open(amavis_conf, 'w') if not fp == None: fp.write(t.__str__()) fp.close() else: log.error(_("Could not write out Amavis configuration file amavisd.conf")) return # On APT installations, /etc/amavis/conf.d/ is a directory with many more files. # # Somebody could work on enhancement request #1080 to configure LDAP lookups, # while really it isn't required. else: log.info(_("Not writing out any configuration for Amavis.")) # On debian wheezy amavisd-new expects '/etc/mailname' - possibly remediable through # the #1080 enhancement mentioned above, but here's a quick fix. f = open('/etc/mailname','w') f.writelines(conf.get('kolab', 'primary_domain')) f.close() if os.path.isfile('/etc/default/spamassassin'): myaugeas = Augeas() setting = os.path.join('/files/etc/default/spamassassin','ENABLED') if not myaugeas.get(setting) == '1': myaugeas.set(setting,'1') myaugeas.save() myaugeas.close() if os.path.isfile('/etc/default/wallace'): myaugeas = Augeas() setting = os.path.join('/files/etc/default/wallace','START') if not myaugeas.get(setting) == 'yes': myaugeas.set(setting,'yes') myaugeas.save() myaugeas.close() if os.path.isfile('/usr/lib/systemd/system/clamd@.service'): from ConfigParser import SafeConfigParser unitfile = SafeConfigParser() unitfile.optionxform = str unitfile.read('/usr/lib/systemd/system/clamd@.service') if not unitfile.has_section('Install'): unitfile.add_section('Install') if not unitfile.has_option('Install', 'WantedBy'): unitfile.set('Install', 'WantedBy', 'multi-user.target') with open('/etc/systemd/system/clamd@.service', 'wb') as f: unitfile.write(f) log.info(_("Configuring and refreshing Anti-Virus...")) if os.path.isfile('/etc/kolab/templates/freshclam.conf.tpl'): shutil.copy( '/etc/kolab/templates/freshclam.conf.tpl', '/etc/freshclam.conf' ) elif os.path.isfile('/usr/share/kolab/templates/freshclam.conf.tpl'): shutil.copy( '/usr/share/kolab/templates/freshclam.conf.tpl', '/etc/freshclam.conf' ) else: log.error(_("Could not find a ClamAV update configuration file")) if os.path.isfile('/etc/freshclam.conf'): subprocess.call([ '/usr/bin/freshclam', '--quiet', '--datadir="/var/lib/clamav"' ]) amavisservice = 'amavisd.service' clamavservice = 'clamd@amavisd.service' if os.path.isfile('/usr/lib/systemd/system/amavis.service'): amavisservice = 'amavis.service' if os.path.isfile('/lib/systemd/system/amavis.service'): amavisservice = 'amavis.service' if os.path.isfile('/etc/init.d/amavis'): amavisservice = 'amavis.service' if os.path.isfile('/usr/lib/systemd/system/clamd.service'): clamavservice = 'clamd.service' if os.path.isfile('/lib/systemd/system/clamd.service'): clamavservice = 'clamd.service' if os.path.isfile('/lib/systemd/system/clamav-daemon.service'): clamavservice = 'clamav-daemon.service' if os.path.isfile('/bin/systemctl'): subprocess.call(['systemctl', 'restart', 'postfix.service']) subprocess.call(['systemctl', 'restart', amavisservice]) subprocess.call(['systemctl', 'restart', clamavservice]) subprocess.call(['systemctl', 'restart', 'wallace.service']) elif os.path.isfile('/sbin/service'): subprocess.call(['service', 'postfix', 'restart']) subprocess.call(['service', 'amavisd', 'restart']) subprocess.call(['service', 'clamd.amavisd', 'restart']) subprocess.call(['service', 'wallace', 'restart']) elif os.path.isfile('/usr/sbin/service'): subprocess.call(['/usr/sbin/service','postfix','restart']) subprocess.call(['/usr/sbin/service','amavis','restart']) subprocess.call(['/usr/sbin/service','clamav-daemon','restart']) subprocess.call(['/usr/sbin/service','wallace','restart']) else: log.error(_("Could not start the postfix, clamav and amavisd services services.")) if os.path.isfile('/bin/systemctl'): subprocess.call(['systemctl', 'enable', 'postfix.service']) subprocess.call(['systemctl', 'enable', amavisservice]) subprocess.call(['systemctl', 'enable', clamavservice]) subprocess.call(['systemctl', 'enable', 'wallace.service']) elif os.path.isfile('/sbin/chkconfig'): subprocess.call(['chkconfig', 'postfix', 'on']) subprocess.call(['chkconfig', 'amavisd', 'on']) subprocess.call(['chkconfig', 'clamd.amavisd', 'on']) subprocess.call(['chkconfig', 'wallace', 'on']) elif os.path.isfile('/usr/sbin/update-rc.d'): subprocess.call(['/usr/sbin/update-rc.d', 'postfix', 'defaults']) subprocess.call(['/usr/sbin/update-rc.d', 'amavis', 'defaults']) subprocess.call(['/usr/sbin/update-rc.d', 'clamav-daemon', 'defaults']) subprocess.call(['/usr/sbin/update-rc.d', 'wallace', 'defaults']) else: log.error(_("Could not configure to start on boot, the " + \ "postfix, clamav and amavisd services.")) diff --git a/pykolab/setup/setup_mysql.py b/pykolab/setup/setup_mysql.py index 08b7b7c..73c2245 100644 --- a/pykolab/setup/setup_mysql.py +++ b/pykolab/setup/setup_mysql.py @@ -1,341 +1,341 @@ # -*- 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 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( _(""" Please supply the root password for MySQL, so we can set up user accounts for other components that use MySQL. """) ) 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( _(""" 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. """) ) 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', 0600) + 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( _(""" Please supply a password for the MySQL user 'kolab'. This password will be used by Kolab services, such as the Web Administration Panel. """) ) 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_roundcube.py b/pykolab/setup/setup_roundcube.py index 47ac8fc..869533f 100644 --- a/pykolab/setup/setup_roundcube.py +++ b/pykolab/setup/setup_roundcube.py @@ -1,373 +1,373 @@ # -*- 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 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( """ Please supply a password for the MySQL user 'roundcube'. This password will be used by the Roundcube webmail interface. """ ) 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( """Please supply the MySQL root password (use 'unix_socket' for socket based authentication)""" ) 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', 0600) + 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 c7c03ce..1cccd0e 100644 --- a/pykolab/setup/setup_syncroton.py +++ b/pykolab/setup/setup_syncroton.py @@ -1,153 +1,153 @@ # -*- 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 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( """Please supply the MySQL root password (use 'unix_socket' for socket based authentication)""" ) 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', 0600) + 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/saslauthd/__init__.py b/saslauthd/__init__.py index 30e8189..e11296b 100644 --- a/saslauthd/__init__.py +++ b/saslauthd/__init__.py @@ -1,378 +1,378 @@ # 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 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") 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") 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, 0777) + 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") % ( conf.process_groupname ) 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") % ( conf.process_username ) 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"))