diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a0eb5d3c0ba28e99ea5737b65caf35b5b4e32995..10cafd6a6f5b0fb9f21366b850bf944b74926929 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ image: registry.git.cccv.de/uffd/docker-images/buster variables: - DEBIAN_FRONTEND: noninteractive + DEBIAN_FRONTEND: noninteractive GIT_SUBMODULE_STRATEGY: normal PYLINT_PIN: pylint~=2.10.0 @@ -12,6 +12,17 @@ before_script: - python3 -m pylint --version - python3 -m coverage --version +build:apt: + script: + - ./debian/create_changelog.py uffd-socketmap > debian/changelog + - dpkg-buildpackage -us -uc + - mv ../*.deb ./ + - dpkg-deb -I *.deb + - dpkg-deb -c *.deb + artifacts: + paths: + - ./*.deb + linter:bullseye: image: registry.git.cccv.de/uffd/docker-images/bullseye stage: test @@ -19,7 +30,7 @@ linter:bullseye: - pip3 install $PYLINT_PIN pylint-gitlab pylint-flask-sqlalchemy # this force-updates jinja2 and some other packages! - python3 -m pylint --exit-zero --rcfile .pylintrc --output-format=pylint_gitlab.GitlabCodeClimateReporter 'server.py' > codeclimate.json - python3 -m pylint --exit-zero --rcfile .pylintrc --output-format=pylint_gitlab.GitlabPagesHtmlReporter 'server.py' > pylint.html - - python3 -m pylint --rcfile .pylintrc --output-format=text 'server.py' + - python3 -m pylint --rcfile .pylintrc --output-format=text 'server.py' artifacts: when: always paths: diff --git a/README.md b/README.md index a4083d621a996a0d820750200efcfe6bbcf0cb33..670f8e3576983b81fa31eb59cfd69459dfe77c9a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,29 @@ -Socketmap proxy for uffd mail alias lookup -========================================== +Socketmap server to integrate uffd mail aliases with postfix +============================================================ + +[uffd](https://git.cccv.de/uffd/uffd) has features that rely on mail aliases. +To make those mail aliases work, it provides an API to lookup alias addresses +for a given address. uffd-socketmap uses this API to integrate alias lookup +with MTAs that support the socketmap protocol, like sendmail and postfix. + +uffd-socketmap can be run manually. For production deployments, use the +provided debian packages. Add our package mirror to `/etc/sources.list`: -Run it like this: ``` -SERVER_API_KEY=my_secret_api_key server.py --socket-path /var/run/uffd-socketmap.sock --api-url=https://sso.example.com +deb https://packages.cccv.de/uffd bullseye main ``` +Then download [cccv-archive-key.gpg](cccv-archive-key.gpg) and add it to +the trusted repository keys in `/etc/apt/trusted.gpg.d/`. Afterwards run +`apt update && apt install uffd-socketmap` to install the package. + +Set the API url and secret in `/etc/uffd-socketmap-postfix.conf`, enable +and start `uffd-socketmap-postfix.socket`. Then configure Postfix, e.g. +by adding the following lines to `/etc/postfix/main.cf`: -Configure Postfix to use it by setting `virtual_alias_maps` in `/etc/postfix/main.cf` like this: ``` -virtual_alias_maps = socketmap:unix:/var/run/uffd-socketmap.sock:virtual +# Note that postfix runs in a chroot (/var/spool/postfix) and paths are +# relative to that! +virtual_alias_maps = socketmap:unix:/uffd-socketmap.sock:virtual +# Defaults to $virtual_alias_maps, which does not work here, so unset it +virtual_alias_domains = ``` diff --git a/cccv-archive-key.gpg b/cccv-archive-key.gpg new file mode 100644 index 0000000000000000000000000000000000000000..b0ac4de43a0786a52060bade75c5150ac552ade7 Binary files /dev/null and b/cccv-archive-key.gpg differ diff --git a/debian/contrib/uffd-socketmap-postfix.conf b/debian/contrib/uffd-socketmap-postfix.conf new file mode 100644 index 0000000000000000000000000000000000000000..c427afa4eac80512fc2c05202558e2249ce2e365 --- /dev/null +++ b/debian/contrib/uffd-socketmap-postfix.conf @@ -0,0 +1,7 @@ +# Both options must be set +#SERVER_API_URL="https://localhost" +#SERVER_API_KEY="my_secret_api_token" + +# The socket path is hard-coded to "/var/spool/postfix/uffd-socketmap.sock" +# ("/uffd-socketmap.sock" in the postfix sandbox). Use systemd overwrites +# for uffd-socketmap-postfix.socket to change it. diff --git a/uffd-socketmap@.service b/debian/contrib/uffd-socketmap-postfix.service similarity index 70% rename from uffd-socketmap@.service rename to debian/contrib/uffd-socketmap-postfix.service index 50344e782ba52b1ed6f6f567552c36e4ec584c6b..65a8275dce66124288b40fd1d9e6cfb702766640 100644 --- a/uffd-socketmap@.service +++ b/debian/contrib/uffd-socketmap-postfix.service @@ -1,17 +1,19 @@ [Unit] -Description=Socketmap proxy for uffd mail alias lookup +Description=Socketmap server to integrate uffd mail aliases with postfix After=network.target Before=postfix.service +BindsTo=uffd-socketmap-postfix.socket [Service] ExecStart=/usr/bin/uffd-socketmap --socket-fd 3 Restart=always RestartSec=10 -StandardOutput=syslog -StandardError=syslog -SyslogIdentifier=uffd-socketmap-%I +StandardOutput=journal +StandardError=journal +SyslogIdentifier=uffd-socketmap-postfix +DynamicUser=true PrivateUsers=true CapabilityBoundingSet= NoNewPrivileges=true @@ -34,9 +36,9 @@ PrivateTmp=true PrivateDevices=true SystemCallArchitectures=native SystemCallFilter=@system-service +MemoryDenyWriteExecute=true -EnvironmentFile=/etc/uffd-socketmap/defaults -EnvironmentFile=/etc/uffd-socketmap/$I.env +EnvironmentFile=/etc/uffd-socketmap-postfix.conf [Install] WantedBy=default.target diff --git a/debian/contrib/uffd-socketmap-postfix.socket b/debian/contrib/uffd-socketmap-postfix.socket new file mode 100644 index 0000000000000000000000000000000000000000..3c2e0a422b13f0f09ba6ae8837b80f66b5b4e742 --- /dev/null +++ b/debian/contrib/uffd-socketmap-postfix.socket @@ -0,0 +1,11 @@ +[Unit] +Description=Socketmap server to integrate uffd mail aliases with postfix + +[Socket] +ListenStream=/var/spool/postfix/uffd-socketmap.sock +SocketUser=postfix +SocketGroup=postfix +SocketMode=0640 + +[Install] +WantedBy=sockets.target diff --git a/debian/control b/debian/control new file mode 100644 index 0000000000000000000000000000000000000000..22732bed74b10029b629f12fa6f4471ad582ab3f --- /dev/null +++ b/debian/control @@ -0,0 +1,16 @@ +Source: uffd-socketmap +Section: python +Priority: optional +Maintainer: CCCV <it@cccv.de> +Build-Depends: + debhelper-compat (= 12), +Standards-Version: 4.5.0 +Homepage: https://git.cccv.de/uffd/socketmap-proxy +Vcs-Git: https://git.cccv.de/uffd/socketmap-proxy.git + +Package: uffd-socketmap +Architecture: all +Depends: + ${misc:Depends}, + python3-requests, +Description: Socketmap server to integrate uffd mail aliases with postfix diff --git a/debian/create_changelog.py b/debian/create_changelog.py new file mode 100755 index 0000000000000000000000000000000000000000..b62f211c24403fbfb2a1465cbf58491b5616f5b6 --- /dev/null +++ b/debian/create_changelog.py @@ -0,0 +1,106 @@ +#!/usr/bin/python3 +import sys +import re +import textwrap +import datetime +import email.utils + +import git + +package_name = 'UNKNOWN' + +alias_names = { + 'julian': 'Julian Rother', + 'Julian': 'Julian Rother', +} + +ignore_commit_regexes = [ + '^fixup!', +] + +def print_release(tag=None, commits=tuple(), last_tag=None): + release_version = '0.0.0' + release_author = git.objects.util.Actor('None', 'undefined@example.com') + release_date = 0 + release_status = 'UNRELEASED' + message = '' + + if tag: + release_status = 'unstable' + release_version = tag.name[1:] # strip leading "v" + if isinstance(tag.object, git.TagObject): + release_author = tag.object.tagger + release_date = tag.object.tagged_date + message = tag.object.message.split('-----BEGIN PGP SIGNATURE-----')[0].strip() + else: + release_author = tag.object.committer + release_date = tag.object.committed_date + elif commits: + release_author = commits[0].committer + release_date = commits[0].committed_date + date = datetime.datetime.fromtimestamp(release_date).strftime('%Y%m%dT%H%M%S') + last_version = '0.0.0' + if last_tag: + last_version = last_tag.name[1:] # strip leading "v" + release_version = f'{last_version}+git{date}-{commits[0].hexsha[:8]}' + + print(f'{package_name} ({release_version}) {release_status}; urgency=medium') + print() + if message: + print(textwrap.indent(message, ' ')) + print() + commit_authors = [] # list of (key, author), sorted by first commit date + commit_author_emails = {} # author email -> key + commit_author_names = {} # author name -> key + commit_author_commits = {} # key -> list of commits + for commit in commits: + if any(filter(lambda pattern: re.match(pattern, commit.summary), ignore_commit_regexes)): + continue + if len(commit.parents) > 1: + continue # Ignore merge commits + author_name = alias_names.get(commit.author.name, commit.author.name) + key = commit_author_emails.get(commit.author.email) + if key is None: + key = commit_author_names.get(author_name) + if key is None: + key = commit.author.email + commit_authors.append((key, author_name)) + commit_author_emails[commit.author.email] = key + commit_author_names[author_name] = key + commit_author_commits[key] = commit_author_commits.get(key, []) + [commit] + commit_authors.sort(key=lambda args: len(commit_author_commits[args[0]])) + for key, author_name in commit_authors: + print(f' [ {author_name} ]') + for commit in commit_author_commits[key]: + lines = '\n'.join(textwrap.wrap(commit.summary, 90)) + lines = ' * ' + textwrap.indent(lines, ' ').strip() + print(lines) + print() + print(f' -- {alias_names.get(release_author.name, release_author.name)} <{release_author.email}> {email.utils.formatdate(release_date)}') + +if __name__ == '__main__': + repo = git.Repo('.') + package_name = sys.argv[1] + + version_commits = {} + for tag in repo.tags: + if not re.fullmatch('v[0-9]+[.][0-9]+[.][0-9]+.*', tag.name): + continue + if isinstance(tag.object, git.TagObject): + commit_hexsha = tag.object.object.hexsha + else: + commit_hexsha = tag.object.hexsha + version_commits[commit_hexsha] = tag + + tag = None + commits = [] + for commit in repo.iter_commits('HEAD'): + if commit.hexsha in version_commits: + prev_tag = version_commits[commit.hexsha] + if commits: + print_release(tag, commits, last_tag=prev_tag) + print() + tag = prev_tag + commits = [] + commits.append(commit) + print_release(tag, commits) diff --git a/debian/install b/debian/install new file mode 100644 index 0000000000000000000000000000000000000000..e886322c605606d1f23708ba9b7016abd6d60b8c --- /dev/null +++ b/debian/install @@ -0,0 +1,4 @@ +server.py /usr/lib/uffd-socketmap/ +debian/contrib/uffd-socketmap-postfix.service /usr/lib/systemd/system/ +debian/contrib/uffd-socketmap-postfix.socket /usr/lib/systemd/system/ +debian/contrib/uffd-socketmap-postfix.conf /etc/ diff --git a/debian/links b/debian/links new file mode 100644 index 0000000000000000000000000000000000000000..5c199f3ebe5c63bffb09745ceb82bf912c220605 --- /dev/null +++ b/debian/links @@ -0,0 +1 @@ +/usr/lib/uffd-socketmap/server.py /usr/bin/uffd-socketmap diff --git a/debian/postinst b/debian/postinst new file mode 100755 index 0000000000000000000000000000000000000000..bdba6dc00488e07d643734fae338985b9b62c6f1 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,21 @@ +#!/bin/sh + +set -e + +case "$1" in + configure) + chmod 0640 /etc/uffd-socketmap-postfix.conf + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000000000000000000000000000000000000..cbe925d7587131c8ec8761915930894c60c096fd --- /dev/null +++ b/debian/rules @@ -0,0 +1,3 @@ +#!/usr/bin/make -f +%: + dh $@ diff --git a/server.py b/server.py old mode 100644 new mode 100755 index 3fbfc67f01a4730ace33fb27bafeba0118729b47..dd59646006ee1c4bd0c46dc1a4495253bb05bf3f --- a/server.py +++ b/server.py @@ -1,3 +1,4 @@ +#!/usr/bin/python3 import os import sys import logging diff --git a/uffd-socketmap@.socket b/uffd-socketmap@.socket deleted file mode 100644 index dcff275b7590f3e976430fb3c933212572091497..0000000000000000000000000000000000000000 --- a/uffd-socketmap@.socket +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=Socket proxy for uffd mail alias lookup -PartOf=uffd-socketmap@%i.service - -[Socket] -ListenStream=/run/socketmap-proxy/%I.sock -SocketUser=postfix -SocketGroup=postfix -SocketMode=0640 - -[Install] -WantedBy=sockets.target