diff --git a/README.rst b/README.rst index b1f8e41..0410cc9 100644 --- a/README.rst +++ b/README.rst @@ -23,7 +23,7 @@ Installs and starts postfix SMTP server ``postfix.config`` ------------------ -Manages postfix main.cf configuration file +Manages postfix main.cf and optionally the master.cf configuration file ``postfix.policyd-spf`` ------------------ diff --git a/pillar.example b/pillar.example index a3bfdee..e00a93f 100644 --- a/pillar.example +++ b/pillar.example @@ -16,6 +16,25 @@ postfix: smtpd_sasl_auth_enable: yes smtpd_client_restrictions: permit_sasl_authenticated,reject + # Alternative way of managing services allowing for much more finegrained + # control over each service. See postfix/services.jinja for details. + services: + smtp: + # Limit to no more than 10 smtp processes + maxproc: 10 + # Enable oldstyle TLS wrapped SMTP + smtps: + enable: True + submission: + enable: True + args: + - "-o smtpd_tls_security_level=encrypt" + - "-o smtpd_sasl_auth_enable=yes" + - "-o smtpd_client_restrictions: permit_sasl_authenticated,reject" + tlsproxy: + enable: True + chroot: True + enable_service: True postgrey: diff --git a/postfix/files/master.cf b/postfix/files/master.cf index 5b69be5..0520ed1 100644 --- a/postfix/files/master.cf +++ b/postfix/files/master.cf @@ -1,15 +1,66 @@ {%- from "postfix/map.jinja" import postfix with context -%} +{%- set master_config = salt['pillar.get']('postfix:master_config', {}) -%} +{%- from "postfix/services.jinja" import postfix_master_services_defaults, postfix_master_services_order -%} -{%- macro set_option(parameter, value) -%} - {%- if value is number or value is string -%} --o {{ parameter }}={{ value }} - {%- elif value is iterable -%} --o {{ parameter }}={{ value | join(', ')}} +{#- + # Handle the case that the pillar data does not provide any service + # configuration but submission parameters are provided in the pillar.. + # This is important for backwards compatibility with sites that are using + # the previous enable_submission pillar settings. + -#} +{%- set additional_services = {} -%} +{%- if master_config.get('enable_submission', False) and not salt[ + 'pillar.get']('postfix:master_config:services:submission', False) -%} +{%- do additional_services.update({'submission': {'chroot': False, + 'command': 'smtpd', + 'enable': True, + 'type': 'inet', + 'args': [], + 'private': False}}) -%} +{%- if master_config.get('submission', False) -%} +{%- for parameter, value in master_config.get('submission', {}).items() -%} +{%- if value is number or value is string -%} +{%- do additional_services['submission']['args'].append('-o %s=%s' % ( + parameter, value)) -%} +{%- elif value is iterable -%} +{%- do additional_services['submission']['args'].append('-o %s=%s' % ( + parameter, ', '.join(value))) -%} +{%- endif -%} +{%- endfor -%} +{%- else -%} +{%- do additional_services[ + 'submission']['args'].extend(['# -o syslog_name=postfix/submission', + '-o smtpd_tls_security_level=encrypt', + '-o smtpd_sasl_auth_enable=yes', + '# -o smtpd_reject_unlisted_recipient=no', + '# -o smtpd_client_restrictions=$mua_client_restrictions', + '# -o smtpd_helo_restrictions=$mua_helo_restrictions', + '# -o smtpd_sender_restrictions=$mua_sender_restrictions', + '# -o smtpd_recipient_restrictions=', + '# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject', + '# -o milter_macro_daemon_name=ORIGINATING' + ]) -%} +{%- endif -%} +{%- endif -%} + +{#- Format the postfix service parameters correctly -#} +{%- macro service_param(service, service_name, parameter_name, default='-') -%} + {#- Fetch the value from the passed service dictionary or fall back to the + # service defaults by chaining .get() commands. #} + {%- set value = service.get(parameter_name, + postfix_master_services_defaults[service_name].get( + parameter_name, default)) -%} + {%- if value is sameas false -%} +n + {%- elif value is sameas true -%} +y + {%- elif value is number or value is string -%} +{{ value }} + {%- else -%} +- {%- endif -%} {%- endmacro -%} -{% set master_config = salt['pillar.get']('postfix:master_config', {}) -%} - # # Postfix master process configuration file. For details on the format # of the file, see the master(5) manual page (command: "man 5 master" or @@ -21,67 +72,40 @@ # service type private unpriv chroot wakeup maxproc command + args # (yes) (yes) (no) (never) (100) # ========================================================================== -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 -{%- if master_config.get('enable_submission', False) %} -submission inet n - n - - smtpd -{%- if master_config.get('submission', False) -%} -{% for parameter, value in master_config.get('submission', {}).items() %} - {{ set_option(parameter, value) }} -{%- endfor -%} -{% else %} -# -o syslog_name=postfix/submission - -o smtpd_tls_security_level=encrypt - -o smtpd_sasl_auth_enable=yes -{% endif %} -# -o smtpd_reject_unlisted_recipient=no -# -o smtpd_client_restrictions=$mua_client_restrictions -# -o smtpd_helo_restrictions=$mua_helo_restrictions -# -o smtpd_sender_restrictions=$mua_sender_restrictions -# -o smtpd_recipient_restrictions= -# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -# -o milter_macro_daemon_name=ORIGINATING -{% endif -%} -#smtps inet n - n - - smtpd -# -o syslog_name=postfix/smtps -# -o smtpd_tls_wrappermode=yes -# -o smtpd_sasl_auth_enable=yes -# -o smtpd_reject_unlisted_recipient=no -# -o smtpd_client_restrictions=$mua_client_restrictions -# -o smtpd_helo_restrictions=$mua_helo_restrictions -# -o smtpd_sender_restrictions=$mua_sender_restrictions -# -o smtpd_recipient_restrictions= -# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -# -o milter_macro_daemon_name=ORIGINATING -#628 inet n - n - - qmqpd -pickup unix n - n 60 1 pickup -cleanup unix n - n - 0 cleanup -qmgr unix n - n 300 1 qmgr -#qmgr unix 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 -# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 -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 +{%- for service_name in postfix_master_services_order %} +{#- Try to get the service configuration from the pillar if present. + # Next try if the service has been dynamically configured and is present in + # the additional_services dictionary. + # If absent, fall back to the defaults provided in services.jinja -#} +{%- set service = salt['pillar.get']('postfix:master_config:services:%s' % ( + service_name,), + additional_services.get(service_name, + postfix_master_services_defaults[service_name])) -%} +{%- if service.get('enable', True) -%} +{%- set comment = '' -%} +{%- else -%} +{%- set comment = '#' -%} +{%- endif %} +{{ "%s%-9s %-5s %-7s %-7s %-7s %-7s %-7s %s" | format(comment, + service_param(service, service_name, 'service', service_name), + service_param(service, service_name, 'type'), + service_param(service, service_name, 'private'), + service_param(service, service_name, 'unpriv'), + service_param(service, service_name, 'chroot'), + service_param(service, service_name, 'wakeup'), + service_param(service, service_name, 'maxproc'), + service_param(service, service_name, 'command', service_name)) -}} +{%- if service.args is not none -%} +{%- for option in service.get('args', postfix_master_services_defaults[ + service_name].get('args', [])) -%} +{%- if option.startswith('#') %} +{{ option }} +{%- else %} +{{ comment }} {{ option }} +{%- endif %} +{%- endfor %} +{%- endif %} +{%- endfor %} # # ==================================================================== # Interfaces to non-Postfix software. Be sure to examine the manual diff --git a/postfix/services.jinja b/postfix/services.jinja new file mode 100644 index 0000000..8bb1689 --- /dev/null +++ b/postfix/services.jinja @@ -0,0 +1,261 @@ +{#- + # Default Postfix master processes as defined by postfix + # + # The dictionary is keyed off the service name ("smtp", "smtpd", etc.) except + # for the few cases the service name is repeated to illustrate alternative + # options in the file. + # In such a case the second entry has a unique identifier appended, e.g. + # "smtp-unix". The 'service' attribute is used to provide the service name + # that will be rendered by jinja thus overriding the usual key to prevent + # clashes. +-#} + +{% set postfix_master_services_defaults = { + 'smtp': { + 'type': 'inet', + 'private': False, + 'chroot': False, + 'command': 'smtpd' + }, + 'smtp-postscreen': { + 'service': 'smtp', + 'enable': False, + 'type': 'inet', + 'maxproc': 1, + 'private': False, + 'chroot': False, + 'command': 'postscreen' + }, + 'smtpd': { + 'enable': False, + 'type': 'pass', + 'chroot': False + }, + 'dnsblog': { + 'maxproc': 0, + 'enable': False, + 'type': 'unix', + 'chroot': False + }, + 'tlsproxy': { + 'maxproc': 0, + 'enable': False, + 'type': 'unix', + 'chroot': False + }, + 'submission': { + 'chroot': False, + 'command': 'smtpd', + 'enable': False, + 'args': [ + '-o syslog_name=postfix/submission', + '-o smtpd_tls_security_level=encrypt', + '-o smtpd_sasl_auth_enable=yes', + '-o smtpd_reject_unlisted_recipient=no', + '-o smtpd_client_restrictions=$mua_client_restrictions', + '-o smtpd_helo_restrictions=$mua_helo_restrictions', + '-o smtpd_sender_restrictions=$mua_sender_restrictions', + '-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject', + '-o milter_macro_daemon_name=ORIGINATING' + ], + 'type': 'inet', + 'private': False + }, + 'smtps': { + 'chroot': False, + 'command': 'smtpd', + 'enable': False, + 'args': [ + '-o syslog_name=postfix/smtps', + '-o smtpd_tls_wrappermode=yes', + '-o smtpd_sasl_auth_enable=yes', + '-o smtpd_reject_unlisted_recipient=no', + '-o smtpd_client_restrictions=$mua_client_restrictions', + '-o smtpd_helo_restrictions=$mua_helo_restrictions', + '-o smtpd_sender_restrictions=$mua_sender_restrictions', + '-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject', + '-o milter_macro_daemon_name=ORIGINATING' + ], + 'type': 'inet', + 'private': False + }, + '628': { + 'enable': False, + 'command': 'qmqpd', + 'type': 'inet', + 'chroot': False, + 'private': False + }, + 'pickup': { + 'maxproc': 1, + 'chroot': False, + 'wakeup': 60, + 'type': 'unix', + 'private': False, + }, + 'cleanup': { + 'maxproc': 0, + 'chroot': False, + 'type': 'unix', + 'private': False + }, + 'qmgr': { + 'chroot': False, + 'private': False, + 'maxproc': 1, + 'wakeup': 300, + 'type': 'unix' + }, + 'qmgr-oqmgr': { + 'service': 'qmgr', + 'chroot': False, + 'private': False, + 'maxproc': 1, + 'enable': False, + 'command': 'oqmgr', + 'wakeup': 300, + 'type': 'unix' + }, + 'tlsmgr': { + 'wakeup': '1000?', + 'chroot': False, + 'type': 'unix', + 'maxproc': 1 + }, + 'rewrite': { + 'type': 'unix', + 'chroot': False, + 'command': 'trivial-rewrite' + }, + 'bounce': { + 'maxproc': 0, + 'chroot': False, + 'type': 'unix' + }, + 'defer': { + 'maxproc': 0, + 'chroot': False, + 'type': 'unix', + 'command': 'bounce' + }, + 'trace': { + 'maxproc': 0, + 'chroot': False, + 'type': 'unix', + 'command': 'bounce' + }, + 'smtp-unix': { + 'service': 'smtp', + 'chroot': False, + 'type': 'unix', + 'command': 'smtp' + }, + 'verify': { + 'maxproc': 1, + 'chroot': False, + 'type': 'unix' + }, + 'flush': { + 'maxproc': 0, + 'chroot': False, + 'wakeup': '1000?', + 'type': 'unix', + 'private': False + }, + 'proxymap': { + 'chroot': False, + 'type': 'unix' + }, + 'proxywrite': { + 'maxproc': 1, + 'chroot': False, + 'type': 'unix', + 'command': 'proxymap' + }, + 'relay': { + 'type': 'unix', + 'chroot': False, + 'args': [ + '# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5' + ], + 'command': 'smtp' + }, + 'showq': { + 'chroot': False, + 'type': 'unix', + 'private': False + }, + 'error': { + 'chroot': False, + 'type': 'unix' + }, + 'retry': { + 'type': 'unix', + 'chroot': False, + 'command': 'error' + }, + 'discard': { + 'chroot': False, + 'type': 'unix' + }, + 'local': { + 'unpriv': False, + 'chroot': False, + 'type': 'unix' + }, + 'virtual': { + 'unpriv': False, + 'chroot': False, + 'type': 'unix' + }, + 'lmtp': { + 'chroot': False, + 'type': 'unix' + }, + 'anvil': { + 'maxproc': 1, + 'chroot': False, + 'type': 'unix' + }, + 'scache': { + 'maxproc': 1, + 'chroot': False, + 'type': 'unix' + } +} %} + +{# Service order inside the master.cf file #} +{% set postfix_master_services_order = [ + 'smtp', + 'smtp-postscreen', + 'smtpd', + 'dnsblog', + 'tlsproxy', + 'submission', + 'smtps', + '628', + 'pickup', + 'cleanup', + 'qmgr', + 'qmgr-oqmgr', + 'tlsmgr', + 'rewrite', + 'bounce', + 'defer', + 'trace', + 'verify', + 'flush', + 'proxymap', + 'proxywrite', + 'smtp-unix', + 'relay', + 'showq', + 'error', + 'retry', + 'discard', + 'local', + 'virtual', + 'lmtp', + 'anvil', + 'scache' +] %}