setup-dkim.sh 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. #!/bin/sh
  2. # Erzeugt je Domain einen DKIM-Schlüssel, legt den öffentlichen Schlüssel als
  3. # DNS-TXT-Record an, trägt den Schlüssel in OpenDKIM ein und startet OpenDKIM neu.
  4. #
  5. # DRY_RUN=1 (Vorgabe) schickt nichts ins DNS und startet nichts neu. Schlüssel
  6. # und Konfiguration werden trotzdem lokal geschrieben, damit der Wert zum
  7. # Prüfen überhaupt erst vorliegt. Erst nach der Prüfung auf 0 setzen.
  8. set -eu
  9. DRY_RUN=1
  10. CLI=./schlundtech-dns
  11. KEYDIR=/etc/opendkim/keys
  12. OPENDKIMDIR=/etc/opendkim
  13. SELECTOR=$(date +%Y%m)
  14. DOMAINS="klumpp.de klumpp.net netdome.biz oberrhein.de"
  15. die() { printf '%s\n' "$*" >&2; exit 1; }
  16. run_cli() {
  17. if [ "$DRY_RUN" -eq 1 ]; then
  18. "$CLI" "$@" --dry-run
  19. else
  20. "$CLI" "$@"
  21. fi
  22. }
  23. command -v opendkim-genkey >/dev/null 2>&1 || die "opendkim-genkey nicht gefunden"
  24. [ -x "$CLI" ] || die "$CLI nicht gefunden — vorher 'go build'"
  25. uid=$(id -u) || die "id -u schlug fehl"
  26. [ "$uid" -eq 0 ] || die "muss als root laufen, schreibt nach $KEYDIR"
  27. [ -d "$KEYDIR" ] || die "$KEYDIR fehlt"
  28. cd "$KEYDIR"
  29. for domain in $DOMAINS; do
  30. opendkim-genkey -d "$domain" -b 2048 -r -s "$SELECTOR"
  31. # Nur die zwei neuen Dateien, nicht alles im Verzeichnis.
  32. chown opendkim:opendkim "$SELECTOR.txt" "$SELECTOR.private"
  33. mv "$SELECTOR.txt" "$domain.txt"
  34. mv "$SELECTOR.private" "$domain.private"
  35. # Die .txt von opendkim-genkey ist KEIN blosser Record-Wert, sondern ein
  36. # kompletter Zone-File-Eintrag, vorgeblich in 250-Zeichen-Stuecke geteilt:
  37. #
  38. # 202610._domainkey.klumpp.de IN TXT ( "v=DKIM1; k=rsa; "
  39. # "p=MIIB..." "..."
  40. # )
  41. #
  42. # Die Anfuehrungszeichen trennen die Character-Strings. Nur was zwischen
  43. # ihnen steht ist der Record-Wert; der Rest ist Zone-File-Syntax und darf
  44. # nicht mit in DNS. awk mit -F'"' liefert genau diese Zwischenstuecke,
  45. # die dann aneinandergehaengt werden - genau das, was ein Resolver tut.
  46. value=$(awk -F'"' '{ for (i = 2; i <= NF; i += 2) printf "%s", $i }' "$domain.txt")
  47. [ -n "$value" ] || die "kein Wert in $domain.txt gefunden"
  48. case "$value" in
  49. *v=DKIM1*) ;;
  50. *) die "$domain.txt enthaelt keinen DKIM-Record" ;;
  51. esac
  52. case "$value" in
  53. *p=*) ;;
  54. *) die "$domain.txt enthaelt kein p=" ;;
  55. esac
  56. run_cli records set "$domain" \
  57. --name "$SELECTOR._domainkey" --type TXT --value "$value"
  58. # Ein 2048-Bit-Schluessel ergibt rund 360 Zeichen und ueberschreitet damit
  59. # die 255 Bytes je Character-String, die DNS erlaubt. Das ist normal: der
  60. # Wert wird in mehrere Strings aufgeteilt und vom Gateway als einer
  61. # gespeichert. Im Portal sieht er deshalb quoted und aufgeteilt aus.
  62. if [ "${#value}" -gt 255 ]; then
  63. printf '%s: %d Zeichen, laenger als 255 — das Gateway teilt den Wert in Character-Strings auf, das ist korrekt\n' \
  64. "$domain" "${#value}" >&2
  65. fi
  66. if [ "$DRY_RUN" -eq 0 ]; then
  67. keyline="key-$domain $domain:$SELECTOR:$KEYDIR/$domain.private"
  68. grep -qxF "$keyline" "$OPENDKIMDIR/key.table" 2>/dev/null ||
  69. printf '%s\n' "$keyline" >>"$OPENDKIMDIR/key.table"
  70. signline="*@$domain key-$domain"
  71. grep -qxF "$signline" "$OPENDKIMDIR/signing.table" 2>/dev/null ||
  72. printf '%s\n' "$signline" >>"$OPENDKIMDIR/signing.table"
  73. fi
  74. done
  75. if [ "$DRY_RUN" -eq 1 ]; then
  76. printf 'nur Vorschau: kein DNS-Record gesetzt, key.table/signing.table unveraendert, kein Neustart — DRY_RUN=0 zum Ausfuehren\n' >&2
  77. else
  78. systemctl restart opendkim
  79. fi