diff --git a/.arclint b/.arclint
--- a/.arclint
+++ b/.arclint
@@ -19,11 +19,13 @@
"E131": "disabled",
"E201": "disabled",
"E202": "disabled",
+ "E221": "disabled",
"E225": "disabled",
"E231": "disabled",
"E251": "disabled",
"E261": "disabled",
"E265": "disabled",
+ "E266": "disabled",
"E302": "disabled",
"E303": "disabled",
"E402": "disabled",
diff --git a/conf/kolab.conf b/conf/kolab.conf
--- a/conf/kolab.conf
+++ b/conf/kolab.conf
@@ -226,7 +226,7 @@
; The same as for users, but applicable to groups
group_base_dn = ou=Groups,%(base_dn)s
-group_filter = (|(objectclass=groupofuniquenames)(objectclass=groupofurls))
+group_filter = (|(objectclass=%(group_objectclass)s)(objectclass=groupofurls))
group_scope = sub
kolab_group_filter = (|(objectclass=kolabgroupofuniquenames)(objectclass=kolabgroupofurls))
diff --git a/pykolab/auth/__init__.py b/pykolab/auth/__init__.py
--- a/pykolab/auth/__init__.py
+++ b/pykolab/auth/__init__.py
@@ -49,7 +49,6 @@
Login is a simple list of username, password, service and,
optionally, the realm.
"""
-
if len(login) == 3:
# The realm has not been specified. See if we know whether or not
# to use virtual_domains, as this may be a cause for the realm not
@@ -58,12 +57,12 @@
# TODO: Insert debug statements
#if use_virtual_domains == "userid":
- #print "# Derive domain from login[0]"
+ # print "# Derive domain from login[0]"
#elif not use_virtual_domains:
- #print "# Explicitly do not user virtual domains??"
+ # print "# Explicitly do not user virtual domains??"
#else:
- ## Do use virtual domains, derive domain from login[0]
- #print "# Derive domain from login[0]"
+ # ## Do use virtual domains, derive domain from login[0]
+ # print "# Derive domain from login[0]"
if len(login[0].split('@')) > 1:
domain = login[0].split('@')[1]
@@ -86,7 +85,7 @@
back to the primary domain specified by the configuration.
"""
- log.debug(_("Called for domain %r") % (domain), level=8)
+ log.debug(_("Called for domain %r") % (domain), level=5)
if not self._auth == None:
return
@@ -99,14 +98,10 @@
section = 'kolab'
domain = conf.get('kolab', 'primary_domain')
else:
+ log.debug(_("Getting list of domains for %s ...") % (domain), level=5)
self.list_domains(domain)
section = domain
- log.debug(
- _("Using section %s and domain %s") % (section,domain),
- level=8
- )
-
if not self.domains == None and self.domains.has_key(domain):
section = self.domains[domain]
domain = self.domains[domain]
@@ -116,13 +111,6 @@
level=8
)
- log.debug(
- _("Connecting to Authentication backend for domain %s") % (
- domain
- ),
- level=8
- )
-
if not conf.has_section(section):
section = 'kolab'
@@ -142,21 +130,32 @@
level=8
)
+ _auth_mechanism = conf.get(section, 'auth_mechanism')
+
# Get the actual authentication and authorization backend.
- if conf.get(section, 'auth_mechanism') == 'ldap':
- log.debug(_("Starting LDAP..."), level=8)
+ if _auth_mechanism == 'ldap':
+ log.debug(_("Initializing LDAP..."), level=8)
from pykolab.auth import ldap
self._auth = ldap.LDAP(self.domain)
- elif conf.get(section, 'auth_mechanism') == 'sql':
+ elif _auth_mechanism == 'sql':
+ log.debug(_("Initializing SQL..."), level=8)
from pykolab.auth import sql
self._auth = sql.SQL(self.domain)
else:
- log.debug(_("Starting LDAP..."), level=8)
+ log.debug(_("Fallback to LDAP. Initializing ..."), level=5)
from pykolab.auth import ldap
self._auth = ldap.LDAP(self.domain)
+ log.debug(
+ _("Connecting to Authentication %s backend for domain %s") % (
+ _auth_mechanism,
+ domain
+ ),
+ level=5
+ )
+
self._auth.connect()
def disconnect(self, domain=None):
@@ -277,6 +276,8 @@
for secondary in secondaries:
self.domains[secondary.lower()] = primary.lower()
+ log.debug(_("List of domains for %s is: %s") % (domain, ", ".join(self.domains)), level=8)
+
return self.domains
def synchronize(self, mode=0, callback=None):
@@ -291,6 +292,9 @@
def primary_domain_for_naming_context(self, domain):
return self._auth._primary_domain_for_naming_context(domain)
+ def add_entry(self, domain, entry):
+ return self._auth._add_entry(entry)
+
def get_entry_attribute(self, domain, entry, attribute):
return self._auth.get_entry_attribute(entry, attribute)
diff --git a/pykolab/auth/ldap/__init__.py b/pykolab/auth/ldap/__init__.py
--- a/pykolab/auth/ldap/__init__.py
+++ b/pykolab/auth/ldap/__init__.py
@@ -1525,6 +1525,26 @@
log.debug(_("bind_priv() called but already bound"), level=8)
return True
+ def _add_entry(self, entry):
+ """
+ Add generic type of entry.
+
+ Initialy created for setup-kolab to be able to add AD schema
+ by parsing Kolab AD schema ldif file
+
+ entry - dictionary, same as search results
+ """
+ self._bind()
+
+ entry_dn, entry_attrs = entry
+
+ log.debug(_("Entry DN: %s, with attributes: %s") % (entry_dn, entry_attrs), level=8)
+
+ self.ldap.add_s(
+ entry_dn,
+ entry_attrs
+ )
+
def _change_add_group(self, entry, change):
"""
An entry of type group was added.
diff --git a/pykolab/conf/__init__.py b/pykolab/conf/__init__.py
--- a/pykolab/conf/__init__.py
+++ b/pykolab/conf/__init__.py
@@ -223,8 +223,10 @@
log.error(_("Configuration file %s not readable") % config_file)
config = SafeConfigParser()
- log.debug(_("Reading configuration file %s") % config_file, level=8)
+ log.debug(_("Setting default configuration options: %s") % config.sections(), level=8)
+ config.readfp(self.defaults.default_conf())
try:
+ log.debug(_("Reading configuration file %s") % config_file, level=8)
config.read(config_file)
except:
log.error(_("Invalid configuration file %s") % config_file)
@@ -329,7 +331,7 @@
if self.cli_args:
if len(self.cli_args) >= 1:
if hasattr(self,"command_%s" % self.cli_args[0].replace('-','_')):
- exec("self.command_%s(%r)" % (self.cli_args[0].replace('-','_'), self.cli_args[1:]))
+ exec("self.command_%s(%r)" % (self.cli_args[0].replace('-','_'), self.cli_args[1:]))
else:
print >> sys.stderr, _("No command supplied")
@@ -367,7 +369,7 @@
continue
for key in keys:
- print "%s_%s = %s" % (mode, key ,self.cfg_parser.get(mode,key))
+ print "%s_%s = %s" % (mode, key, self.cfg_parser.get(mode,key))
def read_config(self, value=None):
"""
@@ -381,11 +383,12 @@
value = self.cli_keywords.config_file
self.cfg_parser = SafeConfigParser()
+ self.cfg_parser.readfp(self.defaults.default_conf())
self.cfg_parser.read(value)
if hasattr(self, 'cli_keywords') and hasattr(self.cli_keywords, 'config_file'):
self.cli_keywords.config_file = value
- self.defaults.config_file = value
+
self.config_file = value
def command_get(self, *args, **kw):
diff --git a/pykolab/conf/defaults.py b/pykolab/conf/defaults.py
--- a/pykolab/conf/defaults.py
+++ b/pykolab/conf/defaults.py
@@ -18,8 +18,10 @@
#
import logging
+from StringIO import StringIO
class Defaults(object):
+
def __init__(self, plugins=None):
self.loglevel = logging.CRITICAL
@@ -39,4 +41,37 @@
self.kolab_default_locale = 'en_US'
self.ldap_unique_attribute = 'nsuniqueid'
- self.wallace_resource_calendar_expire_days = 100
\ No newline at end of file
+ # LDAPS connection certificate verification
+ self.ldap_verify_certs = 'never'
+
+ # Static group object class, because ActiveDirectory uses objectClass=group
+ self.ldap_group_objectclass = 'groupOfUniqueNames'
+ self.ldap_group_member_attr = 'uniqueMember'
+
+ self.wallace_resource_calendar_expire_days = 100
+
+ def default_conf(self):
+ default_config = """
+[cyrus]
+annotations_retry_interval = %(cyrus_annotations_retry_interval)s
+
+[ldap]
+group_objectclass = %(ldap_group_objectclass)s
+group_member_attr = %(ldap_group_member_attr)s
+verify_certs = %(ldap_verify_certs)s
+unique_attribute = %(ldap_unique_attribute)s
+mail_attributes = %(mail_attributes)s
+mailserver_attribute = %(mailserver_attribute)s
+
+[kolab]
+default_locale = %(kolab_default_locale)s
+domain_sync_interval = %(kolab_domain_sync_interval)s
+
+[kolab_smtp_access_policy]
+address_search_attrs = %(address_search_attrs)s
+
+[wallace]
+resource_calendar_expire_days = %(wallace_resource_calendar_expire_days)s
+""" % self.__dict__
+
+ return StringIO(default_config)
diff --git a/pykolab/constants.py.in b/pykolab/constants.py.in
--- a/pykolab/constants.py.in
+++ b/pykolab/constants.py.in
@@ -20,6 +20,7 @@
import math
import socket
import sys
+import os
from pykolab.translate import _
@@ -88,6 +89,12 @@
'one': ldap.SCOPE_ONELEVEL
}
+LDAP_VERIFY_CERTS = {
+ 'allow': ldap.OPT_X_TLS_ALLOW,
+ 'demand': ldap.OPT_X_TLS_DEMAND,
+ 'never': ldap.OPT_X_TLS_NEVER
+ }
+
SUPPORTED_LDAP_CONTROLS = {
0: {
'desc': 'Persistent Search Control',
@@ -118,3 +125,17 @@
# 'func': '_sync_repl'
# }
# }
+
+
+# Binay attributes which should not be stripped
+BINARY_ATTRS = (
+ 'objectguid',
+ 'objectsid'
+ )
+
+TEMPLATE_LOCATIONS = (
+ '/etc/kolab/templates/',
+ '/usr/share/kolab/templates/',
+ '/usr/share/doc/pykolab/templates',
+ os.path.abspath(os.path.join(__file__, '..', '..', '..', 'share', 'templates'))
+ )
diff --git a/pykolab/setup/setup_ldap.py b/pykolab/setup/setup_ldap.py
--- a/pykolab/setup/setup_ldap.py
+++ b/pykolab/setup/setup_ldap.py
@@ -17,11 +17,15 @@
# along with this program. If not, see .
#
+import base64
import ldap
import ldap.modlist
+import ldap.dn
+import ldif
import os
import pwd
import shutil
+import socket
import subprocess
import tempfile
import time
@@ -35,6 +39,9 @@
from pykolab.constants import *
from pykolab.translate import _
+from StringIO import StringIO
+from jinja2 import Template
+
log = pykolab.getLogger('pykolab.setup')
conf = pykolab.getConf()
@@ -95,20 +102,468 @@
def description():
return _("Setup LDAP.")
+def get_answers():
+
+ answers = {}
+
+ if conf.fqdn:
+ answers['fqdn'] = conf.fqdn
+ answers['hostname'] = conf.fqdn.split('.')[0]
+ answers['domain'] = '.'.join(conf.fqdn.split('.')[1:])
+ else:
+ answers['fqdn'] = fqdn
+ answers['hostname'] = hostname.split('.')[0]
+ answers['domain'] = domainname
+
+ print >> sys.stderr, 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.
+ """)
+ )
+
+ answers['domain'] = utils.ask_question(
+ _("Domain name to use"),
+ default = answers['domain']
+ )
+
+ answers['nodotdomain'] = answers['domain'].replace('.','_')
+
+ print >> sys.stderr, utils.multiline_message(
+ _("""
+ The standard root dn we composed for you follows. Please
+ confirm this is the root dn you wish to use.
+ """)
+ )
+
+ answers['base_dn'] = utils.ask_question(
+ _("LDAP base DN"),
+ default = utils.standard_root_dn(answers['domain'])
+ )
+
+ if conf.with_ad:
+ print >> sys.stderr, utils.multiline_message(
+ _("""
+ Please supply Active Directory Administrator bind DN.
+ This will be used to bind to Active Directory LDAP to
+ perform various administrative tasks. This user should
+ have necessary privileges in Active Directory already.
+ """)
+ )
+
+ answers['bind_dn'] = utils.ask_question(
+ _("AD Administrator bind DN"),
+ default="CN=Administrator,CN=Users,%s" % answers['base_dn']
+ )
+ elif not conf.with_ad and not conf.with_openldap:
+ # Assuming Dirsrv
+ answers['bind_dn'] = "cn=Directory Manager"
+ log.info(_("Default administrative bind dn for Directory Server: %s") % answers['bind_dn'])
+
+ # Get cn=Directory Manger password
+ if conf.directory_manager_pwd is not None:
+ # password was set with command line options
+ answers['bind_pw'] = conf.directory_manager_pwd
+ else:
+ # Ask for password
+ if conf.with_ad:
+ print >> sys.stderr, utils.multiline_message(
+ _("""
+ Please supply a password for the Active Directory Administrator user.
+ """)
+ )
+ _short_text = _("Active Directory administrator password")
+ else:
+ print >> sys.stderr, 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.
+ """)
+ )
+ _short_text = _("Directory Manager password")
+
+ answers['bind_pw'] = utils.ask_question(
+ _short_text,
+ default=utils.generate_password(),
+ password=True,
+ confirm=True
+ )
+
+ # Ask only if LDAP is DS
+ if not conf.with_openldap and not conf.with_ad:
+ # Get Dirsrv Admin server admin user pass
+ print >> sys.stderr, utils.multiline_message(
+ _("""
+ Please supply a password for the LDAP administrator user
+ 'admin', used to login to the graphical console of 389
+ Directory server.
+ """)
+ )
+
+ answers['admin_pass'] = utils.ask_question(
+ _("Administrator password"),
+ default=utils.generate_password(),
+ password=True,
+ confirm=True
+ )
+
+
+ # Set users for DS LDAP
+ print >> sys.stderr, 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.
+ """)
+ )
+
+ try:
+ pw = pwd.getpwnam("dirsrv")
+ except:
+ answers['userid'] = utils.ask_question(_("User"), default="nobody")
+ answers['group'] = utils.ask_question(_("Group"), default="nobody")
+ else:
+ answers['userid'] = utils.ask_question(_("User"), default="dirsrv")
+ answers['group'] = utils.ask_question(_("Group"), default="dirsrv")
+
+ # Ask if Active Directory
+ elif conf.with_ad and not conf.with_openldap:
+ # Get answers for Active Directory setup
+ print >> sys.stderr, utils.multiline_message(
+ _("""
+ Specify basic Active Directory connection information.
+ """)
+ )
+
+ print >> sys.stderr, utils.multiline_message(
+ _("""
+ Specify LDAP tree DN for Kolab related data. Kolab specific
+ LDAP records such as Kolab domain information, special users
+ for Kolab, shared folders and resources LDAP information.
+ """)
+ )
+
+ answers['kolab_tree_dn'] = utils.ask_question(
+ _("AD LDAP tree for Kolab:"),
+ default="OU=Kolab,%s" % answers['base_dn']
+ )
+
+ answers['specialuser_base_dn'] = "OU=Special Users,%s" % answers['kolab_tree_dn']
+ answers['cyrus_admin_dn'] = "CN=Cyrus Admin,%s" % answers['specialuser_base_dn']
+ answers['service_bind_dn'] = "CN=Kolab Service,%s" % answers['specialuser_base_dn']
+ answers['domain_base_dn'] = "OU=Domains,%s" % answers['kolab_tree_dn']
+ answers['group_base_dn'] = "OU=Groups,%s" % answers['kolab_tree_dn']
+ answers['resource_base_dn'] = "OU=Resources,%s" % answers['kolab_tree_dn']
+ answers['sharedfolder_base_dn'] = "OU=Shared Folders,%s" % answers['kolab_tree_dn']
+ answers['user_base_dn'] = "CN=Users,%s" % answers['base_dn']
+ answers['kolab_user_base_dn'] = answers['user_base_dn']
+
+ print >> sys.stderr, utils.multiline_message(
+ _("""
+ Please provide Active Directory server URI. Setup will use it to load
+ Kolab schema into AD and then will configure Kolab to run with AD as
+ LDAP server. Schema can be loaded to schema master server only wherefore
+ this needs to be URI specifying AD schema master. You can change AD server
+ URI later, in kolab.conf file.
+ """)
+ )
+
+ #answers['ldap_uri'] = utils.ask_question(
+ # _("AD server URI"),
+ # default="ldap://adserver.domain:389"
+ # )
+ answers['ldap_uri'] = utils.ask_question(
+ _("AD server URI"),
+ default="ldaps://w2k16-1.ad-lab.loc:636"
+ )
+
+ print >> sys.stderr, 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.
+ """)
+ )
+
+ answers['cyrus_admin_password'] = utils.ask_question(
+ _("Cyrus Administrator password"),
+ default=utils.generate_password(),
+ password=True,
+ confirm=True
+ )
+
+ print >> sys.stderr, 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.
+ """)
+ )
+
+ answers['service_bind_pw'] = utils.ask_question(
+ _("Kolab Service password"),
+ default=utils.generate_password(),
+ password=True,
+ confirm=True
+ )
+
+ return answers
+
+def ad_schema_import(*args, **kwargs):
+ """
+ Function to load Kolab schema to Active Directory
+ Does checks if ldap_uri points to Schema master
+ """
+ conf.ldap_uri = kwargs['ldap_uri']
+
+ # Create LDAP connection
+ try:
+ auth = Auth(kwargs['domain'])
+ auth.connect(immediate=True)
+ auth._auth.connect()
+ auth._auth._bind(kwargs['bind_dn'], kwargs['bind_pw'])
+
+ ad_schema_naming_context = auth._auth.get_entry_attribute("", "schemaNamingContext")
+
+ log.debug("AD Schema Naming context is %s" % ad_schema_naming_context, level=8)
+
+ # Find out AD schema master server. Only this server has writable copy of
+ # AD schema. First need to get fsmoRoleOwner attribute value.
+ ad_fsmo_attribute = auth._auth.get_entry_attribute(ad_schema_naming_context, "fSMORoleOwner")
+
+ log.debug(_("AD FSMO Role Owner entry is: %s") % ad_fsmo_attribute, level=8)
+ except Exception as errmsg:
+ log.error(_("Could not connect to LDAP. Error: %s") % errmsg)
+ sys.exit(1)
+
+ # Now get parent of fsmoRoleOwner
+ if ad_fsmo_attribute is not None:
+ ad_fsmo_dn = ldap.dn.explode_dn(ad_fsmo_attribute)
+ ad_fsmo_parent = ','.join(ad_fsmo_dn[1:])
+
+ log.debug(_("AD FSMO entry parent is: %s") % ad_fsmo_parent, level=8)
+
+ # Look for dNSHostName attribute of the parent
+ if ad_fsmo_parent is not None:
+ ad_schema_master = auth._auth.get_entry_attribute(ad_fsmo_parent, "dNSHostName")
+ log.debug(_("AD schema master server is: %s") % ad_schema_master, level=8)
+
+ (
+ _ad_protocol,
+ _ad_server,
+ _ad_port,
+ _ad_base_dn,
+ _ad_attr,
+ _ad_scope,
+ _ad_filter
+ ) = utils.parse_ldap_uri(conf.ldap_uri)
+
+ if not _ad_server == ad_schema_master:
+ log.debug(_("AD schema master %s is not the same as %s.") % (ad_schema_master, _ad_server), level=5)
+ log.error(_("Cannot install Kolab schema on AD server which is not schema master"))
+ sys.exit(1)
+
+ template_file = utils.find_template(file_name='kolab3-ad-schema.ldif.j2')
+
+ if not template_file == None:
+ log.debug(_("Using template file %r") % (template_file), level=8)
+ fp = open(template_file, 'r')
+ template_definition = fp.read()
+ fp.close()
+
+ schema_template = Template(template_definition)
+
+ log.debug(
+ _("Successfully compiled template %r, ready to add schema to AD server") % (template_file),
+ level=8
+ )
+
+ schema_ldif = StringIO(
+ schema_template.render(ad_schema_naming_context=ad_schema_naming_context).__str__()
+ )
+
+ parser = ldif.LDIFRecordList(schema_ldif)
+ parser.parse()
+
+ # If silent install or install without asking questions
+ # when no need to ask confirmation
+ if kwargs['confirm']:
+
+ print >> sys.stderr, utils.multiline_message(
+ _("""
+ Setup is going to add Kolab schema elements to
+ Active Directory. This is ireversable operation.
+ """)
+ )
+
+ confirmed = utils.ask_confirmation(
+ _("Modify Active Directory schema?"),
+ default="y"
+ )
+
+ else:
+ # this is silent install
+ confirmed = True
+
+ if confirmed:
+ for dn, attributes in parser.all_records:
+ log.debug("Adding DN: %s" % dn, level=5)
+ try:
+ add_attributes = ldap.modlist.addModlist(attributes)
+ auth._auth.ldap.add_s(dn, add_attributes)
+ except ldap.ALREADY_EXISTS:
+ log.warning(_("Schema element already exist: %s") % dn)
+ continue
+ except ldap.NO_SUCH_ATTRIBUTE:
+ log.warning(_("Could not add %s schema element. Force update and retry.") % dn)
+ try:
+ auth._auth.set_entry_attribute("", "schemaUpdateNow", "1")
+ time.sleep(2)
+ auth._auth.ldap.add_s(dn, add_attributes)
+ except:
+ log.error(_("Failed to add %s Kolab schema element.") % dn)
+ sys.exit(1)
+ except Exception, errorMsg:
+ log.error(_("Could not add schema element: %s") % dn)
+ log.error(_("Error: %s") % errorMsg )
+ sys.exit(1)
+ else:
+ log.error(_("Kolab schema must be installed before you continue."))
+ sys.exit(1)
+
+def ldap_data_load(*args, **kwargs):
+ """
+ Function to setup LDAP tree
+ """
+ conf.ldap_uri = kwargs['ldap_uri']
+
+ # Create LDAP connection
+ auth = Auth(kwargs['domain'])
+ auth.connect()
+ auth._auth.connect()
+ auth._auth._bind(kwargs['bind_dn'], kwargs['bind_pw'])
+
+ (
+ _protocol,
+ _server,
+ _port,
+ _base_dn,
+ _attr,
+ _scope,
+ _filter
+ ) = utils.parse_ldap_uri(conf.ldap_uri)
+
+ ad_ldaps = False
+ if _protocol == 'ldap' and conf.with_ad:
+ log.info(_("Active Directory passwords cannot be set over %s protocol.") % (_protocol))
+ elif _protocol == "ldaps" and conf.with_ad:
+ ad_ldaps = True
+
+ if not kwargs['template'] == None:
+ log.debug(_("Using template file %r") % (kwargs['template']), level=8)
+ fp = open(kwargs['template'], 'r')
+ template_definition = fp.read()
+ fp.close()
+
+ _template_vars = kwargs
+ _template_vars['ad_ldaps'] = ad_ldaps
+ _template_vars['cyrus_admin_password_base64'] = base64.b64encode(
+ unicode("\"" + kwargs['cyrus_admin_password'] + "\"", "iso-8859-1").encode('utf-16-le')
+ )
+ _template_vars['service_bind_pw_base64'] = base64.b64encode(
+ unicode("\"" + kwargs['service_bind_pw'] + "\"", "iso-8859-1").encode('utf-16-le')
+ )
+
+ compiled_template = Template(template_definition)
+
+ log.debug(_("Successfully compiled template %r") % (kwargs['template']), level=1)
+
+ rendered_template = compiled_template.render(**_template_vars).__str__()
+
+ log.debug(_("Importing LDIF %s") % rendered_template, level=8)
+
+ template_ldif = StringIO(rendered_template)
+
+ parser = ldif.LDIFRecordList(template_ldif)
+ parser.parse()
+
+ for dn, attributes in parser.all_records:
+ log.debug("Adding DN: %s" % dn, level=5)
+ try:
+ add_attributes = ldap.modlist.addModlist(attributes)
+ # Do the actual synchronous add-operation to the ldapserver
+ auth._auth.ldap.add_s(dn, add_attributes)
+ except ldap.ALREADY_EXISTS:
+ log.warning(_("Object already exist: %s") % dn)
+ continue
+ except Exception, errorMsg:
+ log.error(_("Could not add object: %s") % dn)
+ log.error(_("Error: %s") % errorMsg )
+ sys.exit(1)
+
+def ldap_get_supported_controls(*args, **kwargs):
+ """
+ Function to find which controls are supported by given LDAP server
+ and pykolab. Return the list as a string.
+ """
+ auth = Auth(kwargs['domain'])
+ auth.connect(immediate=True)
+ auth._auth.connect()
+ auth._auth._bind(kwargs['bind_dn'], kwargs['bind_pw'])
+
+ _ldap_controls = auth._auth.get_entry_attribute("", "supportedControl")
+ supported_controls = []
+ for _ldap_control in _ldap_controls:
+ for _control_id, _control_def in SUPPORTED_LDAP_CONTROLS.iteritems():
+ if _control_def['oid'] == _ldap_control:
+ log.debug(_("Found control supported by pykolab: Id:%s Def: %s") % (_control_id, _control_def), level=8)
+ supported_controls.append(_control_id)
+
+ # Return supported controls as a string
+ return ','.join(map(str, supported_controls))
+
def execute(*args, **kw):
ask_questions = True
+ _input = {}
+
if not conf.config_file == conf.defaults.config_file:
+ log.debug(_("Using supplied config file to answer all questions"), level=5)
ask_questions = False
+ if conf.fqdn is not None:
+ log.debug(_("FQDN specified. Setting other dependend values"), level=8)
+ _input['fqdn'] = conf.fqdn
+ _input['hostname'] = conf.fqdn.split('.')[0]
+ _input['domain'] = '.'.join(conf.fqdn.split('.')[1:])
+ else:
+ log.debug(_("FQDN not specified. Derriving from defalts"), level=8)
+ _input['fqdn'] = fqdn
+ _input['hostname'] = hostname.split('.')[0]
+
+ if conf.get('kolab', 'primary_domain') is not None:
+ _input['domain'] = conf.get('kolab', 'primary_domain')
+ else:
+ _input['domain'] = domainname
+
+ if conf.get('ldap', 'base_dn') is not None:
+ _input['base_dn'] = conf.get('ldap', 'base_dn')
+ else:
+ _input['base_dn'] = utils.standard_root_dn(_input['domain'])
if conf.without_ldap:
print >> sys.stderr, _("Skipping setup of LDAP, as specified")
return
- _input = {}
-
if conf.with_openldap and not conf.with_ad:
-
+ conf.command_set('ldap', 'type', 'openldap')
conf.command_set('ldap', 'unique_attribute', 'entryuuid')
fp = open(conf.defaults.config_file, "w+")
@@ -118,14 +573,70 @@
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')
+
+ if ask_questions:
+ _input = get_answers()
+ else:
+ # Get all the information from config file supplied as CLI -c param
+ _input['ldap_uri'] = conf.get('ldap', 'ldap_uri')
+ _input['bind_dn'] = conf.get('ldap', 'bind_dn')
+ _input['bind_pw'] = conf.get('ldap', 'bind_pw')
+ _input['service_bind_dn'] = conf.get('ldap', 'service_bind_dn')
+ _input['service_bind_pw'] = conf.get('ldap', 'service_bind_pw')
+ _input['user_base_dn'] = conf.get('ldap', 'user_base_dn')
+ _input['kolab_user_base_dn'] = conf.get('ldap', 'kolab_user_base_dn')
+ _input['group_base_dn'] = conf.get('ldap', 'group_base_dn')
+ _input['sharedfolder_base_dn'] = conf.get('ldap', 'sharedfolder_base_dn')
+ _input['resource_base_dn'] = conf.get('ldap', 'resource_base_dn')
+ _input['domain_base_dn'] = conf.get('ldap', 'domain_base_dn')
+ _input['cyrus_admin_password'] = conf.get('cyrus-imap', 'admin_password')
+
+ # Things which are specific to AD and are not in config file
+ _input['kolab_tree_dn'] = "OU=Kolab,%s" % _input['base_dn']
+ _input['specialuser_base_dn'] = "OU=Special Users,%s" % _input['kolab_tree_dn']
+ _input['cyrus_admin_dn'] = "CN=Cyrus Admin,%s" % _input['specialuser_base_dn']
+
+ conf.command_set('ldap', 'type', 'ad')
+ conf.command_set('ldap', 'auth_attributes', 'mail, kolabAlias, uid, samaccountname')
+ conf.command_set('ldap', 'mail_attributes', 'mail, kolabAlias')
+ conf.command_set('ldap', 'modifytimestamp_format', '%Y%m%d%H%M%S.0Z')
+ conf.command_set('ldap', 'unique_attribute', 'objectguid')
+ conf.command_set('ldap', 'sharedfolder_acl_entry_attribute', 'kolabImapACL')
+ conf.command_set('ldap', 'mailserver_attribute', 'kolabMailhost')
+ conf.command_set('ldap', 'quota_attribute', 'kolabMailquota')
+ conf.command_set('ldap', 'group_objectclass', 'group')
+ conf.command_set('ldap', 'group_member_attr', 'member')
# 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')
+ conf.command_set('ldap', 'ldap_uri', _input['ldap_uri'])
+ conf.command_set('ldap', 'supported_controls', ldap_get_supported_controls(**_input))
+ conf.command_set('ldap', 'base_dn', _input['base_dn'])
+ conf.command_set('ldap', 'bind_dn', _input['bind_dn'])
+ conf.command_set('ldap', 'bind_pw', _input['bind_pw'])
+ conf.command_set('ldap', 'service_bind_dn', _input['service_bind_dn'])
+ conf.command_set('ldap', 'service_bind_pw', _input['service_bind_pw'])
+ conf.command_set('ldap', 'user_base_dn', _input['user_base_dn'])
+ conf.command_set('ldap', 'kolab_user_base_dn', _input['kolab_user_base_dn'])
+ conf.command_set('ldap', 'group_base_dn', _input['group_base_dn'])
+ conf.command_set('ldap', 'sharedfolder_base_dn', _input['sharedfolder_base_dn'])
+ conf.command_set('ldap', 'resource_base_dn', _input['resource_base_dn'])
+ conf.command_set('ldap', 'domain_base_dn', _input['domain_base_dn'])
+
+ conf.command_set('kolab', 'primary_domain', _input['domain'])
+ conf.command_set('cyrus-imap', 'admin_password', _input['cyrus_admin_password'])
+
+ _input['confirm'] = ask_questions
+
+ ad_schema_import(**_input)
+
+ templates = ['kolab-ad-tree.ldif.j2', 'kolab-ad-special-users.ldif.j2', 'kolab-ad-domain.ldif.j2']
+ for template in templates:
+ _input['template'] = utils.find_template(file_name=template)
+
+ if _input['template'] is not None:
+ ldap_data_load(**_input)
+ else:
+ continue
return
@@ -139,6 +650,7 @@
sys.exit(1)
+ # This is a point where setup starts cofigure local Directory Server
# Pre-execution checks
for path, directories, files in os.walk('/etc/dirsrv/'):
for direct in directories:
@@ -155,63 +667,11 @@
sys.exit(1)
- _input = {}
-
if ask_questions:
- print >> sys.stderr, utils.multiline_message(
- _("""
- Please supply a password for the LDAP administrator user
- 'admin', used to login to the graphical console of 389
- Directory server.
- """)
- )
-
- _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(
- _("""
- 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.
- """)
- )
-
- _input['dirmgr_pass'] = utils.ask_question(
- _("Directory Manager password"),
- default=utils.generate_password(),
- password=True,
- confirm=True
- )
-
- print >> sys.stderr, 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.
- """)
- )
-
- 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")
-
+ _input = get_answers()
else:
_input['admin_pass'] = conf.get('ldap', 'bind_pw')
- _input['dirmgr_pass'] = conf.get('ldap', 'bind_pw')
+ _input['bind_pw'] = conf.get('ldap', 'bind_pw')
try:
pw = pwd.getpwnam("dirsrv")
except:
@@ -228,101 +688,33 @@
#
# 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'])
+ _input['ldap_uri'] = 'ldap://localhost:389/'
- if ask_questions:
- print >> sys.stderr, 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.
- """)
- )
+ # TODO: Loudly complain if the fqdn does not resolve back to this system.
- 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(
- _("""
- Invalid input. Please try again.
- """)
- )
+ template_file = utils.find_template(file_name='kolab-ds-setup.inf.j2')
- _input['nodotdomain'] = _input['domain'].replace('.','_')
- _input['rootdn'] = utils.standard_root_dn(_input['domain'])
+ if template_file is not None:
+ log.debug(_("Setup Directory server using %s template") % (template_file), level=8)
- print >> sys.stderr, utils.multiline_message(
- _("""
- The standard root dn we composed for you follows. Please
- confirm this is the root dn you wish to use.
- """)
- )
+ fpr = open(template_file, 'r')
+ template_definition = fpr.read()
+ fpr.close()
- 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(
- _("""
- Invalid input. Please try again.
- """)
- )
+ setup_inf_template = Template(template_definition)
- # TODO: Loudly complain if the fqdn does not resolve back to this system.
+ log.debug(
+ _("Successfully compiled template %r, ready to add schema to AD server") % (template_file),
+ level=8
+ )
- 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)
+ data = setup_inf_template.render(**_input).__str__()
+
+ (fpw, filename) = tempfile.mkstemp(dir="/tmp/")
+ os.write(fpw, data)
+ os.close(fpw)
if os.path.isfile("/usr/sbin/setup-ds-admin.pl"):
setup_ds_admin = "/usr/sbin/setup-ds-admin.pl"
@@ -438,53 +830,20 @@
log.error(_("Could not configure to start on boot, the " + \
"directory server service."))
- if ask_questions:
- print >> sys.stderr, 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.
- """)
- )
-
- _input['cyrus_admin_pass'] = utils.ask_question(
- _("Cyrus Administrator password"),
- default=utils.generate_password(),
- password=True,
- confirm=True
- )
-
- print >> sys.stderr, 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.
- """)
- )
-
- _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')
+ if not ask_questions:
+ _input['cyrus_admin_password'] = conf.get('cyrus-imap', 'admin_password')
+ _input['service_bind_pw'] = 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', 'ldap_uri', _input['ldap_uri'])
+ conf.command_set('ldap', 'base_dn', _input['base_dn'])
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'])
+ conf.command_set('ldap', 'bind_pw', _input['bind_pw'])
+ conf.command_set('ldap', 'service_bind_dn', 'uid=kolab-service,ou=Special Users,%s' % (_input['base_dn']))
+ conf.command_set('ldap', 'service_bind_pw', _input['service_bind_pw'])
fp = open(conf.defaults.config_file, "w+")
conf.cfg_parser.write(fp)
@@ -496,9 +855,9 @@
auth = Auth(_input['domain'])
auth.connect()
auth._auth.connect()
- auth._auth._bind(bind_dn='cn=Directory Manager', bind_pw=_input['dirmgr_pass'])
+ auth._auth._bind(bind_dn='cn=Directory Manager', bind_pw=_input['bind_pw'])
- dn = 'uid=%s,ou=Special Users,%s' % (conf.get('cyrus-imap', 'admin_login'), _input['rootdn'])
+ dn = 'uid=%s,ou=Special Users,%s' % (conf.get('cyrus-imap', 'admin_login'), _input['base_dn'])
# A dict to help build the "body" of the object
attrs = {}
@@ -507,7 +866,7 @@
attrs['givenname'] = "Cyrus"
attrs['surname'] = "Administrator"
attrs['cn'] = "Cyrus Administrator"
- attrs['userPassword'] = _input['cyrus_admin_pass']
+ attrs['userPassword'] = _input['cyrus_admin_password']
# Convert our dict to nice syntax for the add-function using modlist-module
ldif = ldap.modlist.addModlist(attrs)
@@ -515,9 +874,9 @@
# 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'])
+ conf.command_set('cyrus-imap', 'admin_password', _input['cyrus_admin_password'])
- dn = 'uid=kolab-service,ou=Special Users,%s' % (_input['rootdn'])
+ dn = 'uid=kolab-service,ou=Special Users,%s' % (_input['base_dn'])
# A dict to help build the "body" of the object
attrs = {}
@@ -526,7 +885,7 @@
attrs['givenname'] = "Kolab"
attrs['surname'] = "Service"
attrs['cn'] = "Kolab Service"
- attrs['userPassword'] = _input['kolab_service_pass']
+ attrs['userPassword'] = _input['service_bind_pw']
attrs['nslookthroughlimit'] = '-1'
attrs['nssizelimit'] = '-1'
attrs['nstimelimit'] = '-1'
@@ -538,7 +897,7 @@
# Do the actual synchronous add-operation to the ldapserver
auth._auth.ldap.add_s(dn, ldif)
- dn = 'ou=Resources,%s' % (_input['rootdn'])
+ dn = 'ou=Resources,%s' % (_input['base_dn'])
# A dict to help build the "body" of the object
attrs = {}
@@ -551,7 +910,7 @@
# Do the actual synchronous add-operation to the ldapserver
auth._auth.ldap.add_s(dn, ldif)
- dn = 'ou=Shared Folders,%s' % (_input['rootdn'])
+ dn = 'ou=Shared Folders,%s' % (_input['base_dn'])
# A dict to help build the "body" of the object
attrs = {}
@@ -572,7 +931,7 @@
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'])
+ attrs['aci'] = '(targetattr = "*") (version 3.0;acl "Kolab Services";allow (read,compare,search)(userdn = "ldap:///uid=kolab-service,ou=Special Users,%s");)' % (_input['base_dn'])
# Convert our dict to nice syntax for the add-function using modlist-module
ldif = ldap.modlist.addModlist(attrs)
@@ -596,13 +955,13 @@
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)
+ attrs['aci'] = '(targetattr = "*") (version 3.0;acl "Read Access for %(domain)s Users";allow (read,compare,search)(userdn = "ldap:///%(base_dn)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']):
+ if not _input['base_dn'] == utils.standard_root_dn(_input['domain']):
attrs['objectclass'].append('inetdomain')
- attrs['inetdomainbasedn'] = _input['rootdn']
+ attrs['inetdomainbasedn'] = _input['base_dn']
ldif = ldap.modlist.addModlist(attrs)
auth._auth.ldap.add_s(dn, ldif)
@@ -644,7 +1003,7 @@
# Add kolab-admin role
log.info(_("Adding the kolab-admin role"))
- dn = "cn=kolab-admin,%s" % (_input['rootdn'])
+ dn = "cn=kolab-admin,%s" % (_input['base_dn'])
attrs = {}
attrs['description'] = "Kolab Administrator"
attrs['objectClass'] = ['top','ldapsubentry','nsroledefinition','nssimpleroledefinition','nsmanagedroledefinition']
@@ -654,8 +1013,8 @@
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']
+ log.info(_("Setting access control to %s") % (_input['base_dn']))
+ dn = _input['base_dn']
aci = []
if schema_error:
@@ -663,7 +1022,7 @@
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 "Directory Administrators Group";allow (all)(groupdn = "ldap:///cn=Directory Administrators,%(base_dn)s" or roledn = "ldap:///cn=kolab-admin,%(base_dn)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))
diff --git a/pykolab/setup/setup_mta.py b/pykolab/setup/setup_mta.py
--- a/pykolab/setup/setup_mta.py
+++ b/pykolab/setup/setup_mta.py
@@ -18,7 +18,7 @@
#
from augeas import Augeas
-from Cheetah.Template import Template
+from jinja2 import Template
import os
import shutil
import subprocess
@@ -42,206 +42,38 @@
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'),
- },
- }
+ postfix_ldap_dir = '/etc/postfix/ldap'
+ if not os.path.isdir(postfix_ldap_dir):
+ os.mkdir(postfix_ldap_dir, 0770)
- if not os.path.isdir('/etc/postfix/ldap'):
- os.mkdir('/etc/postfix/ldap/', 0770)
+ # Get ldap config section into dictionary with interpolation
+ config_ldap_dict = {
+ 'ldap': dict(conf.cfg_parser.items('ldap', raw=False))
+ }
- for filename in files.keys():
- fp = open(filename, 'w')
- fp.write(files[filename])
- fp.close()
+ postfix_lookup_tables = (
+ 'local_recipient_maps.cf',
+ 'mydestination.cf',
+ 'mailenabled_distgroups.cf',
+ 'mailenabled_dynamic_distgroups.cf',
+ 'transport_maps.cf',
+ 'virtual_alias_maps.cf',
+ 'virtual_alias_maps_mailforwarding.cf',
+ 'virtual_alias_maps_sharedfolders.cf'
+ )
+
+ for lookup_table in postfix_lookup_tables:
+ try:
+ with open(utils.find_template("%s.j2" % lookup_table)) as _template_def:
+ compiled_template = Template(_template_def.read())
+
+ with open(os.path.join(postfix_ldap_dir, lookup_table), 'w') as _template_dest:
+ _template_dest.write(compiled_template.render(**config_ldap_dict))
+
+ log.debug(_("Saved Postfix LDAP lookup table: %s") % _template_dest.name, level=8)
+ except Exception, errmsg:
+ log.error(_("Failed to save Postfix LDAP lookup table: %s") % errmsg)
+ continue
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')})
@@ -327,27 +159,19 @@
else:
postfix_master_settings['kolab_sap_executable_path'] = '/usr/libexec/postfix/kolab_smtp_access_policy'
- template_file = None
+ try:
+ with open(utils.find_template('master.cf.j2')) as postfix_master_template:
+ compiled_template = Template(postfix_master_template.read())
- 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'))
+ with open('/etc/postfix/master.cf', 'w') as postfix_master_file:
+ postfix_master_file.write(compiled_template.render(**postfix_master_settings))
- 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"))
+ log.debug(_("Saved Postfix /etc/postfix/master.cf file"), level=8)
+ except Exception, errmsg:
+ log.error(
+ _("Could not write out Postfix configuration file /etc/postfix/master.cf: %s") %
+ errmsg
+ )
return
if os.path.isdir('/etc/postfix/sasl/'):
@@ -356,18 +180,18 @@
fp.write("mech_list: plain login\n")
fp.close()
+ # Get raw config values for kolab section. Don't need to interpolate
+ config_kolab_dict = {
+ 'kolab': dict(conf.cfg_parser.items('kolab', raw=True))
+ }
+
+ # Merge ldap and kolab sections into one dictionalry
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',
+ 'kolab': config_kolab_dict['kolab'],
+ 'ldap': config_ldap_dict['ldap']
}
- template_file = None
-
# On RPM installations, Amavis configuration is contained within a single file.
amavisconf_paths = [
"/etc/amavisd.conf",
@@ -379,38 +203,24 @@
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])
+ if line.startswith('LocalSocket'):
+ amavisd_settings['clamdsock'] = line[len('LocalSocket '):].strip()
+ break
- fp = None
- fp = open(amavis_conf, 'w')
+ if os.path.isfile(amavis_conf):
+ try:
+ with open(utils.find_template("%s.j2" % os.path.basename(amavis_conf))) as amavis_conf_template:
+ compiled_template = Template(amavis_conf_template.read())
- if not fp == None:
- fp.write(t.__str__())
- fp.close()
+ with open(amavis_conf, 'w') as amavis_conf_file:
+ amavis_conf_file.write(compiled_template.render(**amavisd_settings))
- else:
- log.error(_("Could not write out Amavis configuration file amavisd.conf"))
+ log.debug(_("Saved Amavisd configuration file %s") % (amavis_conf), level=8)
+ except Exception, errmsg:
+ log.error(_("Could not write out Amavisd configuration file %s: %s") % (amavis_conf, errmsg))
return
# On APT installations, /etc/amavis/conf.d/ is a directory with many more files.
@@ -475,7 +285,7 @@
subprocess.call([
'/usr/bin/freshclam',
'--quiet',
- '--datadir="/var/lib/clamav"'
+ '--datadir=/var/lib/clamav'
])
amavisservice = 'amavisd.service'
@@ -487,9 +297,6 @@
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'
@@ -500,38 +307,36 @@
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'])
+ # Reload systemctl daemon in case any service file changed
+ subprocess.call(['systemctl', 'daemon-reload'])
+ for _srv in ('postfix.service', amavisservice, clamavservice, 'wallace.service'):
+ log.debug(_("Restarting %s systemd service ... ") % _srv, level=8)
+ subprocess.call(['systemctl', 'restart', _srv])
+ subprocess.call(['systemctl', 'enable', _srv])
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'])
+ for _srv in ('postfix', 'amavisd', 'clamd.amavisd', 'wallace'):
+ log.debug(_("Restarting %s service ... ") % _srv, level=8)
+ subprocess.call(['service', _srv, 'restart'])
+ subprocess.call(['chkconfig', _srv, 'on'])
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'])
+ for _srv in ('postfix', 'amavis', 'clamav-daemon', 'wallace'):
+ log.debug(_("Restarting %s service ... ") % _srv, level=8)
+ subprocess.call(['/usr/sbin/service', _srv, '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'])
+ for _srv in ('postfix.service', amavisservice, clamavservice, 'wallace.service'):
+ log.debug(_("Enabling %s systemd service to start on boot") % _srv, level=8)
+ subprocess.call(['systemctl', 'enable', _srv])
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'])
+ for _srv in ('postfix', 'amavisd', 'clamd.amavisd', 'wallace'):
+ log.debug(_("Enabling %s service to start on boot") % _srv, level=8)
+ subprocess.call(['chkconfig', _srv, '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'])
+ for _srv in ('postfix', 'amavis', 'clamav-daemon', 'wallace'):
+ log.debug(_("Enabling %s service to start on boot ") % _srv, level=8)
+ subprocess.call(['/usr/sbin/update-rc.d', _srv, 'defaults'])
else:
log.error(_("Could not configure to start on boot, the " + \
"postfix, clamav and amavisd services."))
diff --git a/pykolab/utils.py b/pykolab/utils.py
--- a/pykolab/utils.py
+++ b/pykolab/utils.py
@@ -364,7 +364,12 @@
if _object[key] is None:
continue
- val = map(_strip, _object[key])
+ # Dont run strip anything from attributes which
+ # hold byte strings
+ if key.lower() in constants.BINARY_ATTRS:
+ val = _object[key]
+ else:
+ val = map(_strip, _object[key])
if len(val) == 1:
result[key.lower()] = ''.join(val)
@@ -601,3 +606,19 @@
_other_services.append(service)
return (_service,_other_services)
+
+
+def find_template(file_name=None):
+ """
+ Function to find template file.
+ """
+
+ for template_location in constants.TEMPLATE_LOCATIONS:
+ if os.path.isfile('%s/%s' % (template_location, file_name)):
+ template_file = '%s/%s' % (template_location, file_name)
+ return template_file
+
+ import errno
+ raise IOError(
+ errno.ENOENT, os.strerror(errno.ENOENT), file_name)
+ log.error(_("Template %s not found.") % file_name)
diff --git a/share/templates/amavisd.conf.tpl b/share/templates/amavisd.conf.j2
copy from share/templates/amavisd.conf.tpl
copy to share/templates/amavisd.conf.j2
--- a/share/templates/amavisd.conf.tpl
+++ b/share/templates/amavisd.conf.j2
@@ -12,178 +12,178 @@
# @bypass_virus_checks_maps = (1); # controls running of anti-virus code
# @bypass_spam_checks_maps = (1); # controls running of anti-spam code
-# \$bypass_decode_parts = 1; # controls running of decoders&dearchivers
-
-\$max_servers = 2; # num of pre-forked children (2..30 is common), -m
-\$daemon_user = 'amavis'; # (no default; customary: vscan or amavis), -u
-\$daemon_group = 'amavis'; # (no default; customary: vscan or amavis), -g
-
-\$mydomain = '$primary_domain'; # a convenient default for other settings
-
-\$MYHOME = '/var/spool/amavisd'; # a convenient default for other settings, -H
-\$TEMPBASE = "\$MYHOME/tmp"; # working directory, needs to exist, -T
-\$ENV{TMPDIR} = \$TEMPBASE; # environment variable TMPDIR, used by SA, etc.
-\$QUARANTINEDIR = undef; # -Q
-# \$quarantine_subdir_levels = 1; # add level of subdirs to disperse quarantine
-# \$release_format = 'resend'; # 'attach', 'plain', 'resend'
-# \$report_format = 'arf'; # 'attach', 'plain', 'resend', 'arf'
-
-# \$daemon_chroot_dir = \$MYHOME; # chroot directory or undef, -R
-
-\$db_home = "\$MYHOME/db"; # dir for bdb nanny/cache/snmp databases, -D
-# \$helpers_home = "\$MYHOME/var"; # working directory for SpamAssassin, -S
-\$lock_file = "/var/run/amavisd/amavisd.lock"; # -L
-\$pid_file = "/var/run/amavisd/amavisd.pid"; # -P
-#NOTE: create directories \$MYHOME/tmp, \$MYHOME/var, \$MYHOME/db manually
-
-\$log_level = 9; # verbosity 0..5, -d
-\$log_recip_templ = undef; # disable by-recipient level-0 log entries
-\$DO_SYSLOG = 1; # log via syslogd (preferred)
-\$syslog_facility = 'mail'; # Syslog facility as a string
+# $bypass_decode_parts = 1; # controls running of decoders&dearchivers
+
+$max_servers = 2; # num of pre-forked children (2..30 is common), -m
+$daemon_user = 'amavis'; # (no default; customary: vscan or amavis), -u
+$daemon_group = 'amavis'; # (no default; customary: vscan or amavis), -g
+
+$mydomain = '{{ kolab.primary_domain }}'; # a convenient default for other settings
+
+$MYHOME = '/var/spool/amavisd'; # a convenient default for other settings, -H
+$TEMPBASE = "$MYHOME/tmp"; # working directory, needs to exist, -T
+$ENV{TMPDIR} = $TEMPBASE; # environment variable TMPDIR, used by SA, etc.
+$QUARANTINEDIR = undef; # -Q
+# $quarantine_subdir_levels = 1; # add level of subdirs to disperse quarantine
+# $release_format = 'resend'; # 'attach', 'plain', 'resend'
+# $report_format = 'arf'; # 'attach', 'plain', 'resend', 'arf'
+
+# $daemon_chroot_dir = $MYHOME; # chroot directory or undef, -R
+
+$db_home = "$MYHOME/db"; # dir for bdb nanny/cache/snmp databases, -D
+# $helpers_home = "$MYHOME/var"; # working directory for SpamAssassin, -S
+$lock_file = "/var/run/amavisd/amavisd.lock"; # -L
+$pid_file = "/var/run/amavisd/amavisd.pid"; # -P
+#NOTE: create directories $MYHOME/tmp, $MYHOME/var, $MYHOME/db manually
+
+$log_level = 3; # verbosity 0..5, -d
+$log_recip_templ = undef; # disable by-recipient level-0 log entries
+$DO_SYSLOG = 1; # log via syslogd (preferred)
+$syslog_facility = 'mail'; # Syslog facility as a string
# e.g.: mail, daemon, user, local0, ... local7
-\$syslog_priority = 'debug'; # Syslog base (minimal) priority as a string,
+$syslog_priority = 'debug'; # Syslog base (minimal) priority as a string,
# choose from: emerg, alert, crit, err, warning, notice, info, debug
-\$enable_db = 1; # enable use of BerkeleyDB/libdb (SNMP and nanny)
-\$enable_global_cache = 1; # enable use of libdb-based cache if \$enable_db=1
-\$nanny_details_level = 2; # nanny verbosity: 1: traditional, 2: detailed
-\$enable_dkim_verification = 1; # enable DKIM signatures verification
-\$enable_dkim_signing = 1; # load DKIM signing code, keys defined by dkim_key
+$enable_db = 1; # enable use of BerkeleyDB/libdb (SNMP and nanny)
+$enable_global_cache = 1; # enable use of libdb-based cache if $enable_db=1
+$nanny_details_level = 2; # nanny verbosity: 1: traditional, 2: detailed
+$enable_dkim_verification = 1; # enable DKIM signatures verification
+$enable_dkim_signing = 1; # load DKIM signing code, keys defined by dkim_key
-\$enable_ldap = 1;
-\$default_ldap = {
- hostname => [ 'localhost' ],
+$enable_ldap = 1;
+$default_ldap = {
+ hostname => [ '{{ ldap.ldap_uri}}' ],
version => 3,
timeout => 5,
tls => 0,
- base => '$ldap_base_dn',
- query_filter => '$ldap_filter',
- bind_dn => '$ldap_bind_dn',
- bind_password => '$ldap_bind_pw'
+ base => '{{ ldap.base_dn }}',
+ query_filter => '(|{% for item in ldap.mail_attributes.split(',') -%}({{ item | trim }}=%m){% endfor -%})',
+ bind_dn => '{{ ldap.service_bind_dn }}',
+ bind_password => '{{ ldap.service_bind_pw }}'
};
-@local_domains_maps = ( [".\$mydomain"] ); # list of all local domains
+@local_domains_maps = ( [".$mydomain"] ); # list of all local domains
@mynetworks = qw( 127.0.0.0/8 [::1] [FE80::]/10 [FEC0::]/10
10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 );
-\$unix_socketname = "\$MYHOME/amavisd.sock"; # amavisd-release or amavis-milter
- # option(s) -p overrides \$inet_socket_port and \$unix_socketname
+$unix_socketname = "$MYHOME/amavisd.sock"; # amavisd-release or amavis-milter
+ # option(s) -p overrides $inet_socket_port and $unix_socketname
-\$inet_socket_port = 10024; # listen on this local TCP port(s)
-# \$inet_socket_port = [10024,10026]; # listen on multiple TCP ports
+$inet_socket_port = 10024; # listen on this local TCP port(s)
+# $inet_socket_port = [10024,10026]; # listen on multiple TCP ports
-\$policy_bank{'MYNETS'} = { # mail originating from @mynetworks
- originating => 1, # is true in MYNETS by default, but let's make it explicit
- os_fingerprint_method => undef, # don't query p0f for internal clients
+$policy_bank{'MYNETS'} = { # mail originating from @mynetworks
+ originating => 1, # is true in MYNETS by default, but let's make it explicit
+ os_fingerprint_method => undef, # don't query p0f for internal clients
};
# it is up to MTA to re-route mail from authenticated roaming users or
# from internal hosts to a dedicated TCP port (such as 10026) for filtering
-\$interface_policy{'10026'} = 'ORIGINATING';
+$interface_policy{'10026'} = 'ORIGINATING';
-\$policy_bank{'ORIGINATING'} = { # mail supposedly originating from our users
- originating => 1, # declare that mail was submitted by our smtp client
- allow_disclaimers => 1, # enables disclaimer insertion if available
+$policy_bank{'ORIGINATING'} = { # mail supposedly originating from our users
+ originating => 1, # declare that mail was submitted by our smtp client
+ allow_disclaimers => 1, # enables disclaimer insertion if available
# notify administrator of locally originating malware
- virus_admin_maps => ["virusalert\@\$mydomain"],
- spam_admin_maps => ["virusalert\@\$mydomain"],
+ virus_admin_maps => ["virusalert\@$mydomain"],
+ spam_admin_maps => ["virusalert\@$mydomain"],
warnbadhsender => 1,
# forward to a smtpd service providing DKIM signing service
forward_method => 'smtp:[127.0.0.1]:10027',
# force MTA conversion to 7-bit (e.g. before DKIM signing)
smtpd_discard_ehlo_keywords => ['8BITMIME'],
- bypass_banned_checks_maps => [1], # allow sending any file names and types
- terminate_dsn_on_notify_success => 0, # don't remove NOTIFY=SUCCESS option
+ bypass_banned_checks_maps => [1], # allow sending any file names and types
+ terminate_dsn_on_notify_success => 0, # don't remove NOTIFY=SUCCESS option
};
-\$interface_policy{'SOCK'} = 'AM.PDP-SOCK'; # only applies with \$unix_socketname
+$interface_policy{'SOCK'} = 'AM.PDP-SOCK'; # only applies with $unix_socketname
# Use with amavis-release over a socket or with Petr Rehor's amavis-milter.c
# (with amavis-milter.c from this package or old amavis.c client use 'AM.CL'):
-\$policy_bank{'AM.PDP-SOCK'} = {
+$policy_bank{'AM.PDP-SOCK'} = {
protocol => 'AM.PDP',
- auth_required_release => 0, # do not require secret_id for amavisd-release
+ auth_required_release => 0, # do not require secret_id for amavisd-release
};
-\$sa_tag_level_deflt = -10; # add spam info headers if at, or above that level
-\$sa_tag2_level_deflt = 6.2; # add 'spam detected' headers at that level
-\$sa_kill_level_deflt = 6.9; # triggers spam evasive actions (e.g. blocks mail)
-\$sa_dsn_cutoff_level = 10; # spam level beyond which a DSN is not sent
-\$sa_crediblefrom_dsn_cutoff_level = 18; # likewise, but for a likely valid From
-# \$sa_quarantine_cutoff_level = 25; # spam level beyond which quarantine is off
-\$penpals_bonus_score = 8; # (no effect without a @storage_sql_dsn database)
-\$penpals_threshold_high = \$sa_kill_level_deflt; # don't waste time on hi spam
-\$bounce_killer_score = 100; # spam score points to add for joe-jobbed bounces
+$sa_tag_level_deflt = -10; # add spam info headers if at, or above that level
+$sa_tag2_level_deflt = 6.2; # add 'spam detected' headers at that level
+$sa_kill_level_deflt = 6.9; # triggers spam evasive actions (e.g. blocks mail)
+$sa_dsn_cutoff_level = 10; # spam level beyond which a DSN is not sent
+$sa_crediblefrom_dsn_cutoff_level = 18; # likewise, but for a likely valid From
+# $sa_quarantine_cutoff_level = 25; # spam level beyond which quarantine is off
+$penpals_bonus_score = 8; # (no effect without a @storage_sql_dsn database)
+$penpals_threshold_high = $sa_kill_level_deflt; # don't waste time on hi spam
+$bounce_killer_score = 100; # spam score points to add for joe-jobbed bounces
-\$sa_mail_body_size_limit = 400*1024; # don't waste time on SA if mail is larger
-\$sa_local_tests_only = 0; # only tests which do not require internet access?
+$sa_mail_body_size_limit = 400*1024; # don't waste time on SA if mail is larger
+$sa_local_tests_only = 0; # only tests which do not require internet access?
# @lookup_sql_dsn =
# ( ['DBI:mysql:database=mail;host=127.0.0.1;port=3306', 'user1', 'passwd1'],
# ['DBI:mysql:database=mail;host=host2', 'username2', 'password2'],
-# ["DBI:SQLite:dbname=\$MYHOME/sql/mail_prefs.sqlite", '', ''] );
+# ["DBI:SQLite:dbname=$MYHOME/sql/mail_prefs.sqlite", '', ''] );
# @storage_sql_dsn = @lookup_sql_dsn; # none, same, or separate database
-# \$timestamp_fmt_mysql = 1; # if using MySQL *and* msgs.time_iso is TIMESTAMP;
+# $timestamp_fmt_mysql = 1; # if using MySQL *and* msgs.time_iso is TIMESTAMP;
# defaults to 0, which is good for non-MySQL or if msgs.time_iso is CHAR(16)
-\$virus_admin = undef; # notifications recip.
+$virus_admin = undef; # notifications recip.
-\$mailfrom_notify_admin = undef; # notifications sender
-\$mailfrom_notify_recip = undef; # notifications sender
-\$mailfrom_notify_spamadmin = undef; # notifications sender
-\$mailfrom_to_quarantine = ''; # null return path; uses original sender if undef
+$mailfrom_notify_admin = undef; # notifications sender
+$mailfrom_notify_recip = undef; # notifications sender
+$mailfrom_notify_spamadmin = undef; # notifications sender
+$mailfrom_to_quarantine = ''; # null return path; uses original sender if undef
@addr_extension_virus_maps = ('virus');
@addr_extension_banned_maps = ('banned');
@addr_extension_spam_maps = ('spam');
@addr_extension_bad_header_maps = ('badh');
-# \$recipient_delimiter = '+'; # undef disables address extensions altogether
+# $recipient_delimiter = '+'; # undef disables address extensions altogether
# when enabling addr extensions do also Postfix/main.cf: recipient_delimiter=+
-\$path = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin:/usr/bin:/bin';
-# \$dspam = 'dspam';
+$path = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin:/usr/bin:/bin';
+# $dspam = 'dspam';
-\$MAXLEVELS = 14;
-\$MAXFILES = 1500;
-\$MIN_EXPANSION_QUOTA = 100*1024; # bytes (default undef, not enforced)
-\$MAX_EXPANSION_QUOTA = 300*1024*1024; # bytes (default undef, not enforced)
+$MAXLEVELS = 14;
+$MAXFILES = 1500;
+$MIN_EXPANSION_QUOTA = 100*1024; # bytes (default undef, not enforced)
+$MAX_EXPANSION_QUOTA = 300*1024*1024; # bytes (default undef, not enforced)
-\$sa_spam_subject_tag = '***SPAM*** ';
-\$defang_virus = 1; # MIME-wrap passed infected mail
-\$defang_banned = 1; # MIME-wrap passed mail containing banned name
+$sa_spam_subject_tag = '***SPAM*** ';
+$defang_virus = 1; # MIME-wrap passed infected mail
+$defang_banned = 1; # MIME-wrap passed mail containing banned name
# for defanging bad headers only turn on certain minor contents categories:
-\$defang_by_ccat{+CC_BADH.",3"} = 1; # NUL or CR character in header
-\$defang_by_ccat{+CC_BADH.",5"} = 1; # header line longer than 998 characters
-\$defang_by_ccat{+CC_BADH.",6"} = 1; # header field syntax error
+$defang_by_ccat{+CC_BADH.",3"} = 1; # NUL or CR character in header
+$defang_by_ccat{+CC_BADH.",5"} = 1; # header line longer than 998 characters
+$defang_by_ccat{+CC_BADH.",6"} = 1; # header field syntax error
# OTHER MORE COMMON SETTINGS (defaults may suffice):
-# \$myhostname = 'host.example.com'; # must be a fully-qualified domain name!
+# $myhostname = 'host.example.com'; # must be a fully-qualified domain name!
-# \$notify_method = 'smtp:[127.0.0.1]:10025';
-# \$forward_method = 'smtp:[127.0.0.1]:10025'; # set to undef with milter!
+# $notify_method = 'smtp:[127.0.0.1]:10025';
+# $forward_method = 'smtp:[127.0.0.1]:10025'; # set to undef with milter!
-\$final_virus_destiny = D_DISCARD;
-\$final_banned_destiny = D_BOUNCE;
-\$final_spam_destiny = D_DISCARD;
-\$final_bad_header_destiny = D_BOUNCE;
-# \$bad_header_quarantine_method = undef;
+$final_virus_destiny = D_DISCARD;
+$final_banned_destiny = D_BOUNCE;
+$final_spam_destiny = D_DISCARD;
+$final_bad_header_destiny = D_BOUNCE;
+# $bad_header_quarantine_method = undef;
-# \$os_fingerprint_method = 'p0f:*:2345'; # to query p0f-analyzer.pl
+# $os_fingerprint_method = 'p0f:*:2345'; # to query p0f-analyzer.pl
-\#\# hierarchy by which a final setting is chosen:
-\#\# policy bank (based on port or IP address) -> *_by_ccat
-\#\# *_by_ccat (based on mail contents) -> *_maps
-\#\# *_maps (based on recipient address) -> final configuration value
+## hierarchy by which a final setting is chosen:
+## policy bank (based on port or IP address) -> *_by_ccat
+## *_by_ccat (based on mail contents) -> *_maps
+## *_maps (based on recipient address) -> final configuration value
# SOME OTHER VARIABLES WORTH CONSIDERING (see amavisd.conf-default for all)
-# \$warnbadhsender,
-# \$warnvirusrecip, \$warnbannedrecip, \$warnbadhrecip, (or @warn*recip_maps)
+# $warnbadhsender,
+# $warnvirusrecip, $warnbannedrecip, $warnbadhrecip, (or @warn*recip_maps)
#
# @bypass_virus_checks_maps, @bypass_spam_checks_maps,
# @bypass_banned_checks_maps, @bypass_header_checks_maps,
@@ -193,67 +193,67 @@
#
# @blacklist_sender_maps, @score_sender_maps,
#
-# \$clean_quarantine_method, \$virus_quarantine_to, \$banned_quarantine_to,
-# \$bad_header_quarantine_to, \$spam_quarantine_to,
+# $clean_quarantine_method, $virus_quarantine_to, $banned_quarantine_to,
+# $bad_header_quarantine_to, $spam_quarantine_to,
#
-# \$defang_bad_header, \$defang_undecipherable, \$defang_spam
+# $defang_bad_header, $defang_undecipherable, $defang_spam
# REMAINING IMPORTANT VARIABLES ARE LISTED HERE BECAUSE OF LONGER ASSIGNMENTS
@keep_decoded_original_maps = (new_RE(
- qr'^MAIL\$', # retain full original message for virus checking
- qr'^MAIL-UNDECIPHERABLE\$', # recheck full mail if it contains undecipherables
+ qr'^MAIL$', # retain full original message for virus checking
+ qr'^MAIL-UNDECIPHERABLE$', # recheck full mail if it contains undecipherables
qr'^(ASCII(?! cpio)|text|uuencoded|xxencoded|binhex)'i,
# qr'^Zip archive data', # don't trust Archive::Zip
));
-# for \$banned_namepath_re (a new-style of banned table) see amavisd.conf-sample
+# for $banned_namepath_re (a new-style of banned table) see amavisd.conf-sample
-\$banned_filename_re = new_RE(
+$banned_filename_re = new_RE(
-\#\## BLOCKED ANYWHERE
-# qr'^UNDECIPHERABLE\$', # is or contains any undecipherable components
- qr'^\.(exe-ms|dll)\$', # banned file(1) types, rudimentary
-# qr'^\.(exe|lha|tnef|cab|dll)\$', # banned file(1) types
+### BLOCKED ANYWHERE
+# qr'^UNDECIPHERABLE$', # is or contains any undecipherable components
+ qr'^\.(exe-ms|dll)$', # banned file(1) types, rudimentary
+# qr'^\.(exe|lha|tnef|cab|dll)$', # banned file(1) types
-\#\## BLOCK THE FOLLOWING, EXCEPT WITHIN UNIX ARCHIVES:
-# [ qr'^\.(gz|bz2)\$' => 0 ], # allow any in gzip or bzip2
- [ qr'^\.(rpm|cpio|tar)\$' => 0 ], # allow any in Unix-type archives
+### BLOCK THE FOLLOWING, EXCEPT WITHIN UNIX ARCHIVES:
+# [ qr'^\.(gz|bz2)$' => 0 ], # allow any in gzip or bzip2
+ [ qr'^\.(rpm|cpio|tar)$' => 0 ], # allow any in Unix-type archives
- qr'.\.(pif|scr)\$'i, # banned extensions - rudimentary
-# qr'^\.zip\$', # block zip type
+ qr'.\.(pif|scr)$'i, # banned extensions - rudimentary
+# qr'^\.zip$', # block zip type
-\#\## BLOCK THE FOLLOWING, EXCEPT WITHIN ARCHIVES:
-# [ qr'^\.(zip|rar|arc|arj|zoo)\$'=> 0 ], # allow any within these archives
+### BLOCK THE FOLLOWING, EXCEPT WITHIN ARCHIVES:
+# [ qr'^\.(zip|rar|arc|arj|zoo)$'=> 0 ], # allow any within these archives
- qr'^application/x-msdownload\$'i, # block these MIME types
- qr'^application/x-msdos-program\$'i,
- qr'^application/hta\$'i,
+ qr'^application/x-msdownload$'i, # block these MIME types
+ qr'^application/x-msdos-program$'i,
+ qr'^application/hta$'i,
-# qr'^message/partial\$'i, # rfc2046 MIME type
-# qr'^message/external-body\$'i, # rfc2046 MIME type
+# qr'^message/partial$'i, # rfc2046 MIME type
+# qr'^message/external-body$'i, # rfc2046 MIME type
-# qr'^(application/x-msmetafile|image/x-wmf)\$'i, # Windows Metafile MIME type
-# qr'^\.wmf\$', # Windows Metafile file(1) type
+# qr'^(application/x-msmetafile|image/x-wmf)$'i, # Windows Metafile MIME type
+# qr'^\.wmf$', # Windows Metafile file(1) type
# block certain double extensions in filenames
- qr'\.[^./]*[A-Za-z][^./]*\.\s*(exe|vbs|pif|scr|bat|cmd|com|cpl|dll)[.\s]*\$'i,
+ qr'\.[^./]*[A-Za-z][^./]*\.\s*(exe|vbs|pif|scr|bat|cmd|com|cpl|dll)[.\s]*$'i,
# qr'\{[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\}?'i, # Class ID CLSID, strict
# qr'\{[0-9a-z]{4,}(-[0-9a-z]{4,}){0,7}\}?'i, # Class ID extension CLSID, loose
- qr'.\.(exe|vbs|pif|scr|cpl)\$'i, # banned extension - basic
-# qr'.\.(exe|vbs|pif|scr|cpl|bat|cmd|com)\$'i, # banned extension - basic+cmd
+ qr'.\.(exe|vbs|pif|scr|cpl)$'i, # banned extension - basic
+# qr'.\.(exe|vbs|pif|scr|cpl|bat|cmd|com)$'i, # banned extension - basic+cmd
# qr'.\.(ade|adp|app|bas|bat|chm|cmd|com|cpl|crt|emf|exe|fxp|grp|hlp|hta|
# inf|ins|isp|js|jse|lnk|mda|mdb|mde|mdw|mdt|mdz|msc|msi|msp|mst|
# ops|pcd|pif|prg|reg|scr|sct|shb|shs|vb|vbe|vbs|
-# wmf|wsc|wsf|wsh)\$'ix, # banned ext - long
-# qr'.\.(ani|cur|ico)\$'i, # banned cursors and icons filename
-# qr'^\.ani\$', # banned animated cursor file(1) type
+# wmf|wsc|wsf|wsh)$'ix, # banned ext - long
+# qr'.\.(ani|cur|ico)$'i, # banned cursors and icons filename
+# qr'^\.ani$', # banned animated cursor file(1) type
-# qr'.\.(mim|b64|bhx|hqx|xxe|uu|uue)\$'i, # banned extension - WinZip vulnerab.
+# qr'.\.(mim|b64|bhx|hqx|xxe|uu|uue)$'i, # banned extension - WinZip vulnerab.
);
# See http://support.microsoft.com/default.aspx?scid=kb;EN-US;q262631
# and http://www.cknow.com/vtutor/vtextensions.htm
@@ -264,13 +264,13 @@
@score_sender_maps = ({ # a by-recipient hash lookup table,
# results from all matching recipient tables are summed
-# \#\# per-recipient personal tables (NOTE: positive: black, negative: white)
+# ## per-recipient personal tables (NOTE: positive: black, negative: white)
# 'user1@example.com' => [{'bla-mobile.press@example.com' => 10.0}],
# 'user3@example.com' => [{'.ebay.com' => -3.0}],
# 'user4@example.com' => [{'cleargreen@cleargreen.com' => -7.0,
# '.cleargreen.com' => -5.0}],
- \#\# site-wide opinions about senders (the '.' matches any recipient)
+ ## site-wide opinions about senders (the '.' matches any recipient)
'.' => [ # the _first_ matching sender determines the score boost
new_RE( # regexp-type lookup table, just happens to be all soft-blacklist
@@ -362,47 +362,47 @@
@av_scanners = (
-# \#\## http://www.clanfield.info/sophie/ (http://www.vanja.com/tools/sophie/)
+# ### http://www.clanfield.info/sophie/ (http://www.vanja.com/tools/sophie/)
# ['Sophie',
# \&ask_daemon, ["{}/\n", '/var/run/sophie'],
-# qr/(?x)^ 0+ ( : | [\000\r\n]* \$)/m, qr/(?x)^ 1 ( : | [\000\r\n]* \$)/m,
-# qr/(?x)^ [-+]? \d+ : (.*?) [\000\r\n]* \$/m ],
+# qr/(?x)^ 0+ ( : | [\000\r\n]* $)/m, qr/(?x)^ 1 ( : | [\000\r\n]* $)/m,
+# qr/(?x)^ [-+]? \d+ : (.*?) [\000\r\n]* $/m ],
-# \#\## http://www.csupomona.edu/~henson/www/projects/SAVI-Perl/
+# ### http://www.csupomona.edu/~henson/www/projects/SAVI-Perl/
# ['Sophos SAVI', \&sophos_savi ],
-# \#\## http://www.clamav.net/
+# ### http://www.clamav.net/
['ClamAV-clamd',
- \&ask_daemon, ["CONTSCAN {}\n", "$clamdsock"],
- qr/\bOK\$/m, qr/\bFOUND\$/m,
- qr/^.*?: (?!Infected Archive)(.*) FOUND\$/m ],
+ \&ask_daemon, ["CONTSCAN {}\n", "{{ clamdsock }}"],
+ qr/\bOK$/m, qr/\bFOUND$/m,
+ qr/^.*?: (?!Infected Archive)(.*) FOUND$/m ],
# # NOTE: run clamd under the same user as amavisd, or run it under its own
# # uid such as clamav, add user clamav to the amavis group, and then add
# # AllowSupplementaryGroups to clamd.conf;
# # NOTE: match socket name (LocalSocket) in clamav.conf to the socket name in
-# # this entry; when running chrooted one may prefer socket "\$MYHOME/clamd".
+# # this entry; when running chrooted one may prefer socket "$MYHOME/clamd".
-# \#\## http://www.clamav.net/ and CPAN (memory-hungry! clamd is preferred)
+# ### http://www.clamav.net/ and CPAN (memory-hungry! clamd is preferred)
# # note that Mail::ClamAV requires perl to be build with threading!
# ['Mail::ClamAV', \&ask_clamav, "*", [0], [1], qr/^INFECTED: (.+)/m ],
-# \#\## http://www.openantivirus.org/
+# ### http://www.openantivirus.org/
# ['OpenAntiVirus ScannerDaemon (OAV)',
# \&ask_daemon, ["SCAN {}\n", '127.0.0.1:8127'],
# qr/^OK/m, qr/^FOUND: /m, qr/^FOUND: (.+)/m ],
-# \#\## http://www.vanja.com/tools/trophie/
+# ### http://www.vanja.com/tools/trophie/
# ['Trophie',
# \&ask_daemon, ["{}/\n", '/var/run/trophie'],
-# qr/(?x)^ 0+ ( : | [\000\r\n]* \$)/m, qr/(?x)^ 1 ( : | [\000\r\n]* \$)/m,
-# qr/(?x)^ [-+]? \d+ : (.*?) [\000\r\n]* \$/m ],
+# qr/(?x)^ 0+ ( : | [\000\r\n]* $)/m, qr/(?x)^ 1 ( : | [\000\r\n]* $)/m,
+# qr/(?x)^ [-+]? \d+ : (.*?) [\000\r\n]* $/m ],
-# \#\## http://www.grisoft.com/
+# ### http://www.grisoft.com/
# ['AVG Anti-Virus',
# \&ask_daemon, ["SCAN {}\n", '127.0.0.1:55555'],
# qr/^200/m, qr/^403/m, qr/^403 .*?: ([^\r\n]+)/m ],
-# \#\## http://www.f-prot.com/
+# ### http://www.f-prot.com/
# ['F-Prot fpscand', # F-PROT Antivirus for BSD/Linux/Solaris, version 6
# \&ask_daemon,
# ["SCAN FILE {}/*\n", '127.0.0.1:10200'],
@@ -410,7 +410,7 @@
# qr/^([1235679]|1[01345]) |<[^>:]*(?i)(infected|suspicious|unwanted)/m,
# qr/(?i)<[^>:]*(?:infected|suspicious|unwanted)[^>:]*: ([^>]*)>/m ],
-# \#\## http://www.f-prot.com/
+# ### http://www.f-prot.com/
# ['F-Prot f-protd', # old version
# \&ask_daemon,
# ["GET {}/*?-dumb%20-archive%20-packed HTTP/1.0\r\n\r\n",
@@ -420,12 +420,12 @@
# qr/(?i)]*>infected<\/summary>/m,
# qr/(?i)(.+)<\/name>/m ],
-# \#\## http://www.sald.com/, http://www.dials.ru/english/, http://www.drweb.ru/
+# ### http://www.sald.com/, http://www.dials.ru/english/, http://www.drweb.ru/
# ['DrWebD', \&ask_daemon, # DrWebD 4.31 or later
# [pack('N',1). # DRWEBD_SCAN_CMD
# pack('N',0x00280001). # DONT_CHANGEMAIL, IS_MAIL, RETURN_VIRUSES
# pack('N', # path length
-# length("\$TEMPBASE/amavis-yyyymmddTHHMMSS-xxxxx/parts/pxxx")).
+# length("$TEMPBASE/amavis-yyyymmddTHHMMSS-xxxxx/parts/pxxx")).
# '{}/*'. # path
# pack('N',0). # content size
# pack('N',0),
@@ -439,9 +439,9 @@
# qr/\A.{12}(?:infected with )?([^\x00]+)\x00/sm,
# ],
# # NOTE: If using amavis-milter, change length to:
-# # length("\$TEMPBASE/amavis-milter-xxxxxxxxxxxxxx/parts/pxxx").
+# # length("$TEMPBASE/amavis-milter-xxxxxxxxxxxxxx/parts/pxxx").
- \#\## http://www.kaspersky.com/ (kav4mailservers)
+ ### http://www.kaspersky.com/ (kav4mailservers)
['KasperskyLab AVP - aveclient',
['/usr/local/kav/bin/aveclient','/usr/local/share/kav/bin/aveclient',
'/opt/kav/5.5/kav4mailservers/bin/aveclient','aveclient'],
@@ -452,50 +452,50 @@
# NOTE: one may prefer [0],[2,3,4,5], depending on how suspicious,
# currupted or protected archives are to be handled
- \#\## http://www.kaspersky.com/
+ ### http://www.kaspersky.com/
['KasperskyLab AntiViral Toolkit Pro (AVP)', ['avp'],
'-* -P -B -Y -O- {}', [0,3,6,8], [2,4], # any use for -A -K ?
qr/infected: (.+)/m,
- sub {chdir('/opt/AVP') or die "Can't chdir to AVP: \$!"},
- sub {chdir(\$TEMPBASE) or die "Can't chdir back to \$TEMPBASE \$!"},
+ sub {chdir('/opt/AVP') or die "Can't chdir to AVP: $!"},
+ sub {chdir($TEMPBASE) or die "Can't chdir back to $TEMPBASE $!"},
],
- \#\## The kavdaemon and AVPDaemonClient have been removed from Kasperky
- \#\## products and replaced by aveserver and aveclient
+ ### The kavdaemon and AVPDaemonClient have been removed from Kasperky
+ ### products and replaced by aveserver and aveclient
['KasperskyLab AVPDaemonClient',
[ '/opt/AVP/kavdaemon', 'kavdaemon',
'/opt/AVP/AvpDaemonClient', 'AvpDaemonClient',
'/opt/AVP/AvpTeamDream', 'AvpTeamDream',
'/opt/AVP/avpdc', 'avpdc' ],
- "-f=\$TEMPBASE {}", [0,8], [3,4,5,6], qr/infected: ([^\r\n]+)/m ],
+ "-f=$TEMPBASE {}", [0,8], [3,4,5,6], qr/infected: ([^\r\n]+)/m ],
# change the startup-script in /etc/init.d/kavd to:
# DPARMS="-* -Y -dl -f=/var/amavis /var/amavis"
# (or perhaps: DPARMS="-I0 -Y -* /var/amavis" )
- # adjusting /var/amavis above to match your \$TEMPBASE.
+ # adjusting /var/amavis above to match your $TEMPBASE.
# The '-f=/var/amavis' is needed if not running it as root, so it
# can find, read, and write its pid file, etc., see 'man kavdaemon'.
# defUnix.prf: there must be an entry "*/var/amavis" (or whatever
- # directory \$TEMPBASE specifies) in the 'Names=' section.
+ # directory $TEMPBASE specifies) in the 'Names=' section.
# cd /opt/AVP/DaemonClients; configure; cd Sample; make
# cp AvpDaemonClient /opt/AVP/
- # su - vscan -c "\${PREFIX}/kavdaemon \${DPARMS}"
+ # su - vscan -c "${PREFIX}/kavdaemon ${DPARMS}"
- \#\## http://www.centralcommand.com/
+ ### http://www.centralcommand.com/
['CentralCommand Vexira (new) vascan',
['vascan','/usr/lib/Vexira/vascan'],
- "-a s --timeout=60 --temp=\$TEMPBASE -y \$QUARANTINEDIR ".
+ "-a s --timeout=60 --temp=$TEMPBASE -y $QUARANTINEDIR ".
"--log=/var/log/vascan.log {}",
[0,3], [1,2,5],
qr/(?x)^\s* (?:virus|iworm|macro|mutant|sequence|trojan)\ found:\ ( [^\]\s']+ )\ \.\.\.\ /m ],
# Adjust the path of the binary and the virus database as needed.
# 'vascan' does not allow to have the temp directory to be the same as
# the quarantine directory, and the quarantine option can not be disabled.
- # If \$QUARANTINEDIR is not used, then another directory must be specified
+ # If $QUARANTINEDIR is not used, then another directory must be specified
# to appease 'vascan'. Move status 3 to the second list if password
# protected files are to be considered infected.
- \#\## http://www.avira.com/
- \#\## Avira AntiVir (formerly H+BEDV) or (old) CentralCommand Vexira Antivirus
+ ### http://www.avira.com/
+ ### Avira AntiVir (formerly H+BEDV) or (old) CentralCommand Vexira Antivirus
['Avira AntiVir', ['antivir','vexira'],
'--allfiles -noboot -nombr -rs -s -z {}', [0], qr/ALERT:|VIRUS:/m,
qr/(?x)^\s* (?: ALERT: \s* (?: \[ | [^']* ' ) |
@@ -503,32 +503,32 @@
# NOTE: if you only have a demo version, remove -z and add 214, as in:
# '--allfiles -noboot -nombr -rs -s {}', [0,214], qr/ALERT:|VIRUS:/,
- \#\## http://www.commandsoftware.com/
+ ### http://www.commandsoftware.com/
['Command AntiVirus for Linux', 'csav',
'-all -archive -packed {}', [50], [51,52,53],
qr/Infection: (.+)/m ],
- \#\## http://www.symantec.com/
+ ### http://www.symantec.com/
['Symantec CarrierScan via Symantec CommandLineScanner',
'cscmdline', '-a scan -i 1 -v -s 127.0.0.1:7777 {}',
- qr/^Files Infected:\s+0\$/m, qr/^Infected\b/m,
+ qr/^Files Infected:\s+0$/m, qr/^Infected\b/m,
qr/^(?:Info|Virus Name):\s+(.+)/m ],
- \#\## http://www.symantec.com/
+ ### http://www.symantec.com/
['Symantec AntiVirus Scan Engine',
'savsecls', '-server 127.0.0.1:7777 -mode scanrepair -details -verbose {}',
[0], qr/^Infected\b/m,
qr/^(?:Info|Virus Name):\s+(.+)/m ],
# NOTE: check options and patterns to see which entry better applies
-# \#\## http://www.f-secure.com/products/anti-virus/ version 4.65
+# ### http://www.f-secure.com/products/anti-virus/ version 4.65
# ['F-Secure Antivirus for Linux servers',
# ['/opt/f-secure/fsav/bin/fsav', 'fsav'],
# '--delete=no --disinf=no --rename=no --archive=yes --auto=yes '.
# '--dumb=yes --list=no --mime=yes {}', [0], [3,6,8],
# qr/(?:infection|Infected|Suspected): (.+)/m ],
- \#\## http://www.f-secure.com/products/anti-virus/ version 5.52
+ ### http://www.f-secure.com/products/anti-virus/ version 5.52
['F-Secure Antivirus for Linux servers',
['/opt/f-secure/fsav/bin/fsav', 'fsav'],
'--virus-action1=report --archive=yes --auto=yes '.
@@ -537,13 +537,13 @@
# NOTE: internal archive handling may be switched off by '--archive=no'
# to prevent fsav from exiting with status 9 on broken archives
-# \#\## http://www.avast.com/
+# ### http://www.avast.com/
# ['avast! Antivirus daemon',
# \&ask_daemon, # greets with 220, terminate with QUIT
# ["SCAN {}\015\012QUIT\015\012", '/var/run/avast4/mailscanner.sock'],
# qr/\t\[\+\]/m, qr/\t\[L\]\t/m, qr/\t\[L\]\t([^[ \t\015\012]+)/m ],
-# \#\## http://www.avast.com/
+# ### http://www.avast.com/
# ['avast! Antivirus - Client/Server Version', 'avastlite',
# '-a /var/run/avast4/mailscanner.sock -n {}', [0], [1],
# qr/\t\[L\]\t([^[ \t\015\012]+)/m ],
@@ -553,24 +553,24 @@
qr/was infected by virus (.+)/m ],
# see: http://www.flatmtn.com/computer/Linux-Antivirus_CAI.html
- \#\## http://www3.ca.com/Solutions/Product.asp?ID=156 (ex InoculateIT)
+ ### http://www3.ca.com/Solutions/Product.asp?ID=156 (ex InoculateIT)
['CAI eTrust Antivirus', 'etrust-wrapper',
'-arc -nex -spm h {}', [0], [101],
qr/is infected by virus: (.+)/m ],
# NOTE: requires suid wrapper around inocmd32; consider flag: -mod reviewer
# see http://marc.theaimsgroup.com/?l=amavis-user&m=109229779912783
- \#\## http://mks.com.pl/english.html
+ ### http://mks.com.pl/english.html
['MkS_Vir for Linux (beta)', ['mks32','mks'],
'-s {}/*', [0], [1,2],
qr/--[ \t]*(.+)/m ],
- \#\## http://mks.com.pl/english.html
+ ### http://mks.com.pl/english.html
['MkS_Vir daemon', 'mksscan',
'-s -q {}', [0], [1..7],
qr/^... (\S+)/m ],
-# \#\## http://www.nod32.com/, version v2.52 (old)
+# ### http://www.nod32.com/, version v2.52 (old)
# ['ESET NOD32 for Linux Mail servers',
# ['/opt/eset/nod32/bin/nod32cli', 'nod32cli'],
# '--subdir --files -z --sfx --rtp --adware --unsafe --pattern --heur '.
@@ -578,23 +578,23 @@
# '--action-on-notscanned=accept {}',
# [0,3], [1,2], qr/virus="([^"]+)"/m ],
-# \#\## http://www.eset.com/, version v2.7 (old)
+# ### http://www.eset.com/, version v2.7 (old)
# ['ESET NOD32 Linux Mail Server - command line interface',
# ['/usr/bin/nod32cli', '/opt/eset/nod32/bin/nod32cli', 'nod32cli'],
# '--subdir {}', [0,3], [1,2], qr/virus="([^"]+)"/m ],
-# \#\## http://www.eset.com/, version 2.71.12
+# ### http://www.eset.com/, version 2.71.12
# ['ESET Software ESETS Command Line Interface',
# ['/usr/bin/esets_cli', 'esets_cli'],
# '--subdir {}', [0], [1,2,3], qr/virus="([^"]+)"/m ],
- \#\## http://www.eset.com/, version 3.0
+ ### http://www.eset.com/, version 3.0
['ESET Software ESETS Command Line Interface',
['/usr/bin/esets_cli', 'esets_cli'],
'--subdir {}', [0], [1,2,3],
qr/:\s*action="(?!accepted)[^"]*"\n.*:\s*virus="([^"]*)"/m ],
- \#\# http://www.nod32.com/, NOD32LFS version 2.5 and above
+ ## http://www.nod32.com/, NOD32LFS version 2.5 and above
['ESET NOD32 for Linux File servers',
['/opt/eset/nod32/sbin/nod32','nod32'],
'--files -z --mail --sfx --rtp --adware --unsafe --pattern --heur '.
@@ -607,12 +607,12 @@
# ["SCAN {}/*\r\n", '127.0.0.1:8448' ],
# qr/^200 File OK/m, qr/^201 /m, qr/^201 (.+)/m ],
- \#\## http://www.norman.com/products_nvc.shtml
+ ### http://www.norman.com/products_nvc.shtml
['Norman Virus Control v5 / Linux', 'nvcc',
- '-c -l:0 -s -u -temp:\$TEMPBASE {}', [0,10,11], [1,2,14],
+ '-c -l:0 -s -u -temp:$TEMPBASE {}', [0,10,11], [1,2,14],
qr/(?i).* virus in .* -> \'(.+)\'/m ],
- \#\## http://www.pandasoftware.com/
+ ### http://www.pandasoftware.com/
['Panda CommandLineSecure 9 for Linux',
['/opt/pavcl/usr/bin/pavcl','pavcl'],
'-auto -aex -heu -cmp -nbr -nor -nos -eng -nob {}',
@@ -626,7 +626,7 @@
# Please review other options of pavcl, for example:
# -nomalw, -nojoke, -nodial, -nohackt, -nospyw, -nocookies
-# \#\## http://www.pandasoftware.com/
+# ### http://www.pandasoftware.com/
# ['Panda Antivirus for Linux', ['pavcl'],
# '-TSR -aut -aex -heu -cmp -nbr -nor -nso -eng {}',
# [0], [0x10, 0x30, 0x50, 0x70, 0x90, 0xB0, 0xD0, 0xF0],
@@ -639,15 +639,15 @@
# # NOTE: the command line switches changed with scan engine 8.5 !
# # (btw, assigning stdin to /dev/null causes RAV to fail)
- \#\## http://www.nai.com/
+ ### http://www.nai.com/
['NAI McAfee AntiVirus (uvscan)', 'uvscan',
'--secure -rv --mime --summary --noboot - {}', [0], [13],
qr/(?x) Found (?:
\ the\ (.+)\ (?:virus|trojan) |
\ (?:virus|trojan)\ or\ variant\ ([^ ]+) |
:\ (.+)\ NOT\ a\ virus)/m,
- # sub {\$ENV{LD_PRELOAD}='/lib/libc.so.6'},
- # sub {delete \$ENV{LD_PRELOAD}},
+ # sub {$ENV{LD_PRELOAD}='/lib/libc.so.6'},
+ # sub {delete $ENV{LD_PRELOAD}},
],
# NOTE1: with RH9: force the dynamic linker to look at /lib/libc.so.6 before
# anything else by setting environment variable LD_PRELOAD=/lib/libc.so.6
@@ -655,72 +655,72 @@
# NOTE2: to treat encrypted files as viruses replace the [13] with:
# qr/^\s{5,}(Found|is password-protected|.*(virus|trojan))/
- \#\## http://www.virusbuster.hu/en/
+ ### http://www.virusbuster.hu/en/
['VirusBuster', ['vbuster', 'vbengcl'],
- "{} -ss -i '*' -log=\$MYHOME/vbuster.log", [0], [1],
+ "{} -ss -i '*' -log=$MYHOME/vbuster.log", [0], [1],
qr/: '(.*)' - Virus/m ],
# VirusBuster Ltd. does not support the daemon version for the workstation
# engine (vbuster-eng-1.12-linux-i386-libc6.tgz) any longer. The names of
# binaries, some parameters AND return codes have changed (from 3 to 1).
# See also the new Vexira entry 'vascan' which is possibly related.
-# \#\## http://www.virusbuster.hu/en/
+# ### http://www.virusbuster.hu/en/
# ['VirusBuster (Client + Daemon)', 'vbengd',
# '-f -log scandir {}', [0], [3],
# qr/Virus found = (.*);/m ],
# # HINT: for an infected file it always returns 3,
# # although the man-page tells a different story
- \#\## http://www.cyber.com/
+ ### http://www.cyber.com/
['CyberSoft VFind', 'vfind',
- '--vexit {}/*', [0], [23], qr/\#\#==>>>> VIRUS ID: CVDL (.+)/m,
- # sub {\$ENV{VSTK_HOME}='/usr/lib/vstk'},
+ '--vexit {}/*', [0], [23], qr/##==>>>> VIRUS ID: CVDL (.+)/m,
+ # sub {$ENV{VSTK_HOME}='/usr/lib/vstk'},
],
- \#\## http://www.avast.com/
+ ### http://www.avast.com/
['avast! Antivirus', ['/usr/bin/avastcmd','avastcmd'],
'-a -i -n -t=A {}', [0], [1], qr/\binfected by:\s+([^ \t\n\[\]]+)/m ],
- \#\## http://www.ikarus-software.com/
+ ### http://www.ikarus-software.com/
['Ikarus AntiVirus for Linux', 'ikarus',
'{}', [0], [40], qr/Signature (.+) found/m ],
- \#\## http://www.bitdefender.com/
+ ### http://www.bitdefender.com/
['BitDefender', 'bdscan', # new version
'--action=ignore --no-list {}', qr/^Infected files\s*:\s*0+(?!\d)/m,
qr/^(?:Infected files|Identified viruses|Suspect files)\s*:\s*0*[1-9]/m,
- qr/(?:suspected|infected)\s*:\s*(.*)(?:\033|\$)/m ],
+ qr/(?:suspected|infected)\s*:\s*(.*)(?:\033|$)/m ],
- \#\## http://www.bitdefender.com/
+ ### http://www.bitdefender.com/
['BitDefender', 'bdc', # old version
'--arc --mail {}', qr/^Infected files *:0+(?!\d)/m,
qr/^(?:Infected files|Identified viruses|Suspect files) *:0*[1-9]/m,
- qr/(?:suspected|infected): (.*)(?:\033|\$)/m ],
+ qr/(?:suspected|infected): (.*)(?:\033|$)/m ],
# consider also: --all --nowarn --alev=15 --flev=15. The --all argument may
# not apply to your version of bdc, check documentation and see 'bdc --help'
- \#\## ArcaVir for Linux and Unix http://www.arcabit.pl/
+ ### ArcaVir for Linux and Unix http://www.arcabit.pl/
['ArcaVir for Linux', ['arcacmd','arcacmd.static'],
'-v 1 -summary 0 -s {}', [0], [1,2],
qr/(?:VIR|WIR):[ \t]*(.+)/m ],
-# \#\## a generic SMTP-client interface to a SMTP-based virus scanner
+# ### a generic SMTP-client interface to a SMTP-based virus scanner
# ['av_smtp', \&ask_av_smtp,
# ['{}', 'smtp:[127.0.0.1]:5525', 'dummy@localhost'],
-# qr/^2/, qr/^5/, qr/^\s*(.*?)\s*\$/m ],
+# qr/^2/, qr/^5/, qr/^\s*(.*?)\s*$/m ],
# ['File::Scan', sub {Amavis::AV::ask_av(sub{
-# use File::Scan; my(\$fn)=@_;
-# my(\$f)=File::Scan->new(max_txt_size=>0, max_bin_size=>0);
-# my(\$vname) = \$f->scan(\$fn);
-# \$f->error ? (2,"Error: ".\$f->error)
-# : (\$vname ne '') ? (1,"\$vname FOUND") : (0,"Clean")}, @_) },
-# ["{}/*"], [0], [1], qr/^(.*) FOUND\$/m ],
-
-# \#\## fully-fledged checker for JPEG marker segments of invalid length
+# use File::Scan; my($fn)=@_;
+# my($f)=File::Scan->new(max_txt_size=>0, max_bin_size=>0);
+# my($vname) = $f->scan($fn);
+# $f->error ? (2,"Error: ".$f->error)
+# : ($vname ne '') ? (1,"$vname FOUND") : (0,"Clean")}, @_) },
+# ["{}/*"], [0], [1], qr/^(.*) FOUND$/m ],
+
+# ### fully-fledged checker for JPEG marker segments of invalid length
# ['check-jpeg',
# sub { use JpegTester (); Amavis::AV::ask_av(\&JpegTester::test_jpeg, @_) },
-# ["{}/*"], undef, [1], qr/^(bad jpeg: .*)\$/m ],
+# ["{}/*"], undef, [1], qr/^(bad jpeg: .*)$/m ],
# # NOTE: place file JpegTester.pm somewhere where Perl can find it,
# # for example in /usr/local/lib/perl5/site_perl
@@ -729,48 +729,48 @@
@av_scanners_backup = (
- \#\## http://www.clamav.net/ - backs up clamd or Mail::ClamAV
+ ### http://www.clamav.net/ - backs up clamd or Mail::ClamAV
['ClamAV-clamscan', 'clamscan',
- "--stdout --no-summary -r --tempdir=\$TEMPBASE {}",
- [0], qr/:.*\sFOUND\$/m, qr/^.*?: (?!Infected Archive)(.*) FOUND\$/m ],
+ "--stdout --no-summary -r --tempdir=$TEMPBASE {}",
+ [0], qr/:.*\sFOUND$/m, qr/^.*?: (?!Infected Archive)(.*) FOUND$/m ],
- \#\## http://www.f-prot.com/ - backs up F-Prot Daemon, V6
+ ### http://www.f-prot.com/ - backs up F-Prot Daemon, V6
['F-PROT Antivirus for UNIX', ['fpscan'],
'--report --mount --adware {}', # consider: --applications -s 4 -u 3 -z 10
[0,8,64], [1,2,3, 4+1,4+2,4+3, 8+1,8+2,8+3, 12+1,12+2,12+3],
qr/^\[Found\s+[^\]]*\]\s+<([^ \t(>]*)/m ],
- \#\## http://www.f-prot.com/ - backs up F-Prot Daemon (old)
+ ### http://www.f-prot.com/ - backs up F-Prot Daemon (old)
['FRISK F-Prot Antivirus', ['f-prot','f-prot.sh'],
'-dumb -archive -packed {}', [0,8], [3,6], # or: [0], [3,6,8],
- qr/(?:Infection:|security risk named) (.+)|\s+contains\s+(.+)\$/m ],
+ qr/(?:Infection:|security risk named) (.+)|\s+contains\s+(.+)$/m ],
- \#\## http://www.trendmicro.com/ - backs up Trophie
+ ### http://www.trendmicro.com/ - backs up Trophie
['Trend Micro FileScanner', ['/etc/iscan/vscan','vscan'],
'-za -a {}', [0], qr/Found virus/m, qr/Found virus (.+) in/m ],
- \#\## http://www.sald.com/, http://drweb.imshop.de/ - backs up DrWebD
+ ### http://www.sald.com/, http://drweb.imshop.de/ - backs up DrWebD
['drweb - DrWeb Antivirus', # security LHA hole in Dr.Web 4.33 and earlier
['/usr/local/drweb/drweb', '/opt/drweb/drweb', 'drweb'],
'-path={} -al -go -ot -cn -upn -ok-',
- [0,32], [1,9,33], qr' infected (?:with|by)(?: virus)? (.*)\$'m ],
+ [0,32], [1,9,33], qr' infected (?:with|by)(?: virus)? (.*)$'m ],
- \#\## http://www.kaspersky.com/
+ ### http://www.kaspersky.com/
['Kaspersky Antivirus v5.5',
['/opt/kaspersky/kav4fs/bin/kav4fs-kavscanner',
'/opt/kav/5.5/kav4unix/bin/kavscanner',
'/opt/kav/5.5/kav4mailservers/bin/kavscanner', 'kavscanner'],
'-i0 -xn -xp -mn -R -ePASBME {}/*', [0,10,15], [5,20,21,25],
qr/(?:INFECTED|WARNING|SUSPICION|SUSPICIOUS) (.*)/m,
-# sub {chdir('/opt/kav/bin') or die "Can't chdir to kav: \$!"},
-# sub {chdir(\$TEMPBASE) or die "Can't chdir back to \$TEMPBASE \$!"},
+# sub {chdir('/opt/kav/bin') or die "Can't chdir to kav: $!"},
+# sub {chdir($TEMPBASE) or die "Can't chdir back to $TEMPBASE $!"},
],
# Commented out because the name 'sweep' clashes with Debian and FreeBSD
# package/port of an audio editor. Make sure the correct 'sweep' is found
# in the path when enabling.
#
-# \#\## http://www.sophos.com/ - backs up Sophie or SAVI-Perl
+# ### http://www.sophos.com/ - backs up Sophie or SAVI-Perl
# ['Sophos Anti Virus (sweep)', 'sweep',
# '-nb -f -all -rec -ss -sc -archive -cab -mime -oe -tnef '.
# '--no-reset-atime {}',
diff --git a/share/templates/amavisd.conf.tpl b/share/templates/amavisd.conf.tpl
--- a/share/templates/amavisd.conf.tpl
+++ b/share/templates/amavisd.conf.tpl
@@ -18,7 +18,7 @@
\$daemon_user = 'amavis'; # (no default; customary: vscan or amavis), -u
\$daemon_group = 'amavis'; # (no default; customary: vscan or amavis), -g
-\$mydomain = '$primary_domain'; # a convenient default for other settings
+\$mydomain = '{{ kolab.primary_domain }}'; # a convenient default for other settings
\$MYHOME = '/var/spool/amavisd'; # a convenient default for other settings, -H
\$TEMPBASE = "\$MYHOME/tmp"; # working directory, needs to exist, -T
@@ -373,7 +373,7 @@
# \#\## http://www.clamav.net/
['ClamAV-clamd',
- \&ask_daemon, ["CONTSCAN {}\n", "$clamdsock"],
+ \&ask_daemon, ["CONTSCAN {}\n", "{{ clamdsock }}"],
qr/\bOK\$/m, qr/\bFOUND\$/m,
qr/^.*?: (?!Infected Archive)(.*) FOUND\$/m ],
# # NOTE: run clamd under the same user as amavisd, or run it under its own
diff --git a/share/templates/freshclam.conf.tpl b/share/templates/freshclam.conf.tpl
--- a/share/templates/freshclam.conf.tpl
+++ b/share/templates/freshclam.conf.tpl
@@ -10,7 +10,7 @@
# Path to the log file (make sure it has proper permissions)
# Default: disabled
-UpdateLogFile /var/log/clamav/freshclam.log
+UpdateLogFile /var/log/freshclam.log
# Maximum size of the log file.
# Value of 0 disables the limit.
diff --git a/share/templates/master.cf.j2 b/share/templates/master.cf.j2
new file mode 100644
--- /dev/null
+++ b/share/templates/master.cf.j2
@@ -0,0 +1,122 @@
+# Postfix master process configuration file. For details on the format
+# of the file, see the master(5) manual page (command: "man 5 master").
+# Do not forget to execute "postfix reload" after editing this file.
+# ==============================================================================
+# service type private unpriv chroot wakeup maxproc command
+# (yes) (yes) (yes) (never) (100) + args
+# ==============================================================================
+smtp inet n - n - - smtpd
+#smtp inet n - n - 1 postscreen
+#smtpd pass - - n - - smtpd
+#dnsblog unix - - n - 0 dnsblog
+#tlsproxy unix - - n - 0 tlsproxy
+submission inet n - n - - smtpd
+ -o cleanup_service_name=cleanup_submission
+ -o syslog_name=postfix/submission
+ -o smtpd_tls_security_level=encrypt
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_sasl_authenticated_header=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o smtpd_data_restrictions=$submission_data_restrictions
+ -o smtpd_recipient_restrictions=$submission_recipient_restrictions
+ -o smtpd_sender_restrictions=$submission_sender_restrictions
+
+#smtps inet n - n - - smtpd
+# -o syslog_name=postfix/smtps
+# -o smtpd_tls_wrappermode=yes
+# -o smtpd_sasl_auth_enable=yes
+# -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+# -o milter_macro_daemon_name=ORIGINATING
+#628 inet n - n - - qmqpd
+pickup fifo n - n 60 1 pickup
+cleanup unix n - n - 0 cleanup
+ -o header_checks=regexp:/etc/postfix/header_checks.inbound
+ -o mime_header_checks=regexp:/etc/postfix/header_checks.inbound
+cleanup_internal unix n - n - 0 cleanup
+ -o header_checks=regexp:/etc/postfix/header_checks.internal
+ -o mime_header_checks=regexp:/etc/postfix/header_checks.internal
+cleanup_submission unix n - n - 0 cleanup
+ -o header_checks=regexp:/etc/postfix/header_checks.submission
+ -o mime_header_checks=regexp:/etc/postfix/header_checks.submission
+qmgr fifo n - n 300 1 qmgr
+#qmgr fifo n - n 300 1 oqmgr
+tlsmgr unix - - n 1000? 1 tlsmgr
+rewrite unix - - n - - trivial-rewrite
+bounce unix - - n - 0 bounce
+defer unix - - n - 0 bounce
+trace unix - - n - 0 bounce
+verify unix - - n - 1 verify
+flush unix n - n 1000? 0 flush
+proxymap unix - - n - - proxymap
+proxywrite unix - - n - 1 proxymap
+smtp unix - - n - - smtp
+relay unix - - n - - smtp
+showq unix n - n - - showq
+error unix - - n - - error
+retry unix - - n - - error
+discard unix - - n - - discard
+local unix - n n - - local
+virtual unix - n n - - virtual
+lmtp unix - - n - - lmtp
+anvil unix - - n - 1 anvil
+scache unix - - n - 1 scache
+
+# Filter email through Amavisd
+smtp-amavis unix - - n - 3 smtp
+ -o smtp_data_done_timeout=1800
+ -o disable_dns_lookups=yes
+ -o smtp_send_xforward_command=yes
+ -o max_use=20
+ -o smtp_bind_address=127.0.0.1
+
+# Listener to re-inject email from Amavisd into Postfix
+127.0.0.1:10025 inet n - n - 100 smtpd
+ -o cleanup_service_name=cleanup_internal
+ -o content_filter=smtp-wallace:[127.0.0.1]:10026
+ -o local_recipient_maps=
+ -o relay_recipient_maps=
+ -o smtpd_restriction_classes=
+ -o smtpd_client_restrictions=
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
+ -o smtpd_authorized_xforward_hosts=127.0.0.0/8
+
+# Filter email through Wallace
+smtp-wallace unix - - n - 3 smtp
+ -o default_destination_recipient_limit=1
+ -o smtp_data_done_timeout=1800
+ -o disable_dns_lookups=yes
+ -o smtp_send_xforward_command=yes
+ -o max_use=20
+
+# Listener to re-inject email from Wallace into Postfix
+127.0.0.1:10027 inet n - n - 100 smtpd
+ -o cleanup_service_name=cleanup_internal
+ -o content_filter=
+ -o local_recipient_maps=
+ -o relay_recipient_maps=
+ -o smtpd_restriction_classes=
+ -o smtpd_client_restrictions=
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
+ -o smtpd_authorized_xforward_hosts=127.0.0.0/8
+
+recipient_policy unix - n n - - spawn
+ user=kolab-n argv={{ kolab_sap_executable_path }} --verify-recipient
+
+recipient_policy_incoming unix - n n - - spawn
+ user=kolab-n argv={{ kolab_sap_executable_path }} --verify-recipient --allow-unauthenticated
+
+sender_policy unix - n n - - spawn
+ user=kolab-n argv={{ kolab_sap_executable_path }} --verify-sender
+
+sender_policy_incoming unix - n n - - spawn
+ user=kolab-n argv={{ kolab_sap_executable_path }} --verify-sender --allow-unauthenticated
+
+submission_policy unix - n n - - spawn
+ user=kolab-n argv={{ kolab_sap_executable_path }} --verify-sender --verify-recipient
+
diff --git a/templates/kolab-ad-domain.ldif.j2 b/templates/kolab-ad-domain.ldif.j2
new file mode 100644
--- /dev/null
+++ b/templates/kolab-ad-domain.ldif.j2
@@ -0,0 +1,13 @@
+dn: CN={{ domain }},{{ domain_base_dn }}
+changetype: add
+objectClass: top
+objectClass: domainRelatedObject
+objectClass: document
+cn: {{ domain }}
+distinguishedName: CN={{ domain }},{{ domain_base_dn }}
+instanceType: 4
+showInAdvancedViewOnly: TRUE
+name: {{ domain }}
+objectCategory: CN=document,CN=Schema,CN=Configuration,{{ base_dn }}
+associatedDomain: {{ domain }}
+
diff --git a/templates/kolab-ad-special-users.ldif.j2 b/templates/kolab-ad-special-users.ldif.j2
new file mode 100644
--- /dev/null
+++ b/templates/kolab-ad-special-users.ldif.j2
@@ -0,0 +1,50 @@
+dn: {{ cyrus_admin_dn }}
+changetype: add
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+objectClass: inetOrgPerson
+cn: Cyrus Admin
+sn: Administrator
+description: User for Cyrus IMAP to connect to LDAP
+givenName: Cyrus
+distinguishedName: {{ cyrus_admin_dn }}
+displayName: Cyrus Administrator
+name: Cyrus Admin
+codePage: 0
+countryCode: 0
+sAMAccountName: cyrus-admin
+userPrincipalName: cyrus-admin@{{ domain }}
+objectCategory: CN=Person,CN=Schema,CN=Configuration,{{ base_dn }}
+uid: cyrus-admin
+{%- if ad_ldaps %}
+unicodePwd:: {{ cyrus_admin_password_base64 }}
+userAccountControl: 66048
+{% endif %}
+
+dn: {{ service_bind_dn }}
+changetype: add
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: user
+objectClass: inetOrgPerson
+cn: Kolab Service
+sn: Service
+description: User for Kolab to connect to LDAP
+givenName: Kolab
+distinguishedName: {{ service_bind_dn }}
+displayName: Kolab Service
+name: Kolab Service
+codePage: 0
+countryCode: 0
+sAMAccountName: kolab-service
+userPrincipalName: kolab-service@{{ domain }}
+objectCategory: CN=Person,CN=Schema,CN=Configuration,{{ base_dn }}
+uid: kolab-service
+{%- if ad_ldaps %}
+unicodePwd:: {{ service_bind_pw_base64 }}
+userAccountControl: 66048
+{% endif %}
+
diff --git a/templates/kolab-ad-tree.ldif.j2 b/templates/kolab-ad-tree.ldif.j2
new file mode 100644
--- /dev/null
+++ b/templates/kolab-ad-tree.ldif.j2
@@ -0,0 +1,66 @@
+dn: {{ kolab_tree_dn }}
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Kolab
+description: LDAP tree part for Kolab related data
+distinguishedName: {{ kolab_tree_dn }}
+instanceType: 4
+name: Kolab
+objectCategory: CN=Organizational-Unit,CN=Schema,CN=Configuration,{{ base_dn }}
+
+dn: {{ domain_base_dn }}
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Domains
+description: Kolab hosted domains
+distinguishedName: {{ domain_base_dn }}
+instanceType: 4
+name: Domains
+objectCategory: CN=Organizational-Unit,CN=Schema,CN=Configuration,{{ base_dn }}
+
+dn: {{ group_base_dn }}
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Groups
+description: Kolab groups
+distinguishedName: {{ group_base_dn }}
+instanceType: 4
+name: Groups
+objectCategory: CN=Organizational-Unit,CN=Schema,CN=Configuration,{{ base_dn }}
+
+dn: {{ resource_base_dn }}
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Resources
+description: Kolab resources
+distinguishedName: {{ resource_base_dn }}
+instanceType: 4
+name: Resources
+objectCategory: CN=Organizational-Unit,CN=Schema,CN=Configuration,{{ base_dn }}
+
+dn: {{ sharedfolder_base_dn }}
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Shared Folders
+description: Kolab shared folders
+distinguishedName: {{ sharedfolder_base_dn }}
+instanceType: 4
+name: Shared Folders
+objectCategory: CN=Organizational-Unit,CN=Schema,CN=Configuration,{{ base_dn }}
+
+dn: {{ specialuser_base_dn }}
+changetype: add
+objectClass: top
+objectClass: organizationalUnit
+ou: Special Users
+description: Kolab special users
+distinguishedName: ${{ specialuser_base_dn }}
+instanceType: 4
+name: Special Users
+objectCategory: CN=Organizational-Unit,CN=Schema,CN=Configuration,{{ base_dn }}
+
diff --git a/templates/kolab-ds-setup.inf.j2 b/templates/kolab-ds-setup.inf.j2
new file mode 100644
--- /dev/null
+++ b/templates/kolab-ds-setup.inf.j2
@@ -0,0 +1,24 @@
+[General]
+FullMachineName = {{ fqdn }}
+SuiteSpotUserID = {{ userid }}
+SuiteSpotGroup = {{ group }}
+AdminDomain = {{ domain }}
+ConfigDirectoryLdapURL = ldap://{{ fqdn }}:389/o=NetscapeRoot
+ConfigDirectoryAdminID = admin
+ConfigDirectoryAdminPwd = {{ admin_pass }}
+
+[slapd]
+SlapdConfigForMC = Yes
+UseExistingMC = 0
+ServerPort = 389
+ServerIdentifier = {{ hostname }}
+Suffix = {{ base_dn }}
+RootDN = cn=Directory Manager
+RootDNPwd = {{ bind_pw }}
+ds_bename = {{ nodotdomain }}
+AddSampleEntries = No
+
+[admin]
+Port = 9830
+ServerAdminID = admin
+ServerAdminPwd = {{ admin_pass }}
diff --git a/templates/kolab3-ad-schema.ldif.j2 b/templates/kolab3-ad-schema.ldif.j2
new file mode 100644
--- /dev/null
+++ b/templates/kolab3-ad-schema.ldif.j2
@@ -0,0 +1,440 @@
+dn: CN=kolabAlias,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabAlias
+distinguishedName: CN=kolabAlias,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.2.1.3
+attributeSyntax: 2.5.5.5
+isSingleValued: FALSE
+rangeLower: 0
+rangeUpper: 256
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabAlias
+adminDescription: RFC1274: RFC822 Mailbox
+oMSyntax: 22
+lDAPDisplayName: kolabAlias
+name: kolabAlias
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabDelegate,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabDelegate
+distinguishedName: CN=kolabDelegate,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.1.1.1.3
+attributeSyntax: 2.5.5.5
+isSingleValued: FALSE
+rangeUpper: 256
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabDelegate
+adminDescription:
+ Kolab user allowed to act as delegates - RFC822 Mailbox/Alias
+oMSyntax: 22
+lDAPDisplayName: kolabDelegate
+name: kolabDelegate
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabInvitationPolicy,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabInvitationPolicy
+distinguishedName:
+ CN=kolabInvitationPolicy,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.1.1.1.4
+attributeSyntax: 2.5.5.5
+isSingleValued: FALSE
+rangeUpper: 256
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabInvitationPolicy
+adminDescription: Defines how to respond to invitations
+oMSyntax: 22
+lDAPDisplayName: kolabInvitationPolicy
+name: kolabInvitationPolicy
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabVacationBeginDateTime,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabVacationBeginDateTime
+distinguishedName:
+ CN=kolabVacationBeginDateTime,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.1.1.1.8
+attributeSyntax: 2.5.5.11
+isSingleValued: TRUE
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabVacationBeginDateTime
+adminDescription: Begin date of vacation
+oMSyntax: 24
+lDAPDisplayName: kolabVacationBeginDateTime
+name: kolabVacationBeginDateTime
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabVacationEndDateTime,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabVacationEndDateTime
+distinguishedName:
+ CN=kolabVacationEndDateTime,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.1.1.1.9
+attributeSyntax: 2.5.5.11
+isSingleValued: TRUE
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabVacationEndDateTime
+adminDescription: Specifies the end of vacation time
+oMSyntax: 24
+lDAPDisplayName: kolabVacationEndDateTime
+name: kolabVacationEndDateTime
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabVacationResendInterval,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabVacationResendInterval
+distinguishedName:
+ CN=kolabVacationResendInterval,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.1.1.1.10
+attributeSyntax: 2.5.5.9
+isSingleValued: TRUE
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabVacationResendInterval
+adminDescription: Vacation notice interval in days
+oMSyntax: 2
+lDAPDisplayName: kolabVacationResendInterval
+name: kolabVacationResendInterval
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabVacationAddress,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabVacationAddress
+distinguishedName:
+ CN=kolabVacationAddress,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.1.1.1.11
+attributeSyntax: 2.5.5.5
+isSingleValued: TRUE
+rangeUpper: 256
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabVacationAddress
+adminDescription: Email address for vacation to response upon
+oMSyntax: 22
+lDAPDisplayName: kolabVacationAddress
+name: kolabVacationAddress
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabForwardUCE,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabForwardUCE
+distinguishedName: CN=kolabForwardUCE,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.1.1.1.16
+attributeSyntax: 2.5.5.8
+isSingleValued: TRUE
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabForwardUCE
+adminDescription: Enable forwarding of mails known as UCE
+oMSyntax: 1
+lDAPDisplayName: kolabForwardUCE
+name: kolabForwardUCE
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabAllowSMTPRecipient,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabAllowSMTPRecipient
+distinguishedName:
+ CN=kolabAllowSMTPRecipient,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.1.1.1.18
+attributeSyntax: 2.5.5.5
+isSingleValued: FALSE
+rangeUpper: 512
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabAllowSMTPRecipient
+adminDescription: SMTP address allowed for destination
+oMSyntax: 22
+lDAPDisplayName: kolabAllowSMTPRecipient
+name: kolabAllowSMTPRecipient
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabAllowSMTPSender,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabAllowSMTPSender
+distinguishedName:
+ CN=kolabAllowSMTPSender,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.1.1.1.43
+attributeSyntax: 2.5.5.5
+isSingleValued: FALSE
+rangeUpper: 512
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabAllowSMTPSender
+adminDescription: SMTP envelope sender address accepted for delivery
+oMSyntax: 22
+lDAPDisplayName: kolabAllowSMTPSender
+name: kolabAllowSMTPSender
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabFolderType,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabFolderType
+distinguishedName: CN=kolabFolderType,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.2.1.7
+attributeSyntax: 2.5.5.5
+isSingleValued: TRUE
+rangeUpper: 256
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabFolderType
+adminDescription: Type of a kolab folder
+oMSyntax: 22
+lDAPDisplayName: kolabFolderType
+name: kolabFolderType
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabTargetFolder,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabTargetFolder
+distinguishedName:
+ CN=kolabTargetFolder,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.2.1.8
+attributeSyntax: 2.5.5.12
+isSingleValued: TRUE
+rangeUpper: 512
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabTargetFolder
+adminDescription: Target for a Kolab Shared Folder delivery
+oMSyntax: 64
+lDAPDisplayName: kolabTargetFolder
+name: kolabTargetFolder
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabImapACL,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabImapACL
+distinguishedName: CN=kolabImapACL,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.2.1.651
+attributeSyntax: 2.5.5.5
+isSingleValued: FALSE
+rangeUpper: 256
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabImapACL
+adminDescription: Cyrus IMAP access control lists
+oMSyntax: 22
+lDAPDisplayName: kolabImapACL
+name: kolabImapACL
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabDescAttribute,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabDescAttribute
+distinguishedName:
+ CN=kolabDescAttribute,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.3.1.1
+attributeSyntax: 2.5.5.5
+isSingleValued: TRUE
+rangeUpper: 256
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabDescAttribute
+adminDescription: Descriptive attribute or parameter for a Resource
+oMSyntax: 22
+lDAPDisplayName: kolabDescAttribute
+name: kolabDescAttribute
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabMailHost,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabMailHost
+distinguishedName: CN=kolabMailHost,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.3.1.2
+attributeSyntax: 2.5.5.4
+isSingleValued: TRUE
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabMailHost
+adminDescription::
+ TWFpbCBob3N0IHdoZXJlIEtvbGFiIHVzZXIsIHNoYXJlZCBmb2xkZXIgLyByZXNvdXJjZSByZXNpZG
+ VzIG9uIA==
+oMSyntax: 20
+lDAPDisplayName: kolabMailHost
+name: kolabMailHost
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabMailQuota,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: attributeSchema
+cn: kolabMailQuota
+distinguishedName: CN=kolabMailQuota,{{ ad_schema_naming_context }}
+instanceType: 4
+attributeID: 1.3.6.1.4.1.19414.3.1.3
+attributeSyntax: 2.5.5.4
+isSingleValued: TRUE
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabMailQuota
+adminDescription: Kolab mailbox quota limit.
+oMSyntax: 20
+lDAPDisplayName: kolabMailQuota
+name: kolabMailQuota
+objectCategory: CN=Attribute-Schema,{{ ad_schema_naming_context }}
+
+dn: CN=kolabSharedFolder,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: classSchema
+cn: kolabSharedFolder
+distinguishedName:
+ CN=kolabSharedFolder,{{ ad_schema_naming_context }}
+instanceType: 4
+subClassOf: top
+governsID: 1.3.6.1.4.1.19414.2.2.9
+mustContain: cn
+mayContain: owner
+mayContain: kolabImapAcl
+mayContain: kolabAlias
+mayContain: kolabAllowSMTPRecipient
+mayContain: kolabAllowSMTPSender
+mayContain: kolabDelegate
+mayContain: kolabFolderType
+mayContain: kolabMailHost
+mayContain: kolabTargetFolder
+rDNAttID: cn
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabSharedFolder
+adminDescription: Kolab public shared folder
+objectClassCategory: 3
+lDAPDisplayName: kolabSharedFolder
+name: kolabSharedFolder
+systemOnly: FALSE
+defaultSecurityDescriptor:
+ D:(A;;RPWPCRCCDCLCLOLORCWOWDSDDTDTSW;;;DA)(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)
+ (A;;RPLCLORC;;;AU)
+objectCategory: CN=Class-Schema,{{ ad_schema_naming_context }}
+defaultObjectCategory:
+ CN=kolabSharedFolder,{{ ad_schema_naming_context }}
+
+dn: CN=kolabInetOrgPerson,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: classSchema
+cn: kolabInetOrgPerson
+distinguishedName:
+ CN=kolabInetOrgPerson,{{ ad_schema_naming_context }}
+instanceType: 4
+subClassOf: top
+governsID: 1.3.6.1.4.1.19414.3.2.2
+mayContain: kolabAlias
+mayContain: kolabAllowSMTPRecipient
+mayContain: kolabAllowSMTPSender
+mayContain: kolabDelegate
+mayContain: kolabForwardUCE
+mayContain: kolabInvitationPolicy
+mayContain: kolabMailHost
+mayContain: kolabMailQuota
+mayContain: kolabVacationAddress
+mayContain: kolabVacationBeginDateTime
+mayContain: kolabVacationEndDateTime
+mayContain: kolabVacationResendInterval
+rDNAttID: cn
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabInetOrgPerson
+adminDescription: Kolab Internet Organizational Person
+objectClassCategory: 3
+lDAPDisplayName: kolabInetOrgPerson
+name: kolabInetOrgPerson
+systemOnly: FALSE
+defaultSecurityDescriptor:
+ D:(A;;RPWPCRCCDCLCLOLORCWOWDSDDTDTSW;;;DA)(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)
+ (A;;RPLCLORC;;;AU)
+objectCategory: CN=Class-Schema,{{ ad_schema_naming_context }}
+defaultObjectCategory:
+ CN=kolabInetOrgPerson,{{ ad_schema_naming_context }}
+
+dn: CN=kolabGroupOfUniqueNames,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: classSchema
+cn: kolabGroupOfUniqueNames
+distinguishedName:
+ CN=kolabGroupOfUniqueNames,{{ ad_schema_naming_context }}
+instanceType: 4
+subClassOf: top
+governsID: 1.3.6.1.4.1.19414.3.2.8
+mayContain: kolabAlias
+mayContain: kolabAllowSMTPRecipient
+mayContain: kolabAllowSMTPSender
+mayContain: kolabDelegate
+rDNAttID: cn
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabGroupOfUniqueNames
+adminDescription: Kolab group of names (DNs) derived from RFC2256
+objectClassCategory: 3
+lDAPDisplayName: kolabGroupOfUniqueNames
+name: kolabGroupOfUniqueNames
+systemOnly: FALSE
+defaultSecurityDescriptor:
+ D:(A;;RPWPCRCCDCLCLOLORCWOWDSDDTDTSW;;;DA)(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)
+ (A;;RPLCLORC;;;AU)
+objectCategory: CN=Class-Schema,{{ ad_schema_naming_context }}
+defaultObjectCategory:
+ CN=kolabGroupOfUniqueNames,{{ ad_schema_naming_context }}
+
+dn: CN=kolabResource,{{ ad_schema_naming_context }}
+changetype: add
+objectClass: top
+objectClass: classSchema
+cn: kolabResource
+distinguishedName: CN=kolabResource,{{ ad_schema_naming_context }}
+instanceType: 4
+subClassOf: top
+governsID: 1.3.6.1.4.1.19414.3.2.9
+mayContain: description
+mayContain: kolabDescAttribute
+mayContain: kolabInvitationPolicy
+mayContain: owner
+rDNAttID: cn
+showInAdvancedViewOnly: TRUE
+adminDisplayName: kolabResource
+adminDescription: Kolab resource
+objectClassCategory: 3
+lDAPDisplayName: kolabResource
+name: kolabResource
+systemOnly: FALSE
+defaultSecurityDescriptor:
+ D:(A;;RPWPCRCCDCLCLOLORCWOWDSDDTDTSW;;;DA)(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)
+ (A;;RPLCLORC;;;AU)
+objectCategory: CN=Class-Schema,{{ ad_schema_naming_context }}
+defaultObjectCategory:
+ CN=kolabResource,{{ ad_schema_naming_context }}
+-
diff --git a/templates/local_recipient_maps.cf.j2 b/templates/local_recipient_maps.cf.j2
new file mode 100644
--- /dev/null
+++ b/templates/local_recipient_maps.cf.j2
@@ -0,0 +1,26 @@
+server_host = {{ ldap.ldap_uri }}
+version = 3
+search_base = {{ ldap.base_dn }}
+scope = sub
+
+domain = ldap:/etc/postfix/ldap/mydestination.cf
+
+bind_dn = {{ ldap.service_bind_dn }}
+bind_pw = {{ ldap.service_bind_pw }}
+
+query_filter =
+ (&
+ (|
+ {% for item in ldap.mail_attributes.split(',') -%}
+ ({{ item | trim }}=%s)
+ {%- endfor %}
+ )
+ (|
+ {{ ldap.kolab_user_filter }}
+ {{ ldap.kolab_group_filter }}
+ {{ ldap.resource_filter }}
+ {{ ldap.sharedfolder_filter }}
+ )
+ )
+
+result_attribute = mail
diff --git a/templates/mailenabled_distgroups.cf.j2 b/templates/mailenabled_distgroups.cf.j2
new file mode 100644
--- /dev/null
+++ b/templates/mailenabled_distgroups.cf.j2
@@ -0,0 +1,30 @@
+server_host = {{ ldap.ldap_uri }}
+server_port = 389
+version = 3
+search_base = {{ ldap.group_base_dn }}
+scope = sub
+
+domain = ldap:/etc/postfix/ldap/mydestination.cf
+
+bind_dn = {{ ldap.service_bind_dn }}
+bind_pw = {{ ldap.service_bind_pw }}
+
+# This finds the mail enabled distribution group LDAP entry
+query_filter =
+ (&
+ (|
+ {% for item in ldap.mail_attributes.split(',') -%}
+ ({{ item | trim }}=%s)
+ {%- endfor %}
+ )
+ (objectClass=kolabgroupofuniquenames)
+ (objectclass={{ ldap.group_objectclass }})
+ (!(objectclass=groupofurls))
+ )
+
+# From this type of group, get all uniqueMember DNs
+special_result_attribute = {{ ldap.group_member_attr }}
+
+# Only from those DNs, get the mail
+result_attribute =
+leaf_result_attribute = mail
diff --git a/templates/mailenabled_dynamic_distgroups.cf.j2 b/templates/mailenabled_dynamic_distgroups.cf.j2
new file mode 100644
--- /dev/null
+++ b/templates/mailenabled_dynamic_distgroups.cf.j2
@@ -0,0 +1,28 @@
+server_host = {{ ldap.ldap_uri }}
+version = 3
+search_base = {{ ldap.group_base_dn }}
+scope = sub
+
+domain = ldap:/etc/postfix/ldap/mydestination.cf
+
+bind_dn = {{ ldap.service_bind_dn }}
+bind_pw = {{ ldap.service_bind_pw }}
+
+# This finds the mail enabled dynamic distribution group LDAP entry
+query_filter =
+ (&
+ (|
+ {% for item in ldap.mail_attributes.split(',') -%}
+ ({{ item | trim }}=%s)
+ {%- endfor %}
+ )
+ (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
diff --git a/templates/mydestination.cf.j2 b/templates/mydestination.cf.j2
new file mode 100644
--- /dev/null
+++ b/templates/mydestination.cf.j2
@@ -0,0 +1,10 @@
+server_host = {{ ldap.ldap_uri }}
+version = 3
+search_base = {{ ldap.domain_base_dn }}
+scope = sub
+
+bind_dn = {{ ldap.service_bind_dn }}
+bind_pw = {{ ldap.service_bind_pw }}
+
+query_filter = {{ ldap.domain_filter | replace('*', '%s') }}
+result_attribute = {{ ldap.domain_name_attribute }}
diff --git a/templates/transport_maps.cf.j2 b/templates/transport_maps.cf.j2
new file mode 100644
--- /dev/null
+++ b/templates/transport_maps.cf.j2
@@ -0,0 +1,23 @@
+server_host = {{ ldap.ldap_uri }}
+version = 3
+search_base = {{ ldap.base_dn }}
+scope = sub
+
+domain = ldap:/etc/postfix/ldap/mydestination.cf
+
+bind_dn = {{ ldap.service_bind_dn }}
+bind_pw = {{ ldap.service_bind_pw }}
+
+query_filter =
+ (&
+ (|
+ (mailAlternateAddress=%%s)
+ {% for item in ldap.mail_attributes.split(',') -%}
+ ({{ item | trim }}=%s)
+ {%- endfor %}
+ )
+ (objectclass=kolabinetorgperson)
+ )
+
+result_attribute = mail
+result_format = lmtp:unix:/var/lib/imap/socket/lmtp
diff --git a/templates/virtual_alias_maps.cf.j2 b/templates/virtual_alias_maps.cf.j2
new file mode 100644
--- /dev/null
+++ b/templates/virtual_alias_maps.cf.j2
@@ -0,0 +1,20 @@
+server_host = {{ ldap.ldap_uri }}
+version = 3
+search_base = {{ ldap.base_dn }}
+scope = sub
+
+domain = ldap:/etc/postfix/ldap/mydestination.cf
+
+bind_dn = {{ ldap.service_bind_dn }}
+bind_pw = {{ ldap.service_bind_pw }}
+
+query_filter =
+ (&
+ (|
+ {% for item in ldap.mail_attributes.split(',') -%}
+ ({{ item | trim }}=%s)
+ {%- endfor %}
+ )
+ (objectclass=kolabinetorgperson)
+ )
+result_attribute = mail
diff --git a/templates/virtual_alias_maps_mailforwarding.cf.j2 b/templates/virtual_alias_maps_mailforwarding.cf.j2
new file mode 100644
--- /dev/null
+++ b/templates/virtual_alias_maps_mailforwarding.cf.j2
@@ -0,0 +1,23 @@
+server_host = {{ ldap.ldap_uri }}
+version = 3
+search_base = {{ ldap.base_dn }}
+scope = sub
+
+domain = ldap:/etc/postfix/ldap/mydestination.cf
+
+bind_dn = {{ ldap.service_bind_dn }}
+bind_pw = {{ ldap.service_bind_pw }}
+
+query_filter =
+ (&
+ (|
+ {% for item in ldap.mail_attributes.split(',') -%}
+ ({{ item | trim }}=%s)
+ {%- endfor %}
+ )
+ (objectclass=mailRecipient)
+ (objectclass=inetOrgPerson)
+ (mailforwardingaddress=*)
+ )
+
+result_attribute = mailForwardingAddress
diff --git a/templates/virtual_alias_maps_sharedfolders.cf.j2 b/templates/virtual_alias_maps_sharedfolders.cf.j2
new file mode 100644
--- /dev/null
+++ b/templates/virtual_alias_maps_sharedfolders.cf.j2
@@ -0,0 +1,23 @@
+server_host = {{ ldap.ldap_uri }}
+version = 3
+search_base = {{ ldap.base_dn }}
+scope = sub
+
+domain = ldap:/etc/postfix/ldap/mydestination.cf
+
+bind_dn = {{ ldap.service_bind_dn }}
+bind_pw = {{ ldap.service_bind_pw }}
+
+query_filter =
+ (&
+ (|
+ {% for item in ldap.mail_attributes.split(',') -%}
+ ({{ item | trim }}=%s)
+ {%- endfor %}
+ )
+ (objectclass=kolabsharedfolder)
+ (kolabFolderType=mail)
+ )
+
+result_attribute = kolabtargetfolder
+result_format = "shared+%s"