diff --git a/scripts/run-shellcheck.sh b/scripts/run-shellcheck.sh index 475f426..d6c2db6 100755 --- a/scripts/run-shellcheck.sh +++ b/scripts/run-shellcheck.sh @@ -1,21 +1,29 @@ -#!/bin/sh +#!/bin/sh -eu -SHELLCHECKCMD="shellcheck -s sh -f gcc -x" +SHELLCHECKCMD='shellcheck -s sh -f gcc -x' # Skip SC2154 for variables starting with __ since such variables are cdist # environment variables. SHELLCHECK_SKIP=': __.*is referenced but not assigned.*\[SC2154\]' -SHELLCHECKTMP=".shellcheck.tmp" +SHELLCHECKTMP='.shellcheck.tmp' # Move to top-level cdist-contrib directory. -cd $(dirname $0)/.. +cd "$(dirname $0)"/.. -check () { - find type/ -type f $1 $2 -exec ${SHELLCHECKCMD} {} + | grep -v "${SHELLCHECK_SKIP}" > "${SHELLCHECKTMP}" - test ! -s "${SHELLCHECKTMP}" || { cat "${SHELLCHECKTMP}"; exit 1; } +check() { + find type/ -type f "$@" -exec ${SHELLCHECKCMD} {} + \ + | grep -v "${SHELLCHECK_SKIP}" >>"${SHELLCHECKTMP}" || true } -check -path "*/explorer/*" -check -path "*/files/*.sh" +rm -f "${SHELLCHECKTMP}" + +check -path '*/explorer/*' +check -path '*/files/*' -name '*.sh' check -name manifest check -name gencode-local check -name gencode-remote + +if test -s "${SHELLCHECKTMP}" +then + cat "${SHELLCHECKTMP}" >&2 + exit 1 +fi diff --git a/type/__dma/explorer/auth_conf b/type/__dma/explorer/auth_conf new file mode 100755 index 0000000..cef0aca --- /dev/null +++ b/type/__dma/explorer/auth_conf @@ -0,0 +1,49 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# This explorer determines the path of dma's auth.conf file + +# No dma.conf -> use default +test -f /etc/dma/dma.conf || { + echo /etc/dma/auth.conf + exit 0 +} +test -r /etc/dma/dma.conf || { + echo 'Cannot read /etc/dma/dma.conf' >&2 + exit 1 +} + +# Get AUTHPATH from dma.conf +awk -F'[ \t]' ' +{ + sub(/#.*$/, "", $0) # remove comments + if (!$0) next # ignore empty lines +} +$1 == "AUTHPATH" { + # Store authpath. In dma conf parsing last wins. + if ($2) authpath = substr($0, index($0, " ") + 1) +} +END { + if (authpath) { + print authpath + exit 0 + } else exit 1 +} +' /etc/dma/dma.conf \ +|| echo /etc/dma/auth.conf # default diff --git a/type/__dma/explorer/conf b/type/__dma/explorer/conf new file mode 100755 index 0000000..b4d6d26 --- /dev/null +++ b/type/__dma/explorer/conf @@ -0,0 +1,34 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# This explorer returns a sorted list of "active" (= non-commented) lines +# in the dma.conf file. +# "Trailing" line comments are stripped off. +# +# NOTE: This explorer assumes that the sort(1) utility supports the non-POXIX +# -s (stable sort) option. + +CONF_PATH=/etc/dma # set in Makefile +dma_conf="${CONF_PATH:?}/dma.conf" + +test -f "${dma_conf}" || exit 0 + +grep -v -e '^[ \t]*#\|^$' "${dma_conf}" \ +| sed -e 's/[ \t]*#.*$//' \ +| sort -s -k 1,1 diff --git a/type/__dma/files/update_dma_conf.awk b/type/__dma/files/update_dma_conf.awk new file mode 100644 index 0000000..15ef7bf --- /dev/null +++ b/type/__dma/files/update_dma_conf.awk @@ -0,0 +1,178 @@ +#!/usr/bin/awk -f +# +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . + +function comment_line(line) { + # returns the position in line at which the comment's text starts + # (0 if the line is not a comment) + match(line, /^[ \t]*\#+[ \t]*/) + return RSTART ? (RLENGTH + 1) : 0 +} +function empty_line(line) { return line ~ /^[ \t]*$/ } +function is_word(s) { return s ~ /^[A-Z_]+$/ } # "looks like a plausible word" + +function first(line, sep_re) { + # returns the part of the line until sep is found + # (or the whole line if sep is not found) + if (!sep_re) sep_re = "[" SUBSEP "]" + match(line, sep_re) + return RSTART ? substr(line, 1, RSTART - 1) : line +} + +function rest(line, sep_re) { + # returns the part of the line after the first occurrence of sep is found. + # (or nothing if sep is not found) + if (!sep_re) sep_re = "[" SUBSEP "]" + if (match(line, sep_re)) + return substr(line, RSTART + RLENGTH) +} + +function conf_pop(word, value) { + # returns the next value for the config `word` and delete it from the list. + # if value is set, this function will only return value if it is the first + # option in the list, otherwise it returns 0. + + if (!(word in conf)) return 0 + if (!value) { + if (index(conf[word], SUBSEP)) # more than one element? + value = substr(conf[word], 1, index(conf[word], SUBSEP) - 1) + else + value = conf[word] + } + + if (index(conf[word], SUBSEP)) { + if (index(conf[word], value SUBSEP) != 1) return 0 + conf[word] = substr(conf[word], length(value) + 2) + } else { + if (conf[word] != value) return 0 + delete conf[word] + } + return value +} + +function print_conf(word, value) { + # print a config line with the given parameters + printf "%s", word + if (value) printf " %s", value + printf "\n" +} + +function print_confs(word, value) { + # print config lines for all values stored in conf[word]. + if (!(word in conf)) return + if (conf[word]) { + while (value = conf_pop(word)) + print_conf(word, value) + } else { + print_conf(word) + delete conf[word] + } +} + +BEGIN { + FS = "\n" + EQS = "[ \t]" # copied from dma/conf.c + + if (ARGV[2]) exit (e=1) + + # Loop over file twice! + ARGV[2] = ARGV[1] + ARGC++ + + # read the "should" state into the `conf` array. + while (getline < "/dev/stdin") { + word = first($0, EQS) + if ((word in conf)) + conf[word] = conf[word] SUBSEP rest($0, EQS) + else + conf[word] = rest($0, EQS) + } +} + +# first pass, gather information about where which information is stored in the +# current config file. This information will be used in the second pass. +NR == FNR { + if (comment_line($0)) { + # comment line + word = first(substr($0, comment_line($0)), " ") + if (is_word(word)) last_occ["#" word] = FNR + } else { + word = first($0, EQS) + if (is_word(word)) last_occ[word] = FNR + } +} + +# before second pass prepare hashes containing location information to be used +# in the second pass. +NR > FNR && FNR == 1 { + # First we drop the locations of commented-out options if a non-commented + # option is available. If a non-commented option is available, we will + # append new config options there to have them all at one place. + for (k in last_occ) + if (k ~ /^\#/ && (substr(k, 2) in last_occ)) + delete last_occ[k] + + # Reverse the option => line mapping. The line_map allows for easier lookups + # in the second pass. + for (k in last_occ) line_map[last_occ[k]] = k +} + +# second pass, generate and output new config +NR > FNR { + if (comment_line($0) || empty_line($0)) { + # comment or empty line + print + + if ((FNR in line_map)) { + if (line_map[FNR] ~ /^\#/) { + # This line contains a commented config option. If the conf hash + # contains options to be set, we output them here because this + # option is not used in the current config. + k = substr(line_map[FNR], 2) + if ((k in conf)) print_confs(k) + } + + if (("INSECURE" in conf) && line_map[FNR] ~ /^\#?SECURE$/) { + # INSECURE goes where SECURE comment is. + print_confs("INSECURE") + } + } + } else { + word = first($0, EQS) + value = rest($0, EQS) + sub(/[ \t]*\#.*$/, "", value) # ignore comments in value + + if ((word in conf) && value == first(conf[word])) { + # keep config options we want + conf_pop(word) + print + } + + if ((FNR in line_map) && line_map[FNR] == word) { + # rest of config options should be here + print_confs(word) + } + } +} + +END { + if (e) exit + + # print rest of config options ( + for (word in conf) print_confs(word) +} diff --git a/type/__dma/gencode-remote b/type/__dma/gencode-remote new file mode 100755 index 0000000..580b22e --- /dev/null +++ b/type/__dma/gencode-remote @@ -0,0 +1,177 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera@ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# + +quote() { printf "'%s'" "$(printf '%s' "$*" | sed -e "s/'/'\\\\''/g")"; } +drop_awk_comments() { quote "$(sed '/^[[:blank:]]*#.*$/d;/^$/d' "$@")"; } + +CONF_PATH=/etc/dma # set in Makefile + +# Determine mailname +if test -f "${__object:?}/parameter/mailname" +then + mailname=$(cat "${__object:?}/parameter/mailname") +else + case $(cat "${__global:?}/explorer/os") + in + (debian|devuan|ubuntu) + # On Debian-like systems use /etc/mailname unless --mailname is used + mailname='/etc/mailname' + ;; + (*) + mailname=${__target_fqdn:?} + ;; + esac +fi + + +# Generate "should" values for config +conf_should=$( + if test -s "${__object:?}/parameter/smarthost" + then + printf 'SMARTHOST %s\n' "$(cat "${__object:?}/parameter/smarthost")" + fi + + printf 'MAILNAME %s\n' "${mailname}" + + if test -s "${__object:?}/explorer/auth_conf" + then + printf "AUTHPATH %s\n" "$(cat "${__object:?}/explorer/auth_conf")" + fi + + case $(cat "${__object:?}/parameter/security") + in + (ssl|tls) + default_smtp_port=465 + echo 'SECURETRANSFER' + ;; + (starttls) + default_smtp_port=587 + echo 'SECURETRANSFER' + echo 'STARTTLS' + ;; + (opportunistic) + default_smtp_port=25 + echo 'SECURETRANSFER' + echo 'STARTTLS' + echo 'OPPORTUNISTIC_TLS' + ;; + (insecure) + default_smtp_port=25 + echo 'INSECURE' + ;; + esac + + if test -s "${__object:?}/parameter/port" + then + printf 'PORT %u\n' "$(cat "${__object:?}/parameter/port")" + elif test "${default_smtp_port}" -ne 25 # DMA uses port 25 by default + then + printf 'PORT %u\n' "${default_smtp_port}" + fi + + if test -f "${__object:?}/parameter/masquerade" + then + while read -r line + do + printf 'MASQUERADE %s\n' "${line}" + done <"${__object:?}/parameter/masquerade" + fi + + if test -f "${__object:?}/parameter/defer" + then + echo 'DEFER' + fi + + if test -f "${__object:?}/parameter/fullbounce" + then + echo 'FULLBOUNCE' + fi + + if test -f "${__object:?}/parameter/nullclient" + then + test -s "${__object:?}/parameter/smarthost" || { + echo '--nullclient requires a --smarthost to be defined' >&2 + exit 1 + } + + echo 'NULLCLIENT' + fi +) +# Sort conf_should to compare against "conf_is" +conf_should=$(echo "${conf_should}" | sort -s -k 1,1) + +config_updated=false +if ! echo "${conf_should}" | cmp -s "${__object:?}/explorer/conf" - +then + # config needs to be updated + dma_conf="${CONF_PATH:?}/dma.conf" + + # The following AWK script will output the new config file to be stored on + # disk. To do so it reads the current dma.conf file and the config options + # that should be set (from stdin). + # Note that the path to the current dma.conf is passed to AWK twice, because + # the new file cannot be generated in one pass. + + # The logic tries to place options at a sensible location, that is: + # a) if the option is already used in the config file: + # group all similar options (e.g. MASQUERADE) at one place in the order + # they are listed in stdin. + # b) if it is a new option and a "default comment" (e.g. "#PORT 25") exists: + # place options grouped directly after the comment (the comment is left + # alone) + # c) otherwise: + # options are grouped by word (the first word in the line) and appended + # at the end of the file. + + cat <<-CODE + awk $(drop_awk_comments "${__type:?}/files/update_dma_conf.awk") $(quote "${dma_conf}") <<'EOF' >$(quote "${dma_conf}.tmp") \ + && cat $(quote "${dma_conf}.tmp") >$(quote "${dma_conf}") + ${conf_should} + EOF + rm $(quote "${dma_conf}.tmp") + CODE + + config_updated=true + echo 'config updated' >>"${__messages_out:?}" +fi + + +# Send a test email if enabled and necessary (=configuration changed) +if test -f "${__object:?}/parameter/send-test-mail" +then + if grep -q '^__mail_alias/root:' "${__messages_in:?}" \ + || grep -q '^__dma_auth/' "${__messages_in:?}" \ + || ${config_updated} + then + cat <<-CODE + sendmail root <<'EOF' + Subject: [cdist] Test mail from '${__target_fqdn:?}' + + Hi, + + you can ignore this message. + Its sole purpose is to notify you that root mail on ${__target_fqdn:?} + will be redirected to you. + + Enjoy! + EOF + CODE + fi +fi diff --git a/type/__dma/man.rst b/type/__dma/man.rst new file mode 100644 index 0000000..29a71fa --- /dev/null +++ b/type/__dma/man.rst @@ -0,0 +1,112 @@ +cdist-type__dma(7) +============================ + +NAME +---- +cdist-type__dma - Setup the DragonFly Mail Agent as the MTA. + + +DESCRIPTION +----------- +This (singleton) type uses DMA, a small Mail Transport Agent (MTA), to accept +mails from locally installed Mail User Agents (MUA) and either deliver the mails +to a remote smart host for delivery or communicate with remote SMTP servers +directly. + + +REQUIRED PARAMETERS +------------------- +None. + + +BOOLEAN PARAMETERS +------------------ +defer + If enabled, mail will not be sent immediately, but stored in a queue. + To flush the queue and send the mails, ```dma -q`` has to be run + periodically (e.g. using a cron job.) + This type does not manage such a cron job, but some operating systems ship + such a cron job with the package. +fullbounce + Enable if bounce messages should include the complete original message, + not just the headers. +nullclient + Enable to bypass aliases and local delivery, and instead forward all mails + to the defined ``--smarthost``. +send-test-mail + If set, this type will send a test email to root after setup, to check if + the configured settings work. + + +OPTIONAL PARAMETERS +------------------- +mailname + If present, this will be the hostname used to identify this host and the + remote part of the sender addresses. + If not defined, it defaults to ``/etc/mailname`` on Debian derivatives and + to ``__target_fqdn`` otherwise. + See `dma(8)` for more information. + + Note: on Debian derivatives the ``/etc/mailname`` file should be updated + instead of using this parameter. +masquerade + Masquerade the envelope-from addresses with this address/hostname. + Use this setting if mails are not accepted by destination mail servers + because your sender domain is invalid. + This option can be used multiple times. + For more information see the `dma(8)` man page. +port + The port on which to deliver email. + If not provided, a sensible default port will be used based on the + ``--security`` argument. +security + Configures whether and how DMA should use secure connections. + + ssl/tls + Enable TLS/SSL secured transfer. + starttls + Use STARTTLS to establish a secure connection. + opportunistic (default) + Will try to establish a secure connection using STARTTLS, but allow + unencrypted transfer if STARTTLS fails. + Most useful when dma is used without a smarthost, delivering remote + messages directly to the outside mail exchangers. + insecure + allow plain text SMTP login over an insecure connection. + Should really *not* be used anymore! +smarthost + The mail server used to send email. + It must be configured to act as a relay for the host being configured by + this type so that mail can be sent to users non-local to the smarthost. + + +EXAMPLES +-------- + +.. code-block:: sh + + # Install DMA and use the smarthost mx1.domain.tld to send mail. + __dma --smarthost mx1.domain.tld --send-test-mail + + # Install DMA in a default configuration. + __dma + + +SEE ALSO +-------- +- `DragonFly Mail Agent `_ +- `DragonFly Handbook MTA `_ + + +AUTHORS +------- +Evilham +Dennis Camera + + +COPYING +------- +Copyright \(C) 2020 Evilham and Dennis Camera. You can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. diff --git a/type/__dma/manifest b/type/__dma/manifest new file mode 100755 index 0000000..530ad09 --- /dev/null +++ b/type/__dma/manifest @@ -0,0 +1,66 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera@ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# + +os=$(cat "${__global:?}/explorer/os") + +# Install DMA +case ${os} +in + (alpine) + __package dma --state present + export require='__package/dma' + ;; + (debian|devuan|ubuntu) + __package dma --state present + export require='__package/dma' + ;; + (freebsd) + # Stop sendmail if necessary + __process 'sendmail' --name 'sendmail.*' --state absent \ + --stop '/etc/rc.d/sendmail onestop' + + # ... and disable it + __key_value 'rcconf-sendmail-enable' --file '/etc/rc.conf' \ + --key 'sendmail_enable' --delimiter '=' --value '"NONE"' \ + --exact_delimiter + + # Setup mailwrapper accordingly + __file '/etc/mail/mailer.conf' --mode 0644 --source - <<-'EOF' + # + # Execute the "real" sendmail program, named /usr/libexec/sendmail/sendmail + # + sendmail /usr/libexec/dma + send-mail /usr/libexec/dma + mailq /usr/libexec/dma + newaliases /usr/libexec/dma + rmail /usr/libexec/dma + EOF + ;; + (*) + cat <&2 +Your OS (${os}) is not supported yet. + +Maybe adding support is as simple as adapting the packages or allowing it, +we highly encourage you to open a PR with the necessary changes. +See: https://code.ungleich.ch/ungleich-public/cdist-contrib/ +EOF + exit 1 + ;; +esac diff --git a/type/__dma/parameter/boolean b/type/__dma/parameter/boolean new file mode 100644 index 0000000..523bb97 --- /dev/null +++ b/type/__dma/parameter/boolean @@ -0,0 +1,4 @@ +defer +fullbounce +nullclient +send-test-mail diff --git a/type/__dma/parameter/default/security b/type/__dma/parameter/default/security new file mode 100644 index 0000000..9f1e0a6 --- /dev/null +++ b/type/__dma/parameter/default/security @@ -0,0 +1 @@ +opportunistic diff --git a/type/__dma/parameter/optional b/type/__dma/parameter/optional new file mode 100644 index 0000000..615c189 --- /dev/null +++ b/type/__dma/parameter/optional @@ -0,0 +1,4 @@ +mailname +port +security +smarthost diff --git a/type/__dma/parameter/optional_multiple b/type/__dma/parameter/optional_multiple new file mode 100644 index 0000000..70f4146 --- /dev/null +++ b/type/__dma/parameter/optional_multiple @@ -0,0 +1 @@ +masquerade diff --git a/type/__dma/singleton b/type/__dma/singleton new file mode 100644 index 0000000..e69de29 diff --git a/type/__dma_auth/explorer/auth_conf b/type/__dma_auth/explorer/auth_conf new file mode 120000 index 0000000..e89de93 --- /dev/null +++ b/type/__dma_auth/explorer/auth_conf @@ -0,0 +1 @@ +../../__dma/explorer/auth_conf \ No newline at end of file diff --git a/type/__dma_auth/explorer/state b/type/__dma_auth/explorer/state new file mode 100755 index 0000000..c829cd4 --- /dev/null +++ b/type/__dma_auth/explorer/state @@ -0,0 +1,91 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# This explorer looks for a line matching the server parameter +# in dma's auth.conf and reports: +# present: a line matching login + host + password exists +# absent: no line matching login + host exists +# different_login: a line exists but with a different login user +# different_password: a line exists but with a different password +# multiple: multiple lines matching host exist (should not happen) + +auth_conf=$("${__type_explorer:?}/auth_conf") +test -r "${auth_conf}" || exit 0 + +awk -F'\n' ' +function getvalue(path) { + # Reads the first line of the file located at path and returns it. + getline < path + close(path) + return $0 +} + +BEGIN { + DP = "[: \t]" # copied from dma/conf.c + + parameter_dir = ENVIRON["__object"] "/parameter/" + + # Read the parameters of this object + host_param = ENVIRON["__object_id"] + login_param = getvalue(parameter_dir "login") + passwd_param = getvalue(parameter_dir "password") + + state = "absent" +} + +/^#/ || /^$/ { + # skip comments and empty lines + next +} + +{ + # parse line + + login = substr($0, 1, index($0, "|") - 1) + if (!login) { login = $0 } # if no "|" found + + host = substr($0, length(login) + 2) + + if (match(host, DP)) { + passwd = substr(host, RSTART + 1) + host = substr(host, 1, RSTART - 1) + } else { + passwd = "" + } +} + +host == host_param { + # a match… + if (state == "absent") { + if (login != login_param) + state = "different_login" + else if (passwd != passwd_param) + state = "different_password" + else + state = "present" + } else { + # report "multiple" to that the type can remove the duplicates. + state = "multiple" + } +} + +END { + print state +} +' "${auth_conf}" diff --git a/type/__dma_auth/files/update_dma_auth.awk b/type/__dma_auth/files/update_dma_auth.awk new file mode 100644 index 0000000..c50198b --- /dev/null +++ b/type/__dma_auth/files/update_dma_auth.awk @@ -0,0 +1,93 @@ +#!/usr/bin/awk -f +# +# 2020 Dennis Camera (dennis.camera@ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# + +function getvalue(path) { + # Reads the first line of the file located at path and returns it. + getline < path + close(path) + return $0 +} + +function print_should() { + printf "%s|%s:%s\n", login_param, host_param, passwd_param +} + +BEGIN { + FS = "\n" + DP = "[: \t]" # copied from dma/conf.c + + parameter_dir = ENVIRON["__object"] "/parameter/" + + mode = (getvalue(parameter_dir "state") != "absent") + + host_param = ENVIRON["__object_id"] + login_param = getvalue(parameter_dir "login") + passwd_param = getvalue(parameter_dir "password") +} + +# skip comments and empty lines +/^#/ || /^$/ { + print + next +} + +{ + # parse line (like dma/conf.c would) + + login = substr($0, 1, index($0, "|") - 1) + if (!login) { login = $0 } # if no "|" found + + host = substr($0, length(login) + 2) + + if (match(host, DP)) { + passwd = substr(host, RSTART + 1) + host = substr(host, 1, RSTART - 1) + } else { + passwd = "" + } +} + +host == host_param { + if (mode) { + # state_should == present + if (!written) { + # replace first line if host matches (but only if no line has + # been written already -> no duplicates) + print_should() + written = 1 + } + next + } else { + # state_should == absent + next + } +} + +# leave other lines alone +{ + print +} + +END { + if (mode && !written) { + # append line if no match to replace was found + print_should() + } +} diff --git a/type/__dma_auth/gencode-remote b/type/__dma_auth/gencode-remote new file mode 100755 index 0000000..b6a0100 --- /dev/null +++ b/type/__dma_auth/gencode-remote @@ -0,0 +1,72 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera@ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# + +quote() { printf "'%s'" "$(printf '%s' "$*" | sed -e "s/'/'\\\\''/g")"; } +drop_awk_comments() { quote "$(sed '/^[[:blank:]]*#.*$/d;/^$/d' "$@")"; } + +state_is=$(cat "${__object:?}/explorer/state") +state_should=$(cat "${__object:?}/parameter/state") + +server=${__object_id:?} +login=$(cat "${__object:?}/parameter/login") + + +auth_conf=$(cat "${__object:?}/explorer/auth_conf") +test -n "${auth_conf}" || { + echo 'Cannot determine path of dma auth.conf' >&2 + exit 1 +} + +if test "${state_is}" = "${state_should}" +then + # state is as it should + exit 0 +fi + +case ${state_should} +in + (present) + test -n "${login}" || { echo '--login must be non-empty' >&2; exit 1; } + + if test "${state_is}" = 'absent' + then + printf 'add authuser %s on %s\n' "${login}" "${server}" >>"${__messages_out:?}" + else + printf 'set authuser %s on %s\n' "${login}" "${server}" >>"${__messages_out:?}" + fi + ;; + (absent) + printf 'delete authuser %s on %s\n' "${login}" "${server}" >>"${__messages_out:?}" + ;; + (*) + printf 'Invalid --state: %s.\n' "${state_should}" >&2 + printf 'Acceptable values are: present, absent.\n' >&2 + exit 1 + ;; +esac + + +cat <$(quote "${auth_conf}.tmp") \ +&& cat $(quote "${auth_conf}.tmp") >$(quote "${auth_conf}") +rm -f $(quote "${auth_conf}.tmp") +EOF diff --git a/type/__dma_auth/man.rst b/type/__dma_auth/man.rst new file mode 100644 index 0000000..da76883 --- /dev/null +++ b/type/__dma_auth/man.rst @@ -0,0 +1,66 @@ +cdist-type__dma_auth(7) +======================= + +NAME +---- +cdist-type__dma_auth - Configure SMTP logins for the DragonFly Mail Agent MTA. + + +DESCRIPTION +----------- +This cdist type allows you to set up credentials to log in to remote SMTP +servers. + +NB: dma currently (v0.13) does not differentiate between users on a host. + It will use whatever user it finds in the ``auth.conf`` first. + Thus, this type will use the ``__object_id`` as the host specifier. + + +REQUIRED PARAMETERS +------------------- +login + The user's LOGIN name on the SMTP server. +password + The user's password (in plain text.) + + +OPTIONAL PARAMETERS +------------------- +state + Either ``present`` or ``absent``. Defaults to ``present``. + +BOOLEAN PARAMETERS +------------------ +None. + + +EXAMPLES +-------- + +.. code-block:: sh + + # Set the password for smarthost + __dma_auth smarthost.example.com --login joe --password hunter2 + + # Set credentials for user at an external provider + __dma_auth mail.provider.com --login paul@example.com --password letmein + + # Delete credentials for example.com (for all users) + __dma_auth example.com --login '' --password '' --state absent + +SEE ALSO +-------- +:strong:`cdist-type__dma`\ (7), :strong:`dma`\ (8) + + +AUTHORS +------- +Dennis Camera + + +COPYING +------- +Copyright \(C) 2020 Dennis Camera. You can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. diff --git a/type/__dma_auth/nonparallel b/type/__dma_auth/nonparallel new file mode 100644 index 0000000..e69de29 diff --git a/type/__dma_auth/parameter/default/state b/type/__dma_auth/parameter/default/state new file mode 100644 index 0000000..e7f6134 --- /dev/null +++ b/type/__dma_auth/parameter/default/state @@ -0,0 +1 @@ +present diff --git a/type/__dma_auth/parameter/optional b/type/__dma_auth/parameter/optional new file mode 100644 index 0000000..ff72b5c --- /dev/null +++ b/type/__dma_auth/parameter/optional @@ -0,0 +1 @@ +state diff --git a/type/__dma_auth/parameter/required b/type/__dma_auth/parameter/required new file mode 100644 index 0000000..ae3c622 --- /dev/null +++ b/type/__dma_auth/parameter/required @@ -0,0 +1,2 @@ +login +password diff --git a/type/__mail_alias/explorer/aliases b/type/__mail_alias/explorer/aliases new file mode 100755 index 0000000..ac13d7c --- /dev/null +++ b/type/__mail_alias/explorer/aliases @@ -0,0 +1,73 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# Find aliases for a given user name and print the aliases (each one on a +# separate line) + +aliases_file=$("${__type_explorer:?}/aliases_file") +test -r "${aliases_file}" || exit 0 + +: "${__object_id:?}" # assert __object_id is set, because it is used in AWK + +awk -F ':[ \t]*' ' +function print_aliases(aliases, matches) { + # prints comma-separated aliases (one per line) + split(aliases, matches, /,[ \t]*/) + for (i in matches) { + gsub(/^[ \t]*|[ \t]*$/, "", matches[i]) + if (matches[i]) print matches[i] + } +} + +/^#/ { + # comment line (ignore) + select = 0; cont = 0 # comments terminate alias lists and continuations + next +} + +{ + # is this line a continuation line? + # (the prev. line ended in a backslash or the line starts with whitespace) + is_cont = /^[ \t]/ || cont + + # detect if the line is a line to be continued (ends with a backslash) + cont = /\\$/ + + # if it is, we drop the backslash from the line + if (cont) sub(/[ \t]*\\$/, "", $0) +} + +is_cont { + # if in the alias list of the "target" user, we also print these aliases. + if (select) print_aliases($0) + next +} + +$1 == ENVIRON["__object_id"] { + # "target" user -> print alias list + select = 1 + print_aliases($2) + next +} + +{ + # other user + select = 0 +} +' "${aliases_file}" diff --git a/type/__mail_alias/explorer/aliases_file b/type/__mail_alias/explorer/aliases_file new file mode 100755 index 0000000..7f09f88 --- /dev/null +++ b/type/__mail_alias/explorer/aliases_file @@ -0,0 +1,52 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# This explorer finds the aliases file to modify. + +found() { echo "$*"; exit 0; } + +check_file() { + if test -f "$1" + then + found "$1" + fi +} + +case $("${__explorer:?}/os") +in + (freebsd|openbsd|solaris) + check_file /etc/mail/aliases + + # default + found /etc/mail/aliases + ;; + (alpine|debian|devuan|ubuntu) + check_file /etc/aliases + + # default + found /etc/aliases + ;; + (*) + check_file /etc/mail/aliases + check_file /etc/aliases + + # default + found /etc/aliases + ;; +esac diff --git a/type/__mail_alias/files/update_aliases.awk b/type/__mail_alias/files/update_aliases.awk new file mode 100644 index 0000000..11a4c85 --- /dev/null +++ b/type/__mail_alias/files/update_aliases.awk @@ -0,0 +1,96 @@ +#!/usr/bin/awk -f +# +# 2020 Dennis Camera (dennis.camera@ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# + +function getvalue(path, line) { + # Reads the first line of the file located at path and returns it. + getline line < path + close(path) + return line +} + +function sepafter(f, def, _) { + # finds the separator between field $f and $(f+1) + _ = substr($0, length($f)+1, index(substr($0, length($f)+1), $(f+1))-1) + return _ ? _ : def +} + +function write_aliases( line) { + if (aliases_written) return + + # print aliases line + printf "%s%s", ENVIRON["__object_id"], sepafter(1, ": ") + while ((getline line < aliases_should_file) > 0) { + if (aliases_written) printf ", " + printf "%s", line + aliases_written = 1 + } + printf "\n" + close(aliases_should_file) +} + +BEGIN { + FS = ":[ \t]*" + + parameter_dir = ENVIRON["__object"] "/parameter/" + + mode = (getvalue(parameter_dir "state") != "absent") + aliases_should_file = (parameter_dir "/alias") +} + +/^[ \t]*\#/ { + # comment line (leave alone) + select = 0; cont = 0 # comments terminate alias lists and continuations + print + next +} + +{ + # is this line a continuation line? + # (the prev. line ended in a backslash or the line starts with whitespace) + is_cont = /^[ \t]/ || cont + + # detect if the line is a line to be continued (ends with a backslash) + cont = /\\$/ +} + +is_cont { + # we only print the line if it has not been rewritten (select) + if (!select) print + next +} + +$1 == ENVIRON["__object_id"] { + # "target" user -> rewrite aliases list + select = 1 + if (mode) write_aliases() + next +} + +{ + # other user + select = 0 + print +} + +END { + # if the last line was an alias, the separator will be reused (looks better) + if (mode && !aliases_written) + write_aliases() +} diff --git a/type/__mail_alias/gencode-remote b/type/__mail_alias/gencode-remote new file mode 100755 index 0000000..4a8f889 --- /dev/null +++ b/type/__mail_alias/gencode-remote @@ -0,0 +1,87 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera@ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# + +quote() { printf "'%s'" "$(printf '%s' "$*" | sed -e "s/'/'\\\\''/g")"; } +drop_awk_comments() { quote "$(sed '/^[[:blank:]]*#.*$/d;/^$/d' "$@")"; } + +aliases_file=$(cat "${__object:?}/explorer/aliases_file") + +test -n "${aliases_file}" || { + echo 'Could not determine aliases file path.' >&2 + exit 1 +} + + +state_should=$(cat "${__object:?}/parameter/state") + +case ${state_should} +in + (present) + if cmp -s "${__object:?}/explorer/aliases" "${__object:?}/parameter/alias" + then + # all good! + exit 0 + fi + + test -s "${__object:?}/parameter/alias" || { + printf 'The --alias parameter is required if --state present.\n' >&2 + printf 'Use --state absent to remove all aliases.\n' >&2 + exit 1 + } + + if test -s "${__object:?}/explorer/aliases" + then + echo "update aliases" >>"${__messages_out:?}" + else + echo "add aliases" >>"${__messages_out:?}" + fi + ;; + (absent) + # nothing to do if no aliases found. + test -s "${__object:?}/explorer/aliases" || exit 0 + + echo "delete aliases" >>"${__messages_out:?}" + ;; + (*) + printf 'Invalid --state: %s.\n' "${state_should}" >&2 + printf 'Acceptable values are: present, absent.\n' >&2 + exit 1 +esac + +cat <$(quote "${aliases_file}.tmp") \ +|| { + rm -f $(quote "${aliases_file}.tmp") + echo 'Generating new aliases file failed!' >&2 + exit 1 +} + +if ! cmp -s $(quote "${aliases_file}") $(quote "${aliases_file}.tmp") +then + # aliases file was modified, replace: + cat $(quote "${aliases_file}.tmp") >$(quote "${aliases_file}") + + # then, run newaliases if present ("missing" on Alpine Linux because of typo) + command -v newaliases >/dev/null 2>&1 && newaliases || true +fi +rm -f $(quote "${aliases_file}.tmp") +EOF diff --git a/type/__mail_alias/man.rst b/type/__mail_alias/man.rst new file mode 100644 index 0000000..de40512 --- /dev/null +++ b/type/__mail_alias/man.rst @@ -0,0 +1,76 @@ +cdist-type__mail_alias(7) +========================= + +NAME +---- +cdist-type__mail_alias - Manage mail aliases. + + +DESCRIPTION +----------- +This cdist type allows you to configure mail aliases (/etc/aliases). + + +REQUIRED PARAMETERS +------------------- +None. + + +OPTIONAL PARAMETERS +------------------- +state + 'present' or 'absent', defaults to 'present' +alias + an alias, i.e. a mail address where mail for the user should be redirected + to. + This parameter can be specified multiple times to redirect to multiple + recipients. + If ``--state`` is ``present`` this parameter is required. + See `aliases(5)` for the different forms this parameter can take. + + +BOOLEAN PARAMETERS +------------------ +None. + + +EXAMPLES +-------- + +.. code-block:: sh + + # Redirect root mail to a "real" email address + __mail_alias root --alias admin@example.com + + # Disable redirection of mail for joe + __mail_alias joe --state absent + + +BUGS +---- +- Quoted strings are not parsed by this type. As a result, aliases + containing ``,`` (commas) are treated incorrectly (they are treated as + separate aliases.) + Make sure that email addresses, file names, and pipe commands do not contain + commas. +- ``:include:`` directives in the aliases file are not evaluated by this type. + They are treated like a regular alias, the values of the included file are + not expanded. + + +SEE ALSO +-------- +:strong:`aliases`\ (5) + + +AUTHORS +------- +Dennis Camera + + +COPYING +------- +Copyright \(C) 2020 Dennis Camera. You can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. diff --git a/type/__mail_alias/nonparallel b/type/__mail_alias/nonparallel new file mode 100644 index 0000000..e69de29 diff --git a/type/__mail_alias/parameter/default/state b/type/__mail_alias/parameter/default/state new file mode 100644 index 0000000..e7f6134 --- /dev/null +++ b/type/__mail_alias/parameter/default/state @@ -0,0 +1 @@ +present diff --git a/type/__mail_alias/parameter/optional b/type/__mail_alias/parameter/optional new file mode 100644 index 0000000..ff72b5c --- /dev/null +++ b/type/__mail_alias/parameter/optional @@ -0,0 +1 @@ +state diff --git a/type/__mail_alias/parameter/optional_multiple b/type/__mail_alias/parameter/optional_multiple new file mode 100644 index 0000000..d077ed8 --- /dev/null +++ b/type/__mail_alias/parameter/optional_multiple @@ -0,0 +1 @@ +alias