more example scripts from Teckids e.V.
authormirabilos <t.glaser@tarent.de>
Thu, 23 Nov 2017 19:57:23 +0000 (20:57 +0100)
committermirabilos <t.glaser@tarent.de>
Thu, 23 Nov 2017 19:57:23 +0000 (20:57 +0100)
36 files changed:
mksh/teckids/advent/parsecsv.sh [new file with mode: 0644]
mksh/teckids/advent/parsemail.sh [new file with mode: 0644]
mksh/teckids/advent/parseparsemail.sh [new file with mode: 0644]
mksh/teckids/intern/anmparse [new file with mode: 0644]
mksh/teckids/intern/anmparsemaildir [new file with mode: 0644]
mksh/teckids/intern/csvfix-1 [new file with mode: 0644]
mksh/teckids/intern/csvfix-2 [new file with mode: 0644]
mksh/teckids/intern/dmppgsql.sh [new file with mode: 0644]
mksh/teckids/intern/fix-slapd-crc [new file with mode: 0755]
mksh/teckids/intern/ldap2for [new file with mode: 0644]
mksh/teckids/intern/ldif_crc.sh [new file with mode: 0755]
mksh/teckids/intern/mailcheck [new file with mode: 0644]
mksh/teckids/intern/mbox2mid [new file with mode: 0644]
mksh/teckids/intern/parse-bb.sh [new file with mode: 0644]
mksh/teckids/intern/parse-be.sh [new file with mode: 0644]
mksh/teckids/intern/parse-by.sh [new file with mode: 0644]
mksh/teckids/intern/parse-hb.sh [new file with mode: 0644]
mksh/teckids/intern/parse-sn.sh [new file with mode: 0644]
mksh/teckids/intern/parse-st.sh [new file with mode: 0644]
mksh/teckids/intern/parse-th.sh [new file with mode: 0644]
mksh/teckids/intern/pty.ksh [new file with mode: 0644]
mksh/teckids/intern/teckids-ejmaint [new file with mode: 0755]
mksh/teckids/intern/teckids-ldapmaint [new file with mode: 0755]
mksh/teckids/intern/teckids-mmmaint [new file with mode: 0755]
mksh/teckids/intern/teckids-sigmaint [new file with mode: 0755]
mksh/teckids/intern/teckids-sshmaint [new file with mode: 0755]
mksh/teckids/mk/docman [new file with mode: 0644]
mksh/teckids/mk/mkids [new file with mode: 0644]
mksh/teckids/mk/mkpeople [new file with mode: 0644]
mksh/teckids/www/auth_vote.cgi [new file with mode: 0755]
mksh/teckids/www/auth_votes.cgi [new file with mode: 0755]
mksh/teckids/www/betreuung_2016_herbst_anmeldung.cgi [new file with mode: 0755]
mksh/teckids/www/bwsrlang.cgi [new file with mode: 0755]
mksh/teckids/www/schultab.cgi [new file with mode: 0755]
mksh/teckids/www/sponsoren-vcard.cgi [new file with mode: 0755]
mksh/teckids/www/webform.sh [new file with mode: 0644]

diff --git a/mksh/teckids/advent/parsecsv.sh b/mksh/teckids/advent/parsecsv.sh
new file mode 100644 (file)
index 0000000..4863066
--- /dev/null
@@ -0,0 +1,247 @@
+#!/bin/mksh
+#-
+# Copyright © 2015, 2016
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+#XXX TODO: sanitätsprüfen
+
+sigdashes='-- '
+
+: "${TEST:=1}"
+[[ $TEST = 0 ]] && unset TEST
+
+# höchste benutzte User-ID (außer nobody) finden
+highest_uid=$(getent passwd | sort -t: -nk3 | tail -n -2 |&
+       IFS=: read -pA
+       echo ${REPLY[2]}
+    )
+
+while IFS=";" read -r TODO Vorname Nachname Geburtsdatum Alter Schulname Schulort Klasse eMail Kontaktumfang Account Benutzername Kanal Mehrbetrag OK_Eltern OK_Datenschutz PWHash Referer FormularID MessageID rest; do
+       eMail=${eMail%>}
+       eMail=${eMail#<}
+
+       if [[ $eMail != +([-!\#-\'*+/-9=?A-Z^-~])*(.+([-!\#-\'*+/-9=?A-Z^-~]))@[0-9A-Za-z]?(*([-0-9A-Za-z])[0-9A-Za-z])*(.[0-9A-Za-z]?(*([-0-9A-Za-z])[0-9A-Za-z])) ]]; then
+               print -u2 -r -- "Ungültige E-Mail-Adresse: <$eMail>"
+               continue
+       fi
+       if [[ $TODO = s ]]; then
+               continue
+       fi
+       if [[ $TODO = rs ]]; then
+               # Ablehnen wegen ungültiger Schulangabe
+               heirloom-mailx ${TEST:+-d} -c advent@lists.teckids.org -r dominik.george@teckids.org -s "Anmeldung zum Adventskalender abgelehnt" "$eMail" >&2 <<-EOF
+                       Hallo $Vorname,
+
+                       leider konnten wir deine Anmeldung zum Adventskalender nicht annehmen.
+
+                       Du hast bei der Anmeldung nicht den richtigen Namen deiner Schule angegeben.
+
+                       Wenn du noch mitmachen möchtest, melde dich bitte bis spätestens 1.12. noch
+                       einmal mit richtigen Angaben an.
+
+                       Viele Grüße,
+                       Dominik George
+
+                       $sigdashes
+                       Dominik George (1. Vorstandsvorsitzender, pädagogischer Leiter)
+                       Teckids e.V. - Erkunden, Entdecken, Erfinden.
+                       https://www.teckids.org
+               EOF
+               continue
+       fi
+       if [[ $TODO = ra ]]; then
+               # Ablehnen wegen ungültigem Geburtsdatum
+               heirloom-mailx ${TEST:+-d} -c advent@lists.teckids.org -r dominik.george@teckids.org -s "Anmeldung zum Adventskalender abgelehnt" "$eMail" >&2 <<-EOF
+                       Hallo $Vorname,
+
+                       leider konnten wir deine Anmeldung zum Adventskalender nicht annehmen.
+
+                       Du hast bei der Anmeldung ein ungültiges Geburtsdatum angegeben.
+
+                       Wenn du noch mitmachen möchtest, melde dich bitte bis spätestens 1.12. noch
+                       einmal mit richtigen Angaben an.
+
+                       Viele Grüße,
+                       Dominik George
+
+                       $sigdashes
+                       Dominik George (1. Vorstandsvorsitzender, pädagogischer Leiter)
+                       Teckids e.V. - Erkunden, Entdecken, Erfinden.
+                       https://www.teckids.org
+               EOF
+               continue
+       fi
+       if [[ $TODO = rb ]]; then
+               # Ablehnen wegen ungültigem Account
+               heirloom-mailx ${TEST:+-d} -c advent@lists.teckids.org -r dominik.george@teckids.org -s "Anmeldung zum Adventskalender abgelehnt" "$eMail" >&2 <<-EOF
+                       Hallo $Vorname,
+
+                       leider konnten wir deine Anmeldung zum Adventskalender nicht annehmen.
+
+                       Du hast bei der Anmeldung keine gültigen Angaben zu Benutzername und
+                       Passwort gemacht.
+
+                       Wenn du noch mitmachen möchtest, melde dich bitte bis spätestens 1.12. noch
+                       einmal mit richtigen Angaben an.
+
+                       Viele Grüße,
+                       Dominik George
+
+                       $sigdashes
+                       Dominik George (1. Vorstandsvorsitzender, pädagogischer Leiter)
+                       Teckids e.V. - Erkunden, Entdecken, Erfinden.
+                       https://www.teckids.org
+               EOF
+               continue
+       fi
+       if [[ $TODO = rn ]]; then
+               # Ablehnen wegen ungültigem Namen
+               heirloom-mailx ${TEST:+-d} -c advent@lists.teckids.org -r dominik.george@teckids.org -s "Anmeldung zum Adventskalender abgelehnt" "$eMail" >&2 <<-EOF
+                       Hallo $Vorname,
+
+                       leider konnten wir deine Anmeldung zum Adventskalender nicht annehmen.
+
+                       Du hast bei der Anmeldung leider einen ungültigen Namen angegeben.
+
+                       Wenn du noch mitmachen möchtest, melde dich bitte bis spätestens 1.12. noch
+                       einmal mit richtigen Angaben an.
+
+                       Viele Grüße,
+                       Dominik George
+
+                       $sigdashes
+                       Dominik George (1. Vorstandsvorsitzender, pädagogischer Leiter)
+                       Teckids e.V. - Erkunden, Entdecken, Erfinden.
+                       https://www.teckids.org
+               EOF
+               continue
+       fi
+
+       # Anlegen oder updaten, Gruppe hinzufügen, etc.
+       # Message-ID Vorname Nachname Geburtsdatum Alter Schulname Schulort Klasse eMail Kontaktumfang Account Benutzername Kanal OK_Eltern OK_Datenschutz PW-Hash
+
+       if [[ $Account = new ]]; then
+               if [[ $TODO = l ]]; then
+                       dn="cn=$Vorname $Nachname+uid=$Benutzername,ou=People,dc=teckids,dc=org"
+               else
+                       dn="cn=$Vorname $Nachname+uid=$Benutzername,ou=Kids,ou=People,dc=teckids,dc=org"
+               fi
+
+               cat <<-EOF
+                       dn: $dn
+                       changetype: add
+                       objectClass: inetOrgPerson
+                       objectClass: posixAccount
+                       objectClass: shadowAccount
+                       objectClass: teckidsPerson
+                       cn: $Vorname $Nachname
+                       givenName: $Vorname
+                       sn: $Nachname
+                       dateOfBirth: $Geburtsdatum
+                       o: $Schulname
+                       ou: $Klasse
+                       l: $Schulort
+                       mail: $eMail
+                       uid: $Benutzername
+                       uidNumber: $(( ++highest_uid ))
+                       userPassword: ${PWHash}
+                       gidNumber: 100
+                       loginShell: /bin/bash
+                       homeDirectory: /home/$Benutzername
+                       anmMessageId: <${MessageID}>
+
+               EOF
+       else
+               if ! dnline=$(ldapsearch -QLLL "(uid=$Benutzername)" dn); then
+                       print -ru2 -- "DN kaputt für $Benutzername ($?)"
+                       dnline='dn: invalid,dc=teckids,dc=org'
+               fi
+               dnline=$(print -r -- "$dnline" | \
+                   tr '\n' $'\a' | sed -e $'s/\a //g' | tr $'\a' '\n' | \
+                   head -n 1)
+               dn=${dnline#* }
+               [[ $dnline = dn::* ]] && dn=$(print -r -- "$dn" | base64 -di)
+               if [[ $dn != *,dc=teckids,dc=org ]]; then
+                       print -ru2 -- "DN kaputt für $Benutzername (#2)"
+                       dn='invalid,dc=teckids,dc=org'
+               fi
+
+               cat <<-EOF
+                       dn: $dn
+                       changetype: modify
+                       replace: o
+                       o: $Schulname
+                       -
+                       replace: l
+                       l: $Schulort
+                       -
+                       replace: ou
+                       ou: $Klasse
+                       -
+                       replace: mail
+                       mail: $eMail
+                       -
+
+               EOF
+       fi
+
+       cat <<-EOF
+               dn: cn=advent-2016,ou=Projekte,ou=Groups,dc=teckids,dc=org
+               changetype: modify
+               add: member
+               member: $dn
+               -
+
+       EOF
+
+       if [[ $Kontaktumfang = all ]]; then
+               g=kids
+               [[ $TODO = l ]] && g=lehrer
+
+               cat <<-EOF
+                       dn: cn=$g,ou=Groups,dc=teckids,dc=org
+                       changetype: modify
+                       add: member
+                       member: $dn
+                       -
+
+               EOF
+       fi
+
+       heirloom-mailx ${TEST:+-d} -c advent@lists.teckids.org -r dominik.george@teckids.org -s "Anmeldung zum Adventskalender" "$eMail" >&2 <<-EOF
+               Hallo $Vorname,
+
+               vielen Dank für deine Anmeldung zum MINT-Adventskalender 2016!
+
+               Dein Benutzername lautet: $Benutzername (nur Kleinbuchstabenund Ziffern)
+
+               Der Login und das Bearbeiten der Aufgaben wird ab dem
+               1.12. um etwa 13 Uhr funktionieren.
+
+               Wenn du Fragen oder Probleme hast, schreibe uns jederzeit eine
+               E-Mail an advent@lists.teckids.org!
+
+               Viele Grüße,
+               Dominik George
+
+               $sigdashes
+               Dominik George (1. Vorstandsvorsitzender, pädagogischer Leiter)
+               Teckids e.V. - Erkunden, Entdecken, Erfinden.
+               https://www.teckids.org
+       EOF
+done
diff --git a/mksh/teckids/advent/parsemail.sh b/mksh/teckids/advent/parsemail.sh
new file mode 100644 (file)
index 0000000..cb7db6f
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2014, 2016
+#      Dominik George <dominik.george@teckids.org>
+# Copyright © 2016
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+cr=$'\r'
+
+while :; do
+       s=0
+       a=0
+       while IFS= read -r -- line; do
+               if (( s == 0 )); then
+                       if [[ $line = "Message-Id: "* ]]; then
+                               msgid=${line#Message-Id: }
+                       elif [[ -z $line ]]; then
+                               s=1
+                               b64=
+                       fi
+               elif (( s == 1 )); then
+                       if [[ -z $line ]]; then
+                               a=1
+                               break
+                       fi
+                       b64+=$line
+               fi
+       done
+       (( a )) || exit 0
+
+       print -r -- "$b64" | base64 -d | while IFS= read -r -- line; do
+               line=${line%$cr}
+               if [[ $line = *([ ])*([! :])": "* ]]; then
+                       val=${line##*([ ])*([! :]):*( )}
+                       val=${val%%*( )}
+                       print -rn -- "$val|"
+               fi
+       done
+
+       print -r -- "$msgid"
+done
diff --git a/mksh/teckids/advent/parseparsemail.sh b/mksh/teckids/advent/parseparsemail.sh
new file mode 100644 (file)
index 0000000..3f9fdbb
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2014
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+myname=$(git config user.name)
+myemail=$(git config user.email)
+
+cat parsemail.csv |&
+while IFS="|" read -rp -- vorname nachname geschlecht gebdat alter schule schulort klasse jgst email kontakt benutzer pwhash msgid; do
+       print -- "$vorname\n$nachname\n$gebdat\n$email\n\n\n\n$schule\n$schulort\n$klasse" | teckids addperson -K
+       print -- "$geschlecht\n$jgst\n$alter" | teckids projadd +V "$msgid"
+       teckids intpasswd -U "$benutzer" -P "$pwhash"
+       [[ $kontakt = all ]] && teckids groupadd kids
+       cat <<-EOF | mail \
+           -a "From: $myname <$myemail>" \
+           -a "Content-Type: text/plain; charset=utf-8" \
+           -a "In-Reply-To: $msgid" \
+           -a "References: $msgid" \
+           -c "advent@lists.teckids.org" \
+           -s "Re: [Teckids advent] Anmeldung [Advent] von $vorname $nachname" \
+           "$email"
+               Hallo $vorname,
+
+               schön, dass du beim Teckids-Adventskalender 2014 mitmachst!
+
+               Wir haben deinen Benutzerzugang angelegt. Ab dem 1.12.2014 kannst du dich damit
+               im Adventskalender anmelden. Bitte beachte, dass du eventuell einen anderen
+               Benutzernamen bekommen hast.
+
+                   Benutzername:     $benutzer      (nur Kleinbuchstaben!)
+
+               Viele Grüße und viel Erfolg,
+               Dominik George (Nik)
+       EOF
+done
diff --git a/mksh/teckids/intern/anmparse b/mksh/teckids/intern/anmparse
new file mode 100644 (file)
index 0000000..a09e9b7
--- /dev/null
@@ -0,0 +1,171 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Parse an ⚠already MUA-decoded⚠ Teckids-MirWebseite Anmeldeformular
+# from stdin. When called from anmparsemaildir, $origfilename is the
+# (relative to $PWD) pathname of the current item, and $msgid is the
+# Message-ID, without angle brackets, and $outputfilename is where a
+# CSV row should be appended.
+
+export LC_ALL=C.UTF-8
+unset LANGUAGE
+set -U
+
+set -A fieldnames -- Vorname Nachname Geburtsdatum Alter \
+    Schulname Schulort Klasse eMail Kontaktumfang Account \
+    Benutzername Kanal OK_Eltern OK_Datenschutz PW-Hash \
+    Bemerkungen Referer Formular-ID
+set -A fieldvars
+typeset -l lc
+for lc in "${fieldnames[@]}"; do
+       fieldvars+=("${lc//-}")
+done
+fieldvarcnt=${#fieldvars[*]}
+
+function csvencode {
+       REPLY=${1//$'\n'/$'\r'}
+       [[ $REPLY = +([\(-~!\#\$%&]) ]] || REPLY=\"${REPLY//\"/\"\"}\"
+}
+
+if [[ $1 = -o ]]; then
+       print -n "TODO  Message-ID"
+       for x in "${fieldnames[@]}"; do
+               print -nr -- "  ${|csvencode "$x";}"
+       done
+       print
+       exit 0
+fi
+
+s=$(cat)
+nl=$'\n'
+
+function die {
+       print -ru2 -- "E: $*"
+       exit 1
+}
+
+function parse_new {
+       print -r -- "${s#*'Eingesendetes Anmeldeformular'*$nl$nl}" |&
+
+       while IFS= read -pr line; do
+               if [[ $line = *( )Bemerkungen: ]]; then
+                       bemerkungen=
+                       while IFS= read -pr line; do
+                               [[ -n $line ]] || break
+                               [[ $line = '| '* ]] || die missing empty line \
+                                   between Bemerkungen and next field group
+                               bemerkungen=${bemerkungen:+$bemerkungen$nl}${line#'| '}
+                       done
+                       continue
+               elif [[ -z $line ]]; then
+                       continue
+               elif [[ $line = +(_) ]]; then
+                       break
+               fi
+               i=${#fieldnames[*]}
+               while (( i-- )); do
+                       #XXX TODO: multiline fields: 「*( )"${fieldnames[i]}:」
+                       [[ $line = *( )"${fieldnames[i]}: "* ]] || continue
+                       nameref V=${fieldvars[i]}
+                       V=${line#*: }
+                       V=${V%%+( )}
+                       break
+               done
+               (( i < 0 )) && print -ru2 -- "W: ignoring line: $line"
+       done
+}
+
+function parse_old {
+       fieldnames+=('eMail Kind')
+       fieldvars+=(email)
+
+       print -r -- "${s#*'Eingesendetes Anmeldeformular'*$nl$nl}" |&
+
+       while IFS= read -pr line; do
+               if [[ $line = *( )Bemerkungen: ]]; then
+                       bemerkungen=
+                       while IFS= read -pr line; do
+                               [[ -n $line ]] || break
+                               [[ $line = '| '* ]] || die missing empty line \
+                                   between Bemerkungen and next field group
+                               bemerkungen=${bemerkungen:+$bemerkungen$nl}${line#'| '}
+                       done
+                       continue
+               elif [[ $line = 'Geburtsdatum: '*' Alter: '* ]]; then
+                       geburtsdatum=${line##'Geburtsdatum:'+( )}
+                       geburtsdatum=${geburtsdatum%%+( )Alter: *}
+                       alter=${line##*' Alter:'+( )}
+                       alter=${alter%%+( )}
+                       continue
+               elif [[ $line = 'Schulort: '*' Klasse: '* ]]; then
+                       schulort=${line##'Schulort:'+( )}
+                       schulort=${schulort%%+( )Klasse: *}
+                       klasse=${line##*' Klasse:'+( )}
+                       klasse=${klasse%%+( )}
+                       continue
+               elif [[ $line = OK:+( )Eltern\ *,\ Datenschutz\ * ]]; then
+                       ok_eltern=${line#* Eltern }
+                       ok_datenschutz=${ok_eltern#*, Datenschutz }
+                       ok_eltern=${ok_eltern%%, Datenschutz *}
+                       ok_eltern=${ok_eltern%%+( )}
+                       ok_datenschutz=${ok_datenschutz%%+( )}
+                       continue
+               elif [[ -z $line ]]; then
+                       continue
+               elif [[ $line = +(_) ]]; then
+                       break
+               fi
+               i=${#fieldnames[*]}
+               while (( i-- )); do
+                       [[ $line = *( )"${fieldnames[i]}: "* ]] || continue
+                       nameref V=${fieldvars[i]}
+                       V=${line#*: }
+                       V=${V##+( )}
+                       V=${V%%+( )}
+                       break
+               done
+               (( i < 0 )) && print -ru2 -- "W: ignoring line: $line"
+       done
+}
+
+if [[ $s = *' Vorname: '* ]]; then
+       parse_new
+elif [[ $s = *"${nl}Vorname:  "* ]]; then
+       parse_old
+else
+       die could not detect format
+fi
+typeset +n V
+unset i lc line s V
+
+if [[ -z $outputfilename ]]; then
+       typeset -p #debug
+       exit 0
+fi
+
+o=".   ${|csvencode "$msgid";}"
+i=-1
+while (( ++i < fieldvarcnt )); do
+       nameref V=${fieldvars[i]}
+       o+="    ${|csvencode "$V";}"
+done
+print -r -- "$o" >>"$outputfilename"
+exit 0
diff --git a/mksh/teckids/intern/anmparsemaildir b/mksh/teckids/intern/anmparsemaildir
new file mode 100644 (file)
index 0000000..49e6548
--- /dev/null
@@ -0,0 +1,128 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Decode all command line arguments (assumed to be Maildir files) to
+# 8-bit plaintext with RFC822 header, then pass that, with the file‐
+# name exported as $origfilename, to anmparse.
+#
+# Exit status:
+# – bit0 ⇒ error in at least one file
+# – bit1 ⇒ anmparse returned nōnzero for at least one file
+# – bit2 ⇒ errors related to the CSV output file
+
+export LC_ALL=C
+unset LANGUAGE
+set +U
+
+me=$(realpath "$0/..")
+nl=$'\n'
+rv=0
+typeset -l l
+
+. "$me/../../www/mk/progress-bar"
+
+usage() {
+       print -ru2 "Usage: $0 -o output.csv /path/to/maildir/*"
+       exit ${1:-1}
+}
+
+outputfilename=
+while getopts "ho:" c; do
+       case $c {
+       (h)     usage 0 ;;
+       (o)     outputfilename=$OPTARG ;;
+       (*)     usage ;;
+       }
+done
+shift $((OPTIND - 1))
+
+[[ -n $outputfilename ]] || usage
+
+if [[ -s $outputfilename ]]; then
+       print -ru2 "E: output file ${outputfilename@Q} already exists"
+       exit 4
+fi
+if ! mksh "$me/anmparse" -o >"$outputfilename" || [[ ! -s $outputfilename ]]; then
+       print -ru2 "E: output file ${outputfilename@Q} could not be created"
+       exit 4
+fi
+
+export msgid origfilename outputfilename
+init_progress_bar $#
+for origfilename in "$@"; do
+       draw_progress_bar
+
+       b=$(tr -d '\r' <"$origfilename")
+       h=${b%%"$nl$nl"*}
+       b=${b#*"$nl$nl"}
+       h=${h//"$nl "/ }
+       l=$nl$h$nl
+
+       if [[ $l != *"${nl}message-id: <"+([ -~])">$nl"* ]]; then
+               print -ru2 "E: missing Message-ID for ${origfilename@Q}, skipping"
+               (( rv |= 1 ))
+               continue
+       fi
+       msgid=${h##*"$nl"[Mm][Ee][Ss][Ss][Aa][Gg][Ee]-[Ii][Dd]': <'}
+       msgid=${msgid%%'>'*}
+
+       if [[ $l != *"${nl}content-type: text/plain; charset="?([\'\"])'utf-8'?([\'\"])"$nl"* ]]; then
+               print -ru2 "E: wrong Content-Type for ${origfilename@Q}, skipping"
+               (( rv |= 1 ))
+               continue
+       fi
+
+       if [[ $l = *"${nl}content-transfer-encoding: base64$nl"* ]]; then
+               if ! b=$(print -r -- "$b" | base64 -di); then
+                       print -ru2 "E: base64 decode error for ${origfilename@Q}, skipping"
+                       (( rv |= 1 ))
+                       continue
+               fi
+               b=${b//$'\r'}
+       elif [[ $l = *"${nl}content-transfer-encoding: quoted-printable$nl"* ]]; then
+               if ! b=$(print -r -- "$b" | perl -pe \
+                   's/=(\n|[a-fA-F0-9]{2})/$1 eq "\n" ? "" : pack("C",hex($1))/eg'); then
+                       print -ru2 "E: qp decode error for ${origfilename@Q}, skipping"
+                       (( rv |= 1 ))
+                       continue
+               fi
+               b=${b//$'\r'}
+       elif [[ $l = *"${nl}content-transfer-encoding: "@(8bit|7bit|binary)"$nl"* ]]; then
+               :
+       elif [[ $l = *"${nl}content-transfer-encoding:"* ]]; then
+               print -ru2 "E: wrong Content-Transfer-Encoding for ${origfilename@Q}, skipping"
+               (( rv |= 1 ))
+               continue
+       fi
+
+       x=$(print -r -- "$h$nl$nl$b" | mksh "$me/anmparse")
+       rc=$?
+       if [[ -n $x ]]; then
+               print -ru2 "I: anmparse output for ${origfilename@Q} follows:"
+               print -r -- "$x" | sed 's/^/N: /'
+       fi
+       if (( rc )); then
+               print -ru2 "W: anmparse errorlevel $rc for ${origfilename@Q}"
+               (( rv |= 2 ))
+       fi
+done
+done_progress_bar
+exit $rv
diff --git a/mksh/teckids/intern/csvfix-1 b/mksh/teckids/intern/csvfix-1
new file mode 100644 (file)
index 0000000..239432f
--- /dev/null
@@ -0,0 +1,5 @@
+s=$'\x1C'
+while IFS=$s read l1 o1 l2 o2; do
+       [[ $l1 = "$l2" && $o1 = "$o2" ]] && continue
+       print -r -- "$l1$s$l2$s$o1$s$o2"
+done <schools.ssv >schools2.ssv
diff --git a/mksh/teckids/intern/csvfix-2 b/mksh/teckids/intern/csvfix-2
new file mode 100644 (file)
index 0000000..c02b86a
--- /dev/null
@@ -0,0 +1,28 @@
+sort -u <schools2.ssv | while IFS=$'\x1C' read l1 l2 o1 o2; do
+       ldapsearch -QLLL "(&(l=$l1)(o=$o1))" dn | \
+           tr '\n' $'\a' | sed -e $'s/\a //g' | tr $'\a' '\n' |&
+       while IFS= read -pr foo; do
+               [[ -n $foo ]] || continue
+               if [[ $foo != 'dn:'* ]]; then
+                       print -ru2 "E: $foo"
+                       exit 1
+               fi
+               dn=${foo#* }
+               [[ $foo = dn::* ]] && dn=$(print -r -- "$dn" | base64 -di)
+               if [[ $dn != *,dc=teckids,dc=org ]]; then
+                       print -ru2 "E: dn kaputt: $dn / $foo"
+                       exit 1
+               fi
+               cat <<-EOF
+                       dn: $dn
+                       changetype: modify
+                       replace: o
+                       o: $o2
+                       -
+                       replace: l
+                       l: $l2
+                       -
+
+               EOF
+       done
+done >schools3.ldif
diff --git a/mksh/teckids/intern/dmppgsql.sh b/mksh/teckids/intern/dmppgsql.sh
new file mode 100644 (file)
index 0000000..03aed67
--- /dev/null
@@ -0,0 +1,85 @@
+#!/bin/mksh
+# $Id: dmppgsql.sh 4369 2015-05-06 14:51:42Z tglase $
+#-
+# Copyright © 2015
+#      Thorsten Glaser <t.glaser@tarent.de>
+#      Dominik George <dominik.george@teckids.org>
+# Copyright © 2007
+#      Benjamin Kix <b.kix@tarent.de>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Backup all databases in the main (default) PostgreSQL cluster from
+# the local system; keep one old backup. If the backup process fails
+# both older dumps will not be touched, and this script exits with a
+# nōn-zero errorlevel.
+
+export LC_ALL=C
+unset LANGUAGE
+
+die() {
+       local rv=$1
+       shift
+       print -ru2 -- "$0: E: $*"
+       exit "$rv"
+}
+
+[[ -o ?pipefail ]] || die 2 'mksh too old'
+set -o pipefail
+
+(( USER_ID )) && die 2 'need superuser privs'
+
+#cd ~postgres || die 2 'HOME directory of management user postgres doesn’t exist'
+#cd dumps || die 2 'dumps subdirectory doesn’t exist'
+#dst=$(realpath .) || die 2 'huh?'
+dst=/var/backups/local/postgres
+cd /
+umask 077
+
+set -A databases
+ndatabases=0
+sudo -u postgres psql -At -P recordsep_zero \
+    -c "SELECT datname FROM pg_database WHERE datistemplate='f';" |&
+while IFS= read -r -d '' -p; do
+       databases[ndatabases++]=$REPLY
+done
+
+(( ndatabases )) || die 1 'no databases found'
+
+gzdatabases=" Syslog "
+
+rv=0
+for database in "${databases[@]}"; do
+       print -nr -- "Processing $database... "
+       rm -f "$dst/$database.sql*"
+       usegz=
+       [[ $gzdatabases = *" $database "* ]] && usegz=.gz
+       if ! sudo -u postgres pg_dump -Fp "$database" | \
+           if [[ -n $usegz ]]; then
+               gzip -n9
+           else
+               cat
+           fi \
+           >"$dst/$database.sql$usegz"; then
+               rv=1
+               print -r -- FAILED
+       else
+               print -r -- ok
+       fi
+done
+(( rv )) && exit $rv
+print Successfully completed.
+exit 0
diff --git a/mksh/teckids/intern/fix-slapd-crc b/mksh/teckids/intern/fix-slapd-crc
new file mode 100755 (executable)
index 0000000..2bc7711
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2014
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+script=$(realpath "$(dirname "$0")"/ldif_crc.sh)
+[[ -s $script && -x $script ]] || exit 1
+
+cd "${1:-/etc/ldap/slapd.d}" || exit 1
+find . -type f -print0 |&
+while IFS= read -pr -d '' name; do
+       if [[ $name != *.ldif ]]; then
+               print -ru2 -- "W: '$name' not *.ldif"
+               continue
+       fi
+       content=$(cat "$name"; echo x)
+       print -nr -- "${content%x}" | "$script" >"$name"
+done
diff --git a/mksh/teckids/intern/ldap2for b/mksh/teckids/intern/ldap2for
new file mode 100644 (file)
index 0000000..f26bcbc
--- /dev/null
@@ -0,0 +1,83 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2013, 2014
+#      Thorsten Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# ldap2for script ldapopts -- attributes
+
+export LC_ALL=C
+unset LANGUAGE
+wd=$(realpath .)
+cd "$(dirname "$0")"
+
+if [[ $HOSTNAME != terra ]]; then
+       function ldapsearch {
+               local _cmd=ldapsearch _x
+
+               for _x in "$@"; do
+                       _cmd+=" ${_x@Q}"
+               done
+               ssh terra.teckids.org "$_cmd"
+       }
+fi
+
+PATH=$PWD/../../www/mk:$PATH . assoldap.ksh
+
+if (( $# < 3 )); then
+       print -u2 Syntax error
+       exit 1
+fi
+script=$1
+shift
+set -A ldapopts
+while (( $# )); do
+       [[ $1 = -- ]] && break
+       ldapopts+=("$1")
+       shift
+done
+if [[ $1 != -- ]]; then
+       print -u2 Syntax error
+       exit 1
+fi
+shift
+set -A attributes -- "$@"
+for x in "${attributes[@]}"; do
+       [[ $x = [a-zA-Z]*([0-9a-zA-Z_]) ]] && continue
+       print -ru2 "Invalid field name ${x@Q}"
+       exit 1
+done
+
+asso_setldap_sasl res -- "${ldapopts[@]}"
+
+asso_loadk res
+cd "$wd"
+typeset -Uui16 -Z11 i=0
+for dn in "${asso_y[@]}"; do
+       export dn lfd_nr=${i#16#}
+       for k in "${attributes[@]}"; do
+               v=$(asso_getv res "$dn" "$k" 0)
+               [[ -n $v ]] || continue 2
+               eval $k=\$v
+               eval export $k
+       done
+       print -ru2 "Running script for #$lfd_nr: $dn"
+       mksh "$script" dn "${attributes[@]}"
+       let ++i
+done
+print -u2 "done"
diff --git a/mksh/teckids/intern/ldif_crc.sh b/mksh/teckids/intern/ldif_crc.sh
new file mode 100755 (executable)
index 0000000..a048f1e
--- /dev/null
@@ -0,0 +1,138 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# From: MirOS: src/lib/libc/hash/sfv.c,v 1.2 2007/08/19 12:03:13 tg Exp $
+#-
+# Copyright © 2014
+#      Thorsten “mirabilos” Glaser <thorsten.glaser@teckids.org>
+# Copyright © 2006
+#      Thorsten “mirabilos” Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un-
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person's immediate fault when using the work as intended.
+
+set -A sfv_tab \
+       0x00000000 0x77073096 0xee0e612c 0x990951ba \
+       0x076dc419 0x706af48f 0xe963a535 0x9e6495a3 \
+       0x0edb8832 0x79dcb8a4 0xe0d5e91e 0x97d2d988 \
+       0x09b64c2b 0x7eb17cbd 0xe7b82d07 0x90bf1d91 \
+       0x1db71064 0x6ab020f2 0xf3b97148 0x84be41de \
+       0x1adad47d 0x6ddde4eb 0xf4d4b551 0x83d385c7 \
+       0x136c9856 0x646ba8c0 0xfd62f97a 0x8a65c9ec \
+       0x14015c4f 0x63066cd9 0xfa0f3d63 0x8d080df5 \
+       0x3b6e20c8 0x4c69105e 0xd56041e4 0xa2677172 \
+       0x3c03e4d1 0x4b04d447 0xd20d85fd 0xa50ab56b \
+       0x35b5a8fa 0x42b2986c 0xdbbbc9d6 0xacbcf940 \
+       0x32d86ce3 0x45df5c75 0xdcd60dcf 0xabd13d59 \
+       0x26d930ac 0x51de003a 0xc8d75180 0xbfd06116 \
+       0x21b4f4b5 0x56b3c423 0xcfba9599 0xb8bda50f \
+       0x2802b89e 0x5f058808 0xc60cd9b2 0xb10be924 \
+       0x2f6f7c87 0x58684c11 0xc1611dab 0xb6662d3d \
+       0x76dc4190 0x01db7106 0x98d220bc 0xefd5102a \
+       0x71b18589 0x06b6b51f 0x9fbfe4a5 0xe8b8d433 \
+       0x7807c9a2 0x0f00f934 0x9609a88e 0xe10e9818 \
+       0x7f6a0dbb 0x086d3d2d 0x91646c97 0xe6635c01 \
+       0x6b6b51f4 0x1c6c6162 0x856530d8 0xf262004e \
+       0x6c0695ed 0x1b01a57b 0x8208f4c1 0xf50fc457 \
+       0x65b0d9c6 0x12b7e950 0x8bbeb8ea 0xfcb9887c \
+       0x62dd1ddf 0x15da2d49 0x8cd37cf3 0xfbd44c65 \
+       0x4db26158 0x3ab551ce 0xa3bc0074 0xd4bb30e2 \
+       0x4adfa541 0x3dd895d7 0xa4d1c46d 0xd3d6f4fb \
+       0x4369e96a 0x346ed9fc 0xad678846 0xda60b8d0 \
+       0x44042d73 0x33031de5 0xaa0a4c5f 0xdd0d7cc9 \
+       0x5005713c 0x270241aa 0xbe0b1010 0xc90c2086 \
+       0x5768b525 0x206f85b3 0xb966d409 0xce61e49f \
+       0x5edef90e 0x29d9c998 0xb0d09822 0xc7d7a8b4 \
+       0x59b33d17 0x2eb40d81 0xb7bd5c3b 0xc0ba6cad \
+       0xedb88320 0x9abfb3b6 0x03b6e20c 0x74b1d29a \
+       0xead54739 0x9dd277af 0x04db2615 0x73dc1683 \
+       0xe3630b12 0x94643b84 0x0d6d6a3e 0x7a6a5aa8 \
+       0xe40ecf0b 0x9309ff9d 0x0a00ae27 0x7d079eb1 \
+       0xf00f9344 0x8708a3d2 0x1e01f268 0x6906c2fe \
+       0xf762575d 0x806567cb 0x196c3671 0x6e6b06e7 \
+       0xfed41b76 0x89d32be0 0x10da7a5a 0x67dd4acc \
+       0xf9b9df6f 0x8ebeeff9 0x17b7be43 0x60b08ed5 \
+       0xd6d6a3e8 0xa1d1937e 0x38d8c2c4 0x4fdff252 \
+       0xd1bb67f1 0xa6bc5767 0x3fb506dd 0x48b2364b \
+       0xd80d2bda 0xaf0a1b4c 0x36034af6 0x41047a60 \
+       0xdf60efc3 0xa867df55 0x316e8eef 0x4669be79 \
+       0xcb61b38c 0xbc66831a 0x256fd2a0 0x5268e236 \
+       0xcc0c7795 0xbb0b4703 0x220216b9 0x5505262f \
+       0xc5ba3bbe 0xb2bd0b28 0x2bb45a92 0x5cb36a04 \
+       0xc2d7ffa7 0xb5d0cf31 0x2cd99e8b 0x5bdeae1d \
+       0x9b64c2b0 0xec63f226 0x756aa39c 0x026d930a \
+       0x9c0906a9 0xeb0e363f 0x72076785 0x05005713 \
+       0x95bf4a82 0xe2b87a14 0x7bb12bae 0x0cb61b38 \
+       0x92d28e9b 0xe5d5be0d 0x7cdcefb7 0x0bdbdf21 \
+       0x86d3d2d4 0xf1d4e242 0x68ddb3f8 0x1fda836e \
+       0x81be16cd 0xf6b9265b 0x6fb077e1 0x18b74777 \
+       0x88085ae6 0xff0f6a70 0x66063bca 0x11010b5c \
+       0x8f659eff 0xf862ae69 0x616bffd3 0x166ccf45 \
+       0xa00ae278 0xd70dd2ee 0x4e048354 0x3903b3c2 \
+       0xa7672661 0xd06016f7 0x4969474d 0x3e6e77db \
+       0xaed16a4a 0xd9d65adc 0x40df0b66 0x37d83bf0 \
+       0xa9bcae53 0xdebb9ec5 0x47b2cf7f 0x30b5ffe9 \
+       0xbdbdf21c 0xcabac28a 0x53b39330 0x24b4a3a6 \
+       0xbad03605 0xcdd70693 0x54de5729 0x23d967bf \
+       0xb3667a2e 0xc4614ab8 0x5d681b02 0x2a6f2b94 \
+       0xb40bbe37 0xc30c8ea1 0x5a05df1b 0x2d02ef8d
+
+typeset -Z11 -Uui16 sfv_v
+function sfv_init {
+       sfv_v=0xFFFFFFFF
+}
+function sfv_add {
+       [[ -o utf8-mode ]]; local u=$?
+       set +U
+       local s
+       if (( $# )); then
+               read -raN-1 s <<<"$*"
+               unset s[${#s[*]}-1]
+       else
+               read -raN-1 s
+       fi
+       local -i i=0 n=${#s[*]}
+
+       while (( i < n )); do
+               ((# sfv_v = (sfv_v >> 8) ^ sfv_tab[(sfv_v ^ s[i++]) & 0xFF] ))
+       done
+
+       (( u )) || set -U
+}
+function sfv_finish {
+       (( sfv_v = ~sfv_v ))
+}
+
+# pipe LDIF in, it will be checksummed and output
+
+nl=$'\n'
+
+sfv_init
+saw_comments=0
+content=
+while IFS= read -r line; do
+       (( saw_comments )) || if [[ $line = '#'* ]]; then
+               continue
+       else
+               saw_comments=1
+       fi
+       content+=$line$nl
+       sfv_add "$line$nl"
+done
+sfv_finish
+typeset -Z11 -Uli16 sfv_v
+print -r -- "# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify."
+print -r -- "# CRC32 ${sfv_v#16#}"
+print -nr -- "$content"
diff --git a/mksh/teckids/intern/mailcheck b/mksh/teckids/intern/mailcheck
new file mode 100644 (file)
index 0000000..c43b614
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015, 2016
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# apt-get install pcregrep
+#
+# TODO:
+# - Hostteil prüfen, sodaß dotted-quad (Legacy IP address) verboten wird
+
+pcregrep -v '^no name <(?=.{1,254}\$)(?=.{1,64}@)[-!#-'\''*+/-9=?A-Z^-~]+(\.[-!#-'\''*+/-9=?A-Z^-~]+)*@[0-9A-Za-z]([-0-9A-Za-z]{0,61}[0-9A-Za-z])?(\.[0-9A-Za-z]([-0-9A-Za-z]{0,61}[0-9A-Za-z])?)*>$' "$@"
diff --git a/mksh/teckids/intern/mbox2mid b/mksh/teckids/intern/mbox2mid
new file mode 100644 (file)
index 0000000..fb21b7a
--- /dev/null
@@ -0,0 +1,91 @@
+# -*- mode: sh -*-
+#-
+# Copyright © 2014
+#      Thorsten Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Creates a Message-ID to Subject list from an mbox (or mbx).
+
+function Lb64decode {
+       [[ -o utf8-mode ]]; local u=$?
+       set +U
+       local c s="$*" t=
+       [[ -n $s ]] || { s=$(cat; print x); s=${s%x}; }
+       local -i i=0 j=0 n=${#s} p=0 v x
+       local -i16 o
+
+       while (( i < n )); do
+               c=${s:(i++):1}
+               case $c {
+               (=)     break ;;
+               ([A-Z]) (( v = 1#$c - 65 )) ;;
+               ([a-z]) (( v = 1#$c - 71 )) ;;
+               ([0-9]) (( v = 1#$c + 4 )) ;;
+               (+)     v=62 ;;
+               (/)     v=63 ;;
+               (*)     continue ;;
+               }
+               (( x = (x << 6) | v ))
+               case $((p++)) {
+               (0)     continue ;;
+               (1)     (( o = (x >> 4) & 255 )) ;;
+               (2)     (( o = (x >> 2) & 255 )) ;;
+               (3)     (( o = x & 255 ))
+                       p=0
+                       ;;
+               }
+               t+=\\x${o#16#}
+               (( ++j & 4095 )) && continue
+               print -n $t
+               t=
+       done
+       print -n $t
+       (( u )) || set -U
+}
+
+unset mid
+unset sbj
+tr '\n' '\ 1' | sed -e 's/\r\ 1/\ 1/g' -e 's/\ 1[        ]/ /g' | tr '\ 1' '\n' | \
+    while IFS= read -r line; do
+       typeset -l llow=$line
+       case x$llow {
+       (x)
+               [[ -n $mid && -n $sbj ]] && print -r -- \
+                   "$mid       $sbj"
+               unset mid
+               unset sbj
+               ;;
+       (xmessage-id:*)
+               mid=${line#*:}
+               mid=${mid##+([   ])}
+               ;;
+       (xsubject:*)
+               sr=${line#*:}
+               sr=${sr##+([     ])}
+               sr=${sr//'?='+([         ])'=?'/'?==?'}
+               sbj=
+               while [[ $sr = *'=?'[Uu][Tt][Ff]'-8?B?'*'?='* ]]; do
+                       sl=${sr%%'?='*}
+                       sr=${sr#"$sl"'?='}
+                       sm=${sl##*'=?'[Uu][Tt][Ff]'-8?B?'}
+                       sl=${sl%'=?'*}
+                       sbj=$sbj$sl$(Lb64decode "$sm")
+               done
+               sbj=$sbj$sr
+               ;;
+       }
+done
diff --git a/mksh/teckids/intern/parse-bb.sh b/mksh/teckids/intern/parse-bb.sh
new file mode 100644 (file)
index 0000000..a80ab69
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Quick hack to extract school information from web spider – this is
+# Bundesland-specific; here: Brandenburg.
+#
+# apt-get install tidy xmlstarlet
+
+export LC_ALL=C.UTF-8
+
+for x in *.htm; do
+       <"$x" fgrep -v -e '<div id="cp">' | \
+           tidy -q -asxhtml -w 0 -utf8 --quote-nbsp no 2>/dev/null | \
+           xmlstarlet fo -D | \
+           xmlstarlet sel -T -t -o "Dateinummer=${x%.*}" -n \
+           -t -o 'Nummer=' -c "//*[.='Schulnummer']/../*[2]" -n \
+           -t -o 'Name=' -c "//*[@id='content']/*[1]/*[2]" -n \
+           -t -o 'Typ=' -c "//*[.='Schulform']/../*[2]" -n \
+           -t -o 'Adresse=' -m "//*[.='Adresse']/../*[2]" \
+             --var linebreak -n --break -v "translate(., \$linebreak, '|')" -n \
+           -t -o 'eMail=' -c "//*[.='E-Mail']/../*[2]/*[@href]" -n \
+           -n
+done
diff --git a/mksh/teckids/intern/parse-be.sh b/mksh/teckids/intern/parse-be.sh
new file mode 100644 (file)
index 0000000..862dfdf
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Quick hack to extract school information from web spider – this is
+# Bundesland-specific; here: Berlin.
+#
+# apt-get install tidy xmlstarlet
+
+export LC_ALL=C.UTF-8
+
+for x in *.htm; do
+       <"$x" fgrep -v -e '<div id="cp">' | \
+           tidy -q -asxhtml -w 0 -utf8 2>/dev/null | \
+           xmlstarlet sel -T -t -o "Nummer=${x%.*}" -n \
+           -t -o 'Name=' -c "//*[@id='ctl00_ContentPlaceHolderMenuListe_lblSchulname']" -n \
+           -t -o 'Typ=' -c "//*[@id='ctl00_ContentPlaceHolderMenuListe_lblSchulart']" -n \
+           -t -o 'Adresse=' -c "//*[@id='ctl00_ContentPlaceHolderMenuListe_lblStrasse']" -n \
+           -t -o 'PLZ_Ort=' -c "//*[@id='ctl00_ContentPlaceHolderMenuListe_lblOrt']" -n \
+           -t -o 'eMail=' -c "//*[@id='ctl00_ContentPlaceHolderMenuListe_HLinkEMail']" -n \
+           -n
+done
diff --git a/mksh/teckids/intern/parse-by.sh b/mksh/teckids/intern/parse-by.sh
new file mode 100644 (file)
index 0000000..a760e88
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Quick hack to extract school information from web spider – this is
+# Bundesland-specific; here: Bayern.
+#
+# apt-get install tidy xmlstarlet
+
+export LC_ALL=C.UTF-8
+
+for x in *.htm; do
+       <"$x" iconv -f cp1252 -t utf-8 | \
+           tidy -q -asxhtml -w 0 -utf8 --quote-nbsp no 2>/dev/null | \
+           sed 's! xmlns="http://www.w3.org/1999/xhtml"!!' | \
+           xmlstarlet sel -T -t -o "Nummer=${x%.*}" -n \
+           -t -o 'Name=' -c '//span[@class="schulart_text"]/../h2' -n \
+           -t -o 'Typ=' -c '//span[@class="schulart_text"]' -n \
+           -t -o 'Rest=' -m '//span[@class="schulart_text"]/../p[1]' \
+             --var linebreak -n --break -v "translate(., \$linebreak, '\`')" -n \
+           -n
+done | while IFS= read -r line; do
+       case $line {
+       (Typ=Schulart:*)
+               print -r -- "Typ=${line##Typ=Schulart:*( )}"
+               ;;
+       (Rest=*)
+               line=\`${line#Rest=}\`
+               if [[ $line = *'`Adresse:'* ]]; then
+                       x=${line##*'`Adresse:'*( )}
+                       x=${x%%*( )\`*}
+                       if [[ $x = *', '* ]]; then
+                               ort=${x%%, *}
+                               adresse=${x#*, }
+                       else
+                               ort=\? adresse=$x
+                       fi
+                       if [[ $ort = +([0-9])\ * ]]; then
+                               plz=${ort%% *}
+                               ort=${ort#* }
+                       else
+                               plz=\?
+                       fi
+                       print -r -- "Adresse=$adresse"
+                       print -r -- "PLZ=$plz"
+                       print -r -- "Ort=$ort"
+               fi
+               if [[ $line = *'`SchulWeb-Nummer:'* ]]; then
+                       x=${line##*'`SchulWeb-Nummer:'*( )}
+                       x=${x%%*( )\`*}
+                       print -r -- "SchulWebNr=$x"
+               fi
+               if [[ $line = *'`Email:'* ]]; then
+                       x=${line##*'`Email:'*( )}
+                       x=${x%%*( )\`*}
+                       print -r -- "eMail=$x"
+               fi
+               ;;
+       (*)
+               print -r -- "$line"
+               ;;
+       }
+done
diff --git a/mksh/teckids/intern/parse-hb.sh b/mksh/teckids/intern/parse-hb.sh
new file mode 100644 (file)
index 0000000..df3682a
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Quick hack to extract school information from web spider – this is
+# Bundesland-specific; here: Bremen.
+#
+# apt-get install tidy xmlstarlet
+
+export LC_ALL=C.UTF-8
+
+for x in *.htm; do
+       <"$x" iconv -f cp1252 -t utf-8 | \
+           tidy -q -asxhtml -w 0 -utf8 --quote-nbsp no 2>/dev/null | \
+           sed 's! xmlns="http://www.w3.org/1999/xhtml"!!' | \
+           xmlstarlet sel -T -t -o "Nummer=${x%.*}" -n \
+           -t -o 'Name=' -m '//div[@class="main_article"]/h3[1]' \
+             --var linebreak -n --break -v "translate(., \$linebreak, ' ')" -n \
+           -t -o 'Adresse=' -m '//div[@class="kogis_main_visitenkarte"]/ul[1]/li[1]' \
+             --var linebreak -n --break -v "translate(., \$linebreak, '|')" -n \
+           -t -o 'eMail=' -c '//div[@class="kogis_main_visitenkarte"]/ul[2]/li[4]' -n \
+           -n
+done
diff --git a/mksh/teckids/intern/parse-sn.sh b/mksh/teckids/intern/parse-sn.sh
new file mode 100644 (file)
index 0000000..d29ad23
--- /dev/null
@@ -0,0 +1,98 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Quick hack to extract school information from web spider – this is
+# Bundesland-specific; here: Sachsen.
+#
+# apt-get install poppler-utils
+
+export LC_ALL=C.UTF-8
+
+function xtrim {
+       REPLY="$*"
+       REPLY=${REPLY##*([       ])}
+       REPLY=${REPLY%%*([       ])}
+       REPLY=${REPLY//+([       ])/ }
+}
+
+function xout {
+       print -r -- "${styp:-\?}        ${snam:-\?}     ${sadr:-\?}     ${splz:-\?}     ${sort:-\?}     ${seml:-\?}"
+}
+
+styp=Schultyp
+snam=Name
+sadr=Adresse
+splz=PLZ
+sort=Ort
+seml=eMail
+xout
+
+# sorted except for more specific before generic; Mittelschule after Oberschule
+set -A typen -- 'Berufliches Schulzentrum' Berufsbildende Berufsfachschul \
+    Berufsschul Ergänzungsschule Fachoberschule Fachschule Förderschul \
+    Förderzentrum 'Grund- und Oberschule' Grundschule Gymnasium \
+    Montessorischule Oberschule Mittelschule Universität Waldorfschule
+set -A typlv
+typeset -l lnam
+for lnam in "${typen[@]}"; do typlv+=("$lnam"); done
+
+s=0 last=
+pdftotext -layout sachsen.pdf - | while IFS= read -r line; do
+       case $s:$line {
+       (0:)
+               last=
+               ;;
+       (0:+( )Adresse\ *)
+               snam=${|xtrim "$last";}
+               sadr=${|xtrim "${line#+( )Adresse}";}
+               s=1
+               ;;
+       (0:*)
+               last+=\ $line
+               ;;
+       (1:+( )@(Telefon|Fax|E-Mail|Homepage)*)
+               print -ru2 -- parse error
+               exit 1
+               ;;
+       (1:*)
+               splz=${|xtrim "$line";}
+               sort=${splz#+([0-9]) }
+               if [[ $splz = "$sort" ]]; then
+                       splz=?
+               else
+                       splz=${splz%% *}
+               fi
+               s=2
+               ;;
+       (2:+( )E-Mail\ *)
+               seml=${|xtrim "${line#+( )E-Mail}";}
+               lnam=$snam
+               styp=?
+               for x in ${!typlv[*]}; do
+                       [[ $lnam = *${typlv[x]}* ]] || continue
+                       styp=${typen[x]}
+                       break
+               done
+               xout
+               s=0 last=
+               ;;
+       }
+done
diff --git a/mksh/teckids/intern/parse-st.sh b/mksh/teckids/intern/parse-st.sh
new file mode 100644 (file)
index 0000000..9926b31
--- /dev/null
@@ -0,0 +1,71 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Quick hack to extract school information from web spider – this is
+# Bundesland-specific; here: Sachsen-Anhalt.
+#
+# apt-get install tidy xmlstarlet
+
+export LC_ALL=C.UTF-8
+
+state=0 snam= sadr= splz= seml=
+<sachsen-anhalt.html iconv -f cp1252 -t utf-8 | \
+    tidy -q -asxhtml -w 0 -utf8 --quote-nbsp no 2>/dev/null | \
+    xmlstarlet pyx | \
+    while IFS= read -r line; do
+       case $state:$line {
+       ([012]:')hr')
+               let ++state
+               ;;
+       ([3569]:'Aclass standard01')
+               let ++state
+               ;;
+       (4:-*)
+               snam+=${line#-}
+               ;;
+       (4:*)
+               state=5
+               ;;
+       (7:-*)
+               sadr+=${line#-}
+               ;;
+       (7:*)
+               state=8
+               ;;
+       (8:'Ahref mailto:'*)
+               seml=${line#'Ahref mailto:'}
+               state=9
+               ;;
+       (10:-*)
+               splz+=${line#-}
+               ;;
+       (10:*)
+               cat <<-EOF
+                       Name=$snam
+                       Adresse=$sadr
+                       PLZ_Ort=$splz
+                       eMail=$seml
+
+               EOF
+               state=0 snam= sadr= splz= seml=
+               ;;
+       }
+done
diff --git a/mksh/teckids/intern/parse-th.sh b/mksh/teckids/intern/parse-th.sh
new file mode 100644 (file)
index 0000000..8f0a0a1
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      mirabilos <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Quick hack to extract school information from web spider – this is
+# Bundesland-specific; here: Thüringen.
+#
+# apt-get install python-html5lib python-lxml xmlstarlet
+
+export LC_ALL=C.UTF-8
+me=$(realpath "$0/..")
+
+for x in *.htm; do
+       <"$x" python "$me/html5tidy/html5tidy" | sed 's!<br/>!|!g' | xmlstarlet sel -T \
+           -t -o 'Name=' -c '//td[@class="tispo_cn_header"]' -n \
+           -t -o 'Schulinfo=' -c '//div[@id="schulportraet_ueberblick_detail_allgemein_stammdaten"]/../div[2]' -n \
+           -t -o 'Adresse=' -c '//div[@id="schulportraet_ueberblick_detail_allgemein_stammdaten"]/../div[3]' -n \
+           -t -o 'eMail=' -c '//span[@id="schulportraet_ueberblick_detail_allgemein_tag_data_encrypter_span_2"]' -n \
+           -n
+done | while IFS= read -r line; do
+       case $line {
+       (*=*)
+               k=${line%%=*}
+               v=${line#*=}
+               ;|
+       (Schulinfo=*' (Schul-Nr. '+([0-9])\)*([  ]))
+               n=${line##*' (Schul-Nr. '}
+               print -r -- "Nummer=${n%\)*}"
+               v=${v%' (Schul-Nr. '*}
+               ;|
+       (Schulinfo=*)
+               k=Typ
+               ;|
+       (eMail=*)
+               # TopdevUtil.decryptZD
+               v=${v//@( |#3b|3e#|o)}
+               read -raN-1 a <<<"$v"
+               v= i=0; (( n = ${#a[*]} - 1 ))
+               typeset -Uui1 C
+               while (( i < n )); do
+                       c=${a[i++]}
+                       (( C = (c < 0x30) || (c > 0x39) ? (c - 97 + 10) : (c - 0x30) ))
+                       c=${a[i++]}
+                       (( c = (c < 0x30) || (c > 0x39) ? (c - 97 + 10) : (c - 0x30) ))
+                       (( C = (C * 23 + c) / 2 ))
+                       v+=${C#1#}
+               done
+               ;|
+       (*=*)
+               v=${v##+([       ])}
+               v=${v%%+([       ])}
+               line=$k=$v
+               ;;
+       }
+       print -r -- "$line"
+done
diff --git a/mksh/teckids/intern/pty.ksh b/mksh/teckids/intern/pty.ksh
new file mode 100644 (file)
index 0000000..8ee69c8
--- /dev/null
@@ -0,0 +1,61 @@
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+pty_base='http://wr.ispsuite.portunity.de/webrequests'
+curl=$(whence -p curl)
+
+pty_wr() {
+       local product command data x
+       product=$1; shift
+       command=$1; shift
+       set -A data
+       for x in "$@"; do
+               data+=(-F "$x")
+       done
+
+       $curl \
+           -F "sCommand=$command" \
+           -F "sProductLogin=$pty_product" -F "sProductCode=$pty_secret" \
+           "${data[@]}" \
+           "$pty_base/$product/"
+}
+
+pty_creds() {
+       pty_product=$1
+       pty_secret=$2
+}
+
+pty_sms() {
+       local from to text
+       from=$1
+       to=$2
+       text=$3
+
+       pty_wr product-voip SendSMS "sSMSText=$text" "sSMSNumber=$to" "sSrcNumber=$from"
+}
+
+pty_fax() {
+       local to file email
+       to=$1
+       file=$2
+       email=$3
+
+       pty_wr product-voip SendFax "sDestination=$to" "sFax=@$file" "sEmail=$email" "mimeType=application/pdf"
+}
diff --git a/mksh/teckids/intern/teckids-ejmaint b/mksh/teckids/intern/teckids-ejmaint
new file mode 100755 (executable)
index 0000000..6908480
--- /dev/null
@@ -0,0 +1,159 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015, 2017
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+export LC_ALL=C
+unset LANGUAGE
+cd /usr/src/verein
+
+# Config
+domain=mercurius.teckids.org
+muc_domain=conference.$domain
+
+# Find the LDAP kit
+PATH=/usr/local/share/teckids/mk:$PWD/../../www/mk:$PATH . assoldap.ksh
+
+# Replace ldapsearch with SSH-wrapped function if not running on LDAP host
+HOSTNAME=${HOSTNAME:-$(hostname -s)}
+if [[ $HOSTNAME != terra ]]; then
+       function ldapsearch {
+               local _cmd=ldapsearch _x
+
+               for _x in "$@"; do
+                       _cmd+=" ${_x@Q}"
+               done
+               ssh terra.teckids.org "$_cmd"
+       }
+fi
+
+###### MUC room maintenance ############
+
+# Load existing MUC rooms
+rooms=$(ejabberdctl muc_online_rooms ${domain} | sed s/@.*// | tr \\n @)
+
+# Find and load chat rooms
+asso_setldap_sasl groups -- -b ou=Groups,dc=teckids,dc=org "(teckidsChatroom=TRUE)" cn description memberUid owner
+
+asso_loadk groups
+for group_dn in "${asso_y[@]}"; do
+       cn=$(asso_getv groups "$group_dn" cn 0)
+       desc=$(asso_getv groups "$group_dn" description 0)
+
+       # Strip ' from desc
+       desc=${desc//\'}
+
+       if ! [[ @$rooms@ = *"@$cn@"* ]]; then
+               # Create room if not existent
+               logger -t teckids-ejmaint "Creating room for $cn…"
+               ejabberdctl create_room "$cn" ${muc_domain} ${domain}
+       fi
+
+       # Set various MUC options
+       logger -t teckids-ejmaint "Setting room options for $cn…"
+       ejabberdctl change_room_option "$cn" ${muc_domain} title "'$desc'"
+       ejabberdctl change_room_option "$cn" ${muc_domain} description "'$desc'"
+       ejabberdctl change_room_option "$cn" ${muc_domain} allow_change_subj false
+       ejabberdctl change_room_option "$cn" ${muc_domain} persistent true
+       ejabberdctl change_room_option "$cn" ${muc_domain} anonymous false
+       ejabberdctl change_room_option "$cn" ${muc_domain} members_only true
+       ejabberdctl change_room_option "$cn" ${muc_domain} allow_user_invites true
+       ejabberdctl change_room_option "$cn" ${muc_domain} mam true
+
+       # Get members of LDAP group
+       mu_count=$(asso_getv groups "$group_dn" memberUid count)
+       set -A mus
+       i=0; while (( i < mu_count )); do
+               mus+=($(asso_getv groups "$group_dn" memberUid $i))
+               (( i++ ))
+       done
+
+       # Get currently affiliated MUC members
+       set -A members -- $(ejabberdctl get_room_affiliations "$cn" ${muc_domain} | sed "s/\t.*//")
+
+       # Iterate over MUC members and remove if not in LDAP
+       for member in "${members[@]}"; do
+               if ! [[ " ${mus[@]} " = *" $member "* ]]; then
+                       logger -t teckids-ejmaint "Removing user $member from room $cn…"
+                       ejabberdctl set_room_affiliation "$cn" ${muc_domain} "$member@${domain}" none
+               fi
+       done
+
+       # Iterate over LDAP members and add affiliation if not set yet
+       for mu in "${mus[@]}"; do
+               if ! [[ " ${members[@]} " = *" $mu "* ]]; then
+                       logger -t teckids-ejmaint "Adding user $mu to room $cn…"
+                       ejabberdctl set_room_affiliation "$cn" ${muc_domain} "$mu@${domain}" member
+               fi
+       done
+
+       # Get owners of LDAP group
+       o_count=$(asso_getv groups "$group_dn" owner count)
+       set -A os
+       i=0; while (( i < o_count )); do
+               dn="$(asso_getv groups "$group_dn" owner $i)"
+               # Get owner object from LDAP
+               asso_setldap_sasl owner -- -b "$dn" -s base cn uid memberUid
+
+               # Check whether owner is a person or a group
+               if [[ $dn = *,ou=People,* ]]; then
+                       # Add single uid to owners
+                       os+=($(asso_getv owner "$dn" uid 0))
+               else
+                       # Iterate over group member uids
+                       mu_count=$(asso_getv owner "$dn" memberUid count)
+                       j=0; while (( j < mu_count )); do
+                               os+=($(asso_getv owner "$dn" memberUid $j))
+                               (( j++ ))
+                       done
+               fi
+
+               (( i++ ))
+       done
+
+       # Iterate over owners and raise affiliation to owner
+       for o in "${os[@]}"; do
+               logger -t teckids-ejmaint "Raising $o to owner of room $cn…"
+               ejabberdctl set_room_affiliation "$cn" ${muc_domain} "$o@${domain}" owner
+       done
+
+       # Iterate over members and manage MUC bookmarks
+       for mu in "${mus[@]}"; do
+               # Get all local MUC bookmarks of that user
+               bms=$(ejabberdctl private_get "$mu" ${domain} storage storage:bookmarks | \
+                   xmlstarlet sel -T -t -m "//*[@jid]" -v @jid -n | \
+                   grep "@${muc_domain}" | \
+                   sed 's/@.*//' | tr \\n @)
+
+               # Create bookmark if it does not exist yet
+               if ! [[ @$bms@ = *"@$cn@"* ]]; then
+                       newxml=$(ejabberdctl private_get "$mu" ${domain} storage storage:bookmarks | \
+                           xmlstarlet ed -O \
+                               -s "/*" -t elem -n conference \
+                               -i "//conference[last()]" -t attr -n jid -v "$cn@${muc_domain}" \
+                               -i "//conference[last()]" -t attr -n autojoin -v true | \
+                           tr -d "\n" | sed 's/  */ /g' | tr "'" "\"")
+                       logger -t teckids-ejmaint "Adding bookmark for room $cn to user $mu…"
+                       ejabberdctl private_set "$mu" ${domain} "$newxml"
+
+                       # Send direct invite to MUC to join without reloading bookmarks
+                       ejabberdctl send_direct_invitation $cn ${muc_domain} none none $mu@${domain}
+               fi
+       done
+done
diff --git a/mksh/teckids/intern/teckids-ldapmaint b/mksh/teckids/intern/teckids-ldapmaint
new file mode 100755 (executable)
index 0000000..38f0e84
--- /dev/null
@@ -0,0 +1,107 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015, 2016
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+export LC_ALL=C
+unset LANGUAGE
+cd /usr/src/verein
+
+nl=$'\n'
+
+# Find the LDAP kit
+PATH=/usr/local/share/teckids/mk:$PWD/../../www/mk:$PATH . assoldap.ksh
+
+# Find and load users (LDAP)
+asso_setldap_sasl users -- -b ou=People,dc=teckids,dc=org -s children "(objectClass=posixAccount)" uid cn
+asso_loadk users
+
+# Iterate over all users
+for user in "${asso_y[@]}"; do
+       uid=$(asso_getv users "$user" uid 0)
+       cn=$(asso_getv users "$user" cn 0)
+
+       if [[ $user != cn=*"+"uid=*,* ]]; then
+               rdn="cn=$cn+uid=$uid"
+               ldapmodrdn "$user" "$rdn"
+       fi
+done
+
+# Find and load groups (LDAP)
+asso_setldap_sasl groups -- -b ou=Groups,dc=teckids,dc=org -s children "(objectClass=posixGroup)" cn member
+asso_loadk groups
+
+# Iterate over all groups
+for group in "${asso_y[@]}"; do
+       cn=$(asso_getv groups "$group" cn)
+       # Is there a parents group?
+       if [[ $cn = *-kids ]]; then
+               cn_e=${cn/kids/eltern}
+       else
+               cn_e=$cn-eltern
+       fi
+       asso_setldap_sasl groups_e -- -b ou=Groups,dc=teckids,dc=org -s children "(cn=$cn_e)" cn
+       asso_loadk groups_e
+       dn_e=${asso_y[0]}
+       ldif_e=
+
+       asso_loadk groups "$group" member
+
+       ldif=
+       # Iterate over DNs
+       for n in "${asso_y[@]}"; do
+               [[ $n = count ]] && continue
+               dn=$(asso_getv groups "$group" member $n)
+
+               if [[ $dn = cn=*([!,+])"+"uid=*,* ]]; then
+                       # Extract uid
+                       uid=${dn#*\+uid=}
+                       uid=${uid%%,*}
+
+                       # Append to LDIF
+                       [[ $nl$ldif = *"${nl}memberUid: $uid$nl"* ]] || ldif="${ldif}memberUid: $uid$nl"
+               fi
+
+               # Is there a parents group?
+               if [[ -n $dn_e ]]; then
+                       asso_setldap_sasl eltern -- -b "$dn" -s children "(objectClass=inetOrgPerson)"
+                       asso_loadk eltern
+
+                       for e in "${asso_y[@]}"; do
+                               [[ $nl$ldif_e = *"${nl}member: $e$nl"* ]] || ldif_e="${ldif_e}member: $e$nl"
+                       done
+               fi
+       done
+
+       cat <<-EOF
+               dn: $group
+               changetype: modify
+               replace: memberUid
+               ${ldif}-
+
+       EOF
+
+       [[ -n $dn_e ]] && cat <<-EOF
+               dn: $dn_e
+               changetype: modify
+               replace: member
+               ${ldif_e}-
+
+       EOF
+done | ldapmodify -c
diff --git a/mksh/teckids/intern/teckids-mmmaint b/mksh/teckids/intern/teckids-mmmaint
new file mode 100755 (executable)
index 0000000..5dbd82e
--- /dev/null
@@ -0,0 +1,265 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      Thorsten „mirabilos“ Glaser <thorsten.glaser@teckids.org>
+# Copyright © 2013, 2014
+#      Thorsten Glaser <tg@mirbsd.org>
+# Copyright © 2014
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+#XXX This script does need extensive re-factoring.
+
+export LC_ALL=C
+unset LANGUAGE
+cd /usr/src/verein
+
+if [[ -s /var/lock/teckids-mmmaint ]]; then
+       grep . /var/lock/teckids-mmmaint
+       print -ru2 -- E: einer reicht
+       exit 1
+fi
+echo $$ >/var/lock/teckids-mmmaint
+
+set +U
+function str2python {
+#      [[ -o utf8-mode ]]; local u=$?
+#      set +U
+
+       local s="$*" t=\"
+       local -i i=0 n=${#s}
+       local -Uui16 -Z5 hv
+
+       while (( i < n )); do
+               hv=1#${s:(i++):1}
+               t+=\\x${hv#16#}
+       done
+       t+=\"
+       print -nr -- "$t"
+
+#      (( u )) || set -U
+}
+
+# Find the LDAP kit
+PATH=/usr/local/share/teckids/mk:$PWD/../../www/mk:$PATH . assoldap.ksh
+
+# Find and load lists (LDAP)
+if [[ -n $1 ]]; then
+       filter="(cn=$1)"
+else
+       filter="(!(cn=schulen*))"
+fi
+asso_setldap_sasl l -- -b ou=Groups,dc=teckids,dc=org -s children $filter
+asso_loadk l
+set -sA lists -- "${asso_y[@]}"
+
+# Find and load lists (Mailman)
+list_lists -b |&
+while IFS= read -pr list; do
+       asso_setnull e "$list"
+done
+
+# Get all members of vorstand as main mailing list owner
+asso_setldap_sasl v -- "(memberOf=cn=vorstand,ou=Groups,dc=teckids,dc=org)" uid
+set -A vorstand_uids
+asso_loadk v
+for vorstand_dn in "${asso_y[@]}"; do
+       uid=$(asso_getv v "$vorstand_dn" uid 0)
+       [[ -n $uid ]] && vorstand_uids+=("$uid")
+done
+
+# Temporary file for mailman config input
+if ! mct=$(mktemp); then
+       print -ru2 E: cannot create temporary file
+       rm -f /var/lock/teckids-mmmaint
+       exit 255
+fi
+
+# Cache for user listadmin configs
+if ! lad=$(mktemp -d); then
+       rm -f "$mct"
+       print -ru2 E: cannot create temporary directory
+       rm -f /var/lock/teckids-mmmaint
+       exit 255
+fi
+
+# Iterate over all lists
+for list in "${lists[@]}"; do
+       cn=$(asso_getv l "$list" cn 0)
+       [[ -n $cn ]] || continue
+       d=$(asso_getv l "$list" description 0)
+
+       pw=$(pwgen 8 1)
+
+       # Owner finden
+       owner=""
+       uid_done=:
+       asso_loadk l "$list" owner
+       for i in "${asso_y[@]}"; do
+               [[ $i = count ]] && continue
+               owner_dn=$(asso_getv l "$list" owner $i)
+
+               if [[ $owner_dn = *',ou=People,'* ]]; then
+                       g_mail=
+                       asso_setldap_sasl x -- -b "$owner_dn" -s base mail uid
+               elif [[ $owner_dn = *',ou=Groups,'* ]]; then
+                       # Determine group mail address
+                       g_mail=${owner_dn#cn=}
+                       g_mail=${g_mail%%,*}@lists.teckids.org
+                       owner+=", '$g_mail'"
+                       asso_setldap_sasl x -- "(memberOf=$owner_dn)" mail uid
+               else
+                       continue
+               fi
+
+               asso_loadk x
+               for owner_dn in "${asso_y[@]}"; do
+                       o_mail=$(asso_getv x "$owner_dn" mail 0)
+                       [[ -n $o_mail ]] || continue
+                       [[ -z $g_mail ]] && owner+=", '$o_mail'"
+                       o_uid=$(asso_getv x "$owner_dn" uid 0)
+                       [[ -n $o_uid ]] || continue
+
+                       #XXX check if “asso_isset e” style is faster
+                       [[ $uid_done = *:"$o_uid":* ]] && continue
+                       uid_done+=$o_uid:
+                       cat >>"$lad/$o_uid" <<EOF
+username $o_mail
+password "$pw"
+spamlevel 8
+adminurl https://www.teckids.org/mailman/admindb/{list}
+
+$cn@lists.teckids.org
+
+EOF
+               done
+       done
+       for o_uid in "${vorstand_uids[@]}"; do
+               [[ $uid_done = *:"$o_uid":* ]] && continue
+               uid_done+=$o_uid:
+               cat >>"$lad/$o_uid" <<EOF
+username vorstand@lists.teckids.org
+password "$pw"
+spamlevel 8
+adminurl https://www.teckids.org/mailman/admindb/{list}
+
+$cn@lists.teckids.org
+
+EOF
+       done
+       if [[ -n $owner ]]; then
+               owner=${owner/#, /\[}']'
+       else
+               owner="['vorstand@lists.teckids.org']"
+       fi
+
+       # Check list existence
+       if ! asso_isset e "$cn"; then
+               # Create and configure if not existent
+               logger -t ldap2ml "Creating new mailing list $cn"
+               print | newlist -q -l de -u www.teckids.org "$cn"@lists.teckids.org vorstand@teckids.org "$pw"
+               cat >"$mct" <<EOF
+# ~*~ coding: utf-8 ~*~
+private_roster = 2
+archive = True
+archive_private = 1
+subscribe_policy = 1
+EOF
+               config_list -i "$mct" "$cn"
+       fi
+       # Sync description and subject tag as well as owner list
+       cat >"$mct" <<EOF
+# ~*~ coding: utf-8 ~*~
+description = $(str2python "$d")
+subject_prefix = $(str2python "[Teckids $cn] ")
+owner = $owner
+EOF
+       config_list -i "$mct" "$cn"
+
+       # Sync members
+       logger -t ldap2ml "Syncing members of mailing list $cn"
+       {
+               # Sync direct members the memberOf overlay knows of
+               #XXX speed this up: streaming mode, avoid assockit
+               asso_setldap_sasl m -- "(&(memberOf=$list)(mail=*))" cn mail
+               asso_loadk m
+               for dn in "${asso_y[@]}"; do
+                       n=$(asso_getv m "$dn" cn 0)
+                       print -r -- "${n:-no name} <$(asso_getv m "$dn" mail 0)>"
+               done
+       } >"$mct"
+       if inv=$(LC_ALL=C pcregrep -a -v '^[^<]* <(?=.{1,254}\$)(?=.{1,64}@)[-!#-'\''*+/-9=?A-Z^-~]+(\.[-!#-'\''*+/-9=?A-Z^-~]+)*@[0-9A-Za-z]([-0-9A-Za-z]{0,61}[0-9A-Za-z])?(\.[0-9A-Za-z]([-0-9A-Za-z]{0,61}[0-9A-Za-z])?)*>$' "$mct"); then
+               print -r -- "$inv" | LC_ALL=C.UTF-8 mailx \
+                   -s "teckids-mmmaint: invalid addresses for list: $cn" root >&2
+               LC_ALL=C pcregrep -a '^[^<]* <(?=.{1,254}\$)(?=.{1,64}@)[-!#-'\''*+/-9=?A-Z^-~]+(\.[-!#-'\''*+/-9=?A-Z^-~]+)*@[0-9A-Za-z]([-0-9A-Za-z]{0,61}[0-9A-Za-z])?(\.[0-9A-Za-z]([-0-9A-Za-z]{0,61}[0-9A-Za-z])?)*>$' "$mct"
+       else
+               cat "$mct"
+       fi | sync_members -w=n -g=n -d=n -a=n -f - "$cn"
+
+       # Set password to what we generated and stored to listadmin configs earlier
+       /usr/lib/mailman/bin/change_pw -q -l "$cn" -p "$pw"
+done
+print -u2 "done"
+
+rm -f "$mct"
+
+# Copy listadmin configs to user homes
+cd "$lad"
+for uid in *; do
+       [[ -e "$uid" ]] || continue
+
+       # Get home path from OTTO-Versandhandel
+       eval "home=~$uid"
+
+       # Copy listadmin config and secure it
+       cp "$uid" "$home/.listadmin.ini"
+       chown "$uid":root "$home/.listadmin.ini"
+       chmod 0460 "$home/.listadmin.ini"
+done
+cd /
+rm -rf "$lad"
+
+# Copies mailman archives to cache folder and sets permissions
+# so groups can read
+
+mkdir -p /var/cache/mailmanarch
+cd /var/lib/mailman/archives/private
+
+for d in *.mbox; do
+       [[ $d = "*.mbox" || ! -d "$d" ]] && continue
+
+       f=$d/$d
+       t=/var/cache/mailmanarch/${d%.mbox}
+       g=${d%.mbox}
+       [[ $g = mailman ]] && g=list
+
+       if ! [[ -e "$f" ]]; then
+               rm -f "$t"
+       fi
+
+       if [[ "$f" -nt "$t" ]]; then
+               cp "$f" "$t"
+               if ! chown root:"$g" "$t"; then
+                       rm -f "$t"
+               else
+                       chmod 0640 "$t"
+               fi
+       fi
+done
+
+rm -f /var/lock/teckids-mmmaint
diff --git a/mksh/teckids/intern/teckids-sigmaint b/mksh/teckids/intern/teckids-sigmaint
new file mode 100755 (executable)
index 0000000..c5f83f4
--- /dev/null
@@ -0,0 +1,106 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2013, 2014
+#      Thorsten Glaser <tg@mirbsd.org>
+# Copyright © 2014
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+export LC_ALL=C
+unset LANGUAGE
+cd /usr/src/verein
+
+# Find the LDAP kit
+PATH=/usr/local/share/teckids/mk:$PWD/../../www/mk:$PATH . assoldap.ksh
+
+HOSTNAME=${HOSTNAME:-$(hostname -s)}
+
+if [[ $HOSTNAME != terra ]]; then
+       function ldapsearch {
+               local _cmd=ldapsearch _x
+
+               for _x in "$@"; do
+                       _cmd+=" ${_x@Q}"
+               done
+               ssh terra.teckids.org "$_cmd"
+       }
+fi
+
+# Find and load users
+asso_setldap_sasl users -- -b ou=Members,ou=People,dc=teckids,dc=org "(&(objectClass=posixAccount)(title=*)(mail=*))" uid cn title mail
+
+kd=$(mktemp -d)
+
+asso_loadk users
+for user_dn in "${asso_y[@]}"; do
+       uid=$(asso_getv users "$user_dn" uid 0)
+       cn=$(asso_getv users "$user_dn" cn 0)
+       title=$(asso_getv users "$user_dn" title 0)
+
+       cat >"$kd/$uid" <<EOF
+$cn ($title)
+Teckids e.V. - Erkunden, Entdecken, Erfinden.
+https://www.teckids.org
+EOF
+done
+
+# Copy signatures to user homes
+cd "$kd"
+for uid in *; do
+       [[ -e "$uid" ]] || continue
+
+       # Get home path from OTTO-Versandhandel
+       eval "home=~$uid"
+
+       # Copy signature
+       cp "$uid" "$home/.signature"
+       chown "$uid":root "$home/.signature"
+       chmod 0644 "$home/.signature"
+done
+
+cd -
+rm -rf "$kd"
+
+kd=$(mktemp -d)
+
+asso_loadk users
+for user_dn in "${asso_y[@]}"; do
+       uid=$(asso_getv users "$user_dn" uid 0)
+       mail=$(asso_getv users "$user_dn" mail 0)
+
+       print -r -- "$mail" >"$kd"/"$uid"
+done
+# Copy files to user homes
+cd "$kd"
+for uid in *; do
+       [[ -e "$uid" ]] || continue
+
+       # Ensure home existence
+       su -c /bin/true "$uid"
+
+       # Get home path from OTTO-Versandhandel
+       eval "home=~$uid"
+
+       # Copy signature
+       cp "$uid" "$home/.mailfrom"
+       chown "$uid":root "$home/.mailfrom"
+       chmod 0644 "$home/.mailfrom"
+done
+
+cd -
+rm -rf "$kd"
diff --git a/mksh/teckids/intern/teckids-sshmaint b/mksh/teckids/intern/teckids-sshmaint
new file mode 100755 (executable)
index 0000000..11151a3
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2013, 2014
+#      Thorsten Glaser <tg@mirbsd.org>
+# Copyright © 2014
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+export LC_ALL=C
+unset LANGUAGE
+cd /usr/src/verein
+
+# Find the LDAP kit
+PATH=/usr/local/share/teckids/mk:$PWD/../../www/mk:$PATH . assoldap.ksh
+
+HOSTNAME=${HOSTNAME:-$(hostname -s)}
+
+if [[ $HOSTNAME != terra ]]; then
+       function ldapsearch {
+               local _cmd=ldapsearch _x
+
+               for _x in "$@"; do
+                       _cmd+=" ${_x@Q}"
+               done
+               ssh terra.teckids.org "$_cmd"
+       }
+fi
+
+# Find and load users
+asso_setldap_sasl users -- -b ou=People,dc=teckids,dc=org "(objectClass=posixAccount)" uid sshAuthorizedKey
+
+kd=$(mktemp -d)
+
+asso_loadk users
+for user_dn in "${asso_y[@]}"; do
+       uid=$(asso_getv users "$user_dn" uid 0)
+        kc=$(asso_getv users "$user_dn" sshAuthorizedKey count)
+
+       [[ -z $kc ]] && continue
+
+       i=0; while (( i < kc )); do
+               key=$(asso_getv users "$user_dn" sshAuthorizedKey $i)
+               print -r -- "$key" >>"$kd/$uid"
+               (( i++ ))
+       done
+done
+
+# Copy authorized_keys to user homes
+cd "$kd"
+for uid in *; do
+       [[ -e "$uid" ]] || continue
+
+       # Get home path from OTTO-Versandhandel
+       eval "home=~$uid"
+
+       # Copy authorized_keys and secure it
+       if ! [[ -d "$home/.ssh" ]]; then
+               mkdir -p "$home/.ssh"
+               chown "$uid":root "$home/.ssh"
+       fi
+       cp "$uid" "$home/.ssh/authorized_keys"
+       chown "$uid":root "$home/.ssh/authorized_keys"
+       chmod 0440 "$home/.ssh/authorized_keys"
+done
+
+cd -
+rm -rf "$kd"
diff --git a/mksh/teckids/mk/docman b/mksh/teckids/mk/docman
new file mode 100644 (file)
index 0000000..b33ca6e
--- /dev/null
@@ -0,0 +1,470 @@
+# -*- mode: sh -*-
+
+# Copyright © 2013, 2014, 2015, 2017
+#      Thorsten „mirabilos“ Glaser <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+mydir=$(dirname "$(realpath "$0")")
+docmannumber=$1; [[ $1 = +([0-9]) ]] || exit 1
+srctree=$2
+dsttree=$3
+
+subdir_levels=1
+x=$dsttree
+while [[ $x = */* ]]; do
+       let ++subdir_levels
+       x=${x#*/}
+done
+
+[[ -n $ASSO_VAL ]] || PATH="$mydir:$mydir/..:$PATH" . assockit.ksh
+(( common_read )) || PATH="$mydir:$mydir/..:$PATH" . common
+
+cwd=$(realpath .)
+mkdir -p docman$docmannumber
+uwd=$cwd/docman$docmannumber
+
+function fdie {
+       print -ru2 "E: '$srcf' $*"
+       exit 1
+}
+
+function fwarn {
+       print -ru2 "W: '$srcf' $*"
+}
+
+typeset -l lk
+typeset -Uui16 -Z11 dword
+
+exec >"$uwd/tree.hts~"
+print '#DEPEND mk/docman'
+
+cd "$srctree"
+set -A all_srcf
+for srcf in $(fgrep -arl TECKIDSMETA . | fgrep -v /drafts/); do
+       srcf=${srcf#./}
+       all_srcf+=("$srcf")
+       print -r -- "#DEPEND $srctree/$srcf"
+       [[ -s $srcf ]] || fdie "does not exist"
+       case $srcf {
+       (*.tex)
+               dstf=${srcf%tex}pdf
+               ;;
+       (*)
+               print -ru2 "W: '$srcf' has unknown extension, skipping"
+               continue
+               # NOTREACHED
+               print -ru2 "W: '$srcf' has unknown extension, assuming copy"
+               dstf=$srcf
+               ;;
+       }
+       od=$uwd/t
+       [[ $dstf = */* ]] && od=$uwd/t/${dstf%/*}
+       mkdir -p "$od"
+       bn=${dstf##*/}
+       sed -n '/TECKIDSMETA:BEGIN$/,/TECKIDSMETA:END$/p' \
+           <"$srcf" >"$od/in-$bn"
+       [[ -s $od/in-$bn ]] || fdie "has format errors: no BEGIN/END markers"
+       exec <"$od/in-$bn"
+       IFS= read -r line
+       marker=${line%TECKIDSMETA:BEGIN}
+       last="Filename: $dstf"
+       while IFS= read -r line; do
+               [[ $line = "$marker"* ]] || fdie "malformed input line '$line'"
+               line=${line#"$marker"}
+               if [[ $line = ' .' ]]; then
+                       last+=$'\r'
+               elif [[ $line = ' '* ]]; then
+                       last+=$line
+               elif [[ $line != *:* ]]; then
+                       fdie "not a key:value line: '$line'"
+               else
+                       k=${last%%:*}
+                       lk=$k
+                       v=${last#*:}
+                       v=${v##+([       ])}
+                       asso_sets "$v" filekeys "$srcf" "$k"
+                       asso_sets "$v" lokeys "$srcf" "$lk"
+                       last=$line
+               fi
+       done
+       [[ $last = TECKIDSMETA:END ]] || \
+           fdie "has format errors: '$last' no END marker"
+
+       havefb=0 havest=0 havekurs=0 haveeinh=0 havetype=0 havebl=0 havet=0
+       asso_loadk lokeys "$srcf"
+       for k in "${asso_y[@]}"; do
+               case $k {
+               (fachbereich)   havefb=1 ;;
+               (stufen)        havest=1 ;;
+               (kurs)          havekurs=1 ;;
+               (einheit)       haveeinh=1 ;;
+               (materialart)   havetype=1 ;;
+               (blatt)         havebl=1 ;;
+               (titel)         havet=1 ;;
+               }
+       done
+
+       (( havefb )) || fdie 'missing mandatory field Fachbereich'
+       (( havest )) || fdie 'missing mandatory field Stufen'
+       (( havekurs )) || fdie 'missing mandatory field Kurs'
+       (( haveeinh )) || fdie 'missing mandatory field Einheit'
+       (( havetype )) || fdie 'missing mandatory field Materialart'
+       type=$(asso_getv lokeys "$srcf" "materialart")
+       lk=$type
+       case $lk {
+       (unterrichtsplan)
+               (( havebl )) && fdie "type '$type' has no Blatt field"
+               (( havet )) && fwarn "type '$type' needs no Titel field"
+               ;;
+       (arbeitsblatt)
+               (( havebl )) || fdie "missing mandatory field Blatt for type '$type'"
+               (( havet )) || fdie "missing mandatory field Titel for type '$type'"
+               ;;
+       (*)
+               fwarn "unknown type '$type'"
+               (( havet )) || fdie "missing mandatory field Titel for type '$type'"
+               ;;
+       }
+
+       fb=$(asso_getv lokeys "$srcf" "fachbereich")
+       st= x=$(asso_getv lokeys "$srcf" "stufen")
+       kurs=$(asso_getv lokeys "$srcf" "kurs")
+       einh=$(asso_getv lokeys "$srcf" "einheit")
+       [[ $einh = [1-9]*([0-9])' - '* ]] || fdie "malformed Einheit '$einh'"
+       for y in $x; do
+               case $y {
+               (+([0-9])-+([0-9]))
+                       i=${y%-*}
+                       j=${y#*-}
+                       while (( i <= j )); do
+                               st+=\ $((i++))
+                       done
+                       ;;
+               (+([0-9])?(,))
+                       i=${y%,}
+                       st+=\ $((i))
+                       ;;
+               (*)
+                       fdie "malformed '$x' in field Stufen"
+                       ;;
+               }
+       done
+       [[ -n $st ]] || fdie 'missing mandatory field Stufen'
+       st+=\ all
+       hr="$fb [$x] $kurs (${einh/ - /. }) "
+       case $lk {
+       (unterrichtsplan)
+               x=$(asso_getv lokeys "$srcf" "beschreibung")
+               for i in $st; do
+                       asso_isset hier "$fb" $i "$kurs" "$einh" planfile && \
+                           fdie 'is a duplicate'
+                       asso_sets "$x" hier "$fb" $i "$kurs" "$einh" desc
+                       asso_sets "$srcf" hier "$fb" $i "$kurs" "$einh" planfile
+               done
+               hr+=Unterrichtsplan
+               ;;
+       (arbeitsblatt)
+               blatt=$(asso_getv lokeys "$srcf" "blatt")
+               [[ $blatt = [1-9]*([0-9]) ]] || \
+                   fdie "malformed '$blatt' in field Blatt"
+               (( dword = blatt ))
+               y=${dword#16#}
+               x=$(asso_getv lokeys "$srcf" "titel")
+               for i in $st; do
+                       asso_isset hier "$fb" $i "$kurs" "$einh" p$y file && \
+                           fdie 'is a duplicate'
+                       asso_sets "$((dword)). $x" hier "$fb" $i "$kurs" "$einh" p$y title
+                       asso_sets "$srcf" hier "$fb" $i "$kurs" "$einh" p$y file
+               done
+               hr+="$((dword)). $x"
+               ;;
+       (*)
+               x=$(asso_getv lokeys "$srcf" "titel")
+               for i in $st; do
+                       asso_isset hier "$fb" $i "$kurs" "$einh" other "$x" && \
+                           fdie 'is a duplicate'
+                       asso_sets "$srcf" hier "$fb" $i "$kurs" "$einh" other "$x"
+                       asso_seti 1 hier "$fb" $i "$kurs" "$einh" hasother
+               done
+               hr+="? $x"
+               ;;
+       }
+       asso_sets "$hr" hr "$srcf"
+done
+
+function printunless {
+       nameref key=$1
+       shift
+
+       (( key )) && return
+       print "$@"
+       key=1
+}
+
+function printif {
+       nameref key=$1
+       shift
+
+       (( key )) || return
+       print "$@"
+       key=0
+}
+
+function dstlink {
+       local dstf title=$1
+
+       dstf=$(asso_getv lokeys "$srcf" "filename")
+       [[ -n $dstf ]] || fdie "Missing destination file"
+       dstf=${dsttree:+$dsttree/}$dstf
+       lastlink=/${dstf%.pdf}.htm
+       print -nr -- "<a href=\"$(xhtml_escape \
+           "$lastlink")\">$(xhtml_escape \
+           "$title")</a>"
+}
+
+function treeadd {
+       local iconclosed=null iconopen=null
+
+       if [[ $1 = -i ]] && (( $# > 3 )); then
+               shift
+               iconclosed=$(json_escape "$1"); shift
+               iconopen=$(json_escape "$1"); shift
+       fi
+
+       local parent=$1 text=$2 link=$3 title=$4
+
+       if (( $# > 1 )); then
+               text=$(json_escape "$text")
+       else
+               text=null
+       fi
+       if (( $# > 2 )); then
+               link=$(json_escape "$link")
+       else
+               link=null
+       fi
+       if (( $# > 3 )); then
+               title=$(json_escape "$title")
+       else
+               title=null
+       fi
+
+       jslines+=("$treename.add($((treeid++)), $((parent)), $text, $link, $title, null, $iconclosed, $iconopen);")
+}
+
+# hier $fb $st $kurs $einh desc = Beschreibung der Einheit
+# hier $fb $st $kurs $einh planfile = SRCF mit Plan
+# hier $fb $st $kurs $einh hasother = 1
+# hier $fb $st $kurs $einh other $titel = SRCF
+# hier $fb $st $kurs $einh p$blatt title = $blatt - $titel
+# hier $fb $st $kurs $einh p$blatt file = SRCF
+cd "$uwd"
+print 'mws_content <<"EOI_DOCMAN"'
+print '<div class="docman_tree">'
+set -A jslines
+treeno=0
+print "<div id=\"docman_tree_nojs\">"
+asso_loadk hier
+set -sA all_fb -- "${asso_y[@]}"
+ul_fb=0
+for fb in "${all_fb[@]}"; do
+       printunless ul_fb "<ul><!-- Fachbereich -->"
+       print -r " <li>$(xhtml_escape "$fb")"
+       treename=d$((treeno++))d
+       jslines+=("var $treename = new dTree('$treename');")
+       jslines+=("$treename.icon.root = '/dtree.img/globe.gif';")
+       treeid=0
+       treeadd -1 "$fb"
+       asso_loadk hier "$fb"
+       set -A all_st
+       for x in "${asso_y[@]}"; do
+               [[ $x = all ]] || all_st[x]=$x
+       done
+       ul_st=0
+       for st in "${all_st[@]}" all; do
+               stx="$st. Klasse ("
+               case $st {
+               (5) stx+=Ⅵ/Sexta\) ;;
+               (6) stx+=Ⅴ/Quinta\) ;;
+               (7) stx+=Ⅳ/Quarta\) ;;
+               (8) stx+=UⅢ/Untertertia\) ;;
+               (9) stx+=OⅢ/Obertertia\) ;;
+               (10) stx+=UⅡ/Untersekunda\) ;;
+               (11) stx+=EF,\ OⅡ/Obersekunda\) ;;
+               (12) stx+=Q1,\ UⅠ/Unterprima\) ;;
+               (13) stx+=Q2,\ OⅠ/Oberprima\) ;;
+               (all) stx=Alle ;;
+               (*) stx=$st ;;
+               }
+               printunless ul_st -r "  <ul><!-- Stufe (FB $(xhtml_escape "$fb")) -->"
+               print -r "   <li>$(xhtml_escape "$stx")"
+               js_st=$treeid
+               treeadd 0 "$stx"
+               asso_loadk hier "$fb" "$st"
+               set -sA all_kurs -- "${asso_y[@]}"
+               ul_kurs=0
+               for kurs in "${all_kurs[@]}"; do
+                       printunless ul_kurs -r "    <ul><!-- Kurs (FB $(xhtml_escape "$fb") ST $(xhtml_escape "$st")) -->"
+                       print -r "     <li>$(xhtml_escape "$kurs")"
+                       js_kurs=$treeid
+                       treeadd $js_st "$kurs"
+                       asso_loadk hier "$fb" "$st" "$kurs"
+                       set -A all_einh
+                       for x in "${asso_y[@]}"; do
+                               i=${x%% *}
+                               all_einh[i]=$x
+                       done
+                       ul_einh=0
+                       for einh in "${all_einh[@]}"; do
+                               printunless ul_einh -r "      <ul><!-- Einheit (FB $(xhtml_escape "$fb") ST $(xhtml_escape "$st") Kurs $(xhtml_escape "$kurs")) -->"
+                               print -r "       <li>$(xhtml_escape "${einh/ - /. }")"
+                               x=$(asso_getv hier "$fb" "$st" "$kurs" "$einh" desc)
+                               js_einh=$treeid
+                               if [[ -n $x ]]; then
+                                       treeadd -i /dtree.img/question.gif /dtree.img/question.gif \
+                                           $js_kurs "${einh/ - /. }" "" "$x"
+                               else
+                                       treeadd $js_kurs "${einh/ - /. }"
+                               fi
+#                              [[ -n $x ]] && print -r "        <br />$(
+#                                  xhtml_escape "$x" | sed $'s!\r!<br />!g')"
+                               ul_ineinh=0
+                               if srcf=$(asso_getv hier "$fb" "$st" "$kurs" "$einh" planfile); then
+                                       printunless ul_ineinh -r "        <ul>"
+                                       print -nr "         <li>"
+                                       dstlink 'Unterrichtsplan'
+                                       treeadd $js_einh "Unterrichtsplan" "$lastlink"
+                                       print "</li>"
+                               fi
+                               asso_loadk hier "$fb" "$st" "$kurs" "$einh"
+                               set -sA all_items -- "${asso_y[@]}"
+                               ul_arbbl=0
+                               for x in "${all_items[@]}"; do
+                                       [[ $x = p+([0-9A-F]) ]] || continue
+                                       if (( !ul_arbbl )); then
+                                               js_arbbl=$treeid
+                                               treeadd $js_einh "Arbeitsblätter"
+                                       fi
+                                       printunless ul_ineinh -r "        <ul>"
+                                       printunless ul_arbbl -r "         <li>Arbeitsblätter<ul>"
+                                       y=$(asso_getv hier "$fb" "$st" "$kurs" "$einh" "$x" title)
+                                       srcf=$(asso_getv hier "$fb" "$st" "$kurs" "$einh" "$x" file)
+                                       print -nr "          <li>"
+                                       dstlink "$y"
+                                       treeadd $js_arbbl "$y" "$lastlink"
+                                       print "</li>"
+                               done
+                               printif ul_arbbl "          </ul></li><!-- /Arbeitsblätter -->"
+                               if x=$(asso_getv hier "$fb" "$st" "$kurs" "$einh" hasother) && \
+                                   [[ $x = 1 ]]; then
+                                       asso_loadk hier "$fb" "$st" "$kurs" "$einh" other
+                                       set -sA all_items -- "${asso_y[@]}"
+                                       for x in "${all_items[@]}"; do
+                                               printunless ul_ineinh -r "        <ul>"
+                                               print -nr "         <li>"
+                                               srcf=$(asso_getv hier "$fb" "$st" "$kurs" "$einh" other "$x")
+                                               dstlink "$x"
+                                               treeadd $js_einh "$x" "$lastlink"
+                                               print "</li>"
+                                       done
+                               fi
+                               printif ul_ineinh "        </ul>"
+                               print "        </li>"
+                       done
+                       printif ul_einh -r "      </ul><!-- /Einheit -->"
+                       print "      </li>"
+               done
+               printif ul_kurs -r "    </ul><!-- /Kurs -->"
+               print "    </li>"
+       done
+       printif ul_st -r "  </ul><!-- /Stufe -->"
+       print "  </li>"
+       jslines+=("document.write($treename);")
+done
+printif ul_fb "</ul><!-- /Fachbereich -->"
+print "</div><!-- /#docman_tree_nojs -->"
+print "<div id=\"docman_tree_js\">"
+print '<script type="text/javascript"><!--//--><![CDATA[//><!--'
+for x in "${jslines[@]}"; do
+       print -r -- "$x"
+done
+print '//--><!]]></script>'
+print "</div><!-- /#docman_tree_js -->"
+print "</div><!-- /.docman_tree -->"
+print EOI_DOCMAN
+
+exec >"$uwd/Makefile.inc"
+cd "$cwd"
+for srcf in "${all_srcf[@]}"; do
+       if ! dstf=$dsttree/$(asso_getv lokeys "$srcf" "filename"); then
+               print -u2 "W: skipping '$srcf'"
+               continue
+       fi
+       [[ $dstf = *.pdf ]] || fdie "dst map file '$dstf' not *.pdf"
+       dbn=${dstf%.pdf}
+       print -r -- "#DEPSRCS+=$dbn.hts"
+       print -r -- "OBJS+=$dbn.htm~"
+       print -r -- "DOCMAN${docmannumber}_SRCS+=$srctree/$srcf"
+       print -r -- "$dbn.hts: docman${docmannumber}/tree.hts"
+
+       htsf=$cwd/$dbn.hts
+       mkdir -p "${htsf%/*}"
+       {
+               hr=$(asso_getv hr "$srcf")
+               cat <<-EOHTS
+                       mws_subdir $subdir_levels
+                       mws_usedocman
+                       mws_setname ${dbn@Q} ${hr@Q}
+                       mws_setheadline Dokument:\ ${hr@Q}
+                       mws_putheader
+                       mws_content <<"EOCNT"
+                       <div>
+                       EOCNT
+                       #DEPEND OBJ:docman$docmannumber/tree.hts
+                       . "\${DST}/docman$docmannumber/tree.hts"
+                       mws_content <<"EOCNT"
+                       <div class="docman_info">
+                       <h2>Download</h2>
+                       <p><a href="$(xhtml_escape "${dstf##*/}")"><img
+                        src="@@RELPATH@@pics/disk.png" style="border:0px;"
+                        alt="Disc icon" /> Lade dieses Blatt herunter</a></p>
+                       <h2>Informationen</h2>
+                       <table width="100%" border="0">
+               EOHTS
+               asso_loadk filekeys "$srcf"
+               # output some metadata keys in order first, then the rest
+               for k in Filename Titel Fachbereich Kurs Stufen Einheit \
+                   Materialart Blatt Beschreibung "${asso_y[@]}"; do
+                       lk=$k
+                       x=$(asso_getv lokeys "$srcf" "$lk")
+                       [[ -n $x ]] || continue
+                       asso_sets "" lokeys "$srcf" "$lk"
+                       print -r -- " <tr><th>$(xhtml_escape "$k"):</th><td>$(
+                           xhtml_escape "$x" | sed $'s!\r!<br />!g')</td></tr>"
+               done
+               cat <<-EOHTS
+                       </table>
+                       </div>
+                       </div>
+                       EOCNT
+                       mws_putfooter
+                       exit 0
+               EOHTS
+       } >"$htsf"
+done
+
+mv "$uwd/tree.hts~" "$uwd/tree.hts"
diff --git a/mksh/teckids/mk/mkids b/mksh/teckids/mk/mkids
new file mode 100644 (file)
index 0000000..439c316
--- /dev/null
@@ -0,0 +1,188 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2014, 2017
+#      Thorsten Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Rewrite all ID (and, for Teckids, Author) fields in all data/*.inc
+# files on a MirWebseite setup.
+
+export LC_ALL=C
+unset LANGUAGE
+cd "$(dirname "$0")"
+export TOP=$(realpath ..)
+cd "$TOP"
+
+if ! read ax <data/mirwebseite.ids || [[ $ax != [1-9]*([0-9]) ]]; then
+       print -u2 E: mirwebseite.ids broken
+       exit 255
+fi
+typeset -i10 cx=$ax
+
+[[ -n $ASSO_VAL ]] || PATH="$TOP/mk:$PATH" . assockit.ksh
+
+# just in case, unset the target array and create it as associative
+asso__lookup 1 authors
+asso__r_free
+asso__r_setf $ASSO_AASS
+# parse authors.txt into associative array
+#DEPEND OBJ:authors.txt
+if [[ ! -s $TOP/obj/authors.txt ]]; then
+       print -u2 E: generate authors.txt first
+       exit 1
+fi
+while IFS='=' read -r uid cn; do
+       asso_sets "$cn" authors "$uid"
+done <"$TOP/obj/authors.txt"
+
+asso__lookup 1 ids
+asso__r_free
+asso__r_setf $ASSO_AASS
+
+save_IFS=$' \t\n'
+nl=$'\n'
+integer state
+typeset -l parser_lctmp
+
+function parsedate {
+       local ta
+       IFS=$IFS:.
+       set -A ta -- ${e_date}
+       IFS=$save_IFS
+       typeset -i -Z2 e_tmpd=10#0${ta[0]}
+       typeset -i -Z2 e_tmpm=10#0${ta[1]}
+       typeset -i -Z4 e_tmpy=10#0${ta[2]}
+       e_date=$e_tmpd.$e_tmpm.$e_tmpy
+}
+
+find data -name \*.inc -print0 |&
+while IFS= read -d '' -p -r fn; do
+       if head "$fn" | grep -a '^#MWS=mbsd$' >/dev/null; then
+               print -ru2 "I: skipping $fn (not for us)"
+               continue
+       fi
+       print -ru2 "I: processing $fn"
+       exec 3>"$fn.new"
+       state=1
+       set -A wheader
+       nwheader=0 nwheaderx=-1
+       e_id= e_date= e_author= e_authoruid= e_authormail= ei_body=
+       while IFS= read -r line; do
+               if [[ $line = ---- ]]; then
+#debug, remove later
+oldid=${e_id}
+                       if [[ -z ${e_id} ]]; then
+                               id=f$((# ++cx))
+                               if [[ -z ${e_author} ]]; then
+                                       print -ru2 "E: No author in a line in $fn"
+                                       exit 1
+                               fi
+                               IFS=';'
+                               set -- $e_author
+                               IFS=$' \t\n'
+                               e_authoruid=
+                               e_author=
+                               e_authormail=
+                               for x_author in "$@"; do
+                                       x=$(asso_getv authors "$x_author")
+                                       if [[ -z $x ]]; then
+                                               print -ru2 \
+                                                   "E: Unknown author in $fn:" \
+                                                   "$x_author ($e_author)"
+                                               print -u2 "I: Deleting authors.txt," \
+                                                   for autofixing in next run
+                                               rm -f "$TOP/obj/authors.txt"
+                                               exit 1
+                                       fi
+                                       id=${id}-${x_author}
+                                       e_authoruid+=${e_authoruid:+;}${x_author}
+                                       e_author+=${e_author:+;}$x
+                                       typeset -l x=${x// /.}@teckids.org
+                                       e_authormail+=${e_authormail:+;}$x
+                                       unset x
+                               done
+                               e_id=$id
+                       fi
+                       if asso_isset ids "$e_id"; then
+                               [[ $e_id = f+([0-9]) ]] || e_id=f$((# ++cx))
+                               if asso_isset ids "$e_id"; then
+                                       print -u2 "E: ID $e_id given twice, data inconsistent"
+                                       exit 1
+                               fi
+                       fi
+                       asso_setnull ids "$e_id"
+#debug, remove later
+if asso_isset ids "$e_id"; then if [[ -z $oldid ]]; then print -u2 "I: ID $e_id given out."; else print -u2 "I: ID $e_id kept."; fi; fi
+                       state=2
+                       i=-1
+                       while (( ++i < nwheader )); do
+                               print -ru3 -- "${wheader[i]}"
+                               if (( i == nwheaderx )); then
+                                       print -ru3 -- "ID: $e_id"
+                                       print -ru3 -- "Author: $e_author"
+                                       print -ru3 -- "Authormail: $e_authormail"
+                                       print -ru3 -- "AuthorUID: $e_authoruid"
+                               fi
+                       done
+                       print -ru3
+                       print -ru3 -- "$ei_body"----
+                       if (( cx < ax )); then
+                               print -u2 E: integer overflow, change the code
+                               exit 255
+                       fi
+                       set -A wheader
+                       nwheader=0 nwheaderx=-1
+                       e_id= e_date= e_author= e_authoruid= e_authormail= ei_body=
+               elif (( state > 0 )); then
+                       wheader[nwheader++]=$line
+                       if [[ $line = @([A-Za-z]*([A-Za-z0-9_]):\ *) ]]; then
+                               x=${line##+([A-Za-z0-9_]):+([    ])}    # value
+                               parser_lctmp=${line/%:+([        ])"$x"} # key
+                               [[ $parser_lctmp = @(id|date|author|authoruid|authormail) ]] && \
+                                   eval e_$parser_lctmp=\$x
+                       elif [[ -z $line ]]; then
+                               (( --nwheader ))
+                               state=0
+                               continue
+                       elif [[ $line = \#* ]]; then
+                               continue        # ignore comment
+                       else
+                               print -ru2 "Unknown header in $fn:" \
+                                   "$line"
+                               exit 1
+                       fi
+                       state=1
+                       if [[ $parser_lctmp = date ]]; then
+                               parsedate
+                               (( nwheaderx = nwheader - 1 ))
+                       elif [[ $parser_lctmp = @(id|author|authormail|authoruid) ]]; then
+                               (( --nwheader ))
+                       fi
+               else
+                       ei_body+=$line$nl
+               fi
+       done <"$fn"
+       if (( state != 2 )); then
+               print -ru2 "Last line of $fn not a separator!"
+               exit 1
+       fi
+       exec 3>&-
+       print $cx >data/mirwebseite.ids
+       mv -f "$fn.new" "$fn"
+done
+print -ru2 "I: done"
diff --git a/mksh/teckids/mk/mkpeople b/mksh/teckids/mk/mkpeople
new file mode 100644 (file)
index 0000000..1e5457c
--- /dev/null
@@ -0,0 +1,127 @@
+# -*- mode: sh -*-
+
+# This file is part of the Teckids website, which is copyrighted
+# material, please read https://www.teckids.org/LICENCE.htm for details.
+
+export LC_ALL=C
+unset LANGUAGE
+cd "$(dirname "$0")"
+export TOP=$(realpath ..)
+
+function ldapsearch {
+       local _cmd=ldapsearch _x
+
+       for _x in "$@"; do
+               _cmd+=" ${_x@Q}"
+       done
+       ssh ticdesk.teckids.org "$_cmd"
+}
+
+PATH=$TOP/mk:$PATH . assoldap.ksh
+PATH=$TOP/mk:$PATH . base64.ksh
+. ./common
+
+asso_setldap_sasl users -- -b ou=Members,ou=People,dc=teckids,dc=org \
+    '(&(objectClass=inetOrgPerson)(title=*))'
+
+cd "$DST"
+rm -rf people
+mkdir people
+cat <<'EOT'
+# *** GENERATED FILE, DO NOT EDIT ***
+#-
+# This file is part of the Teckids website, which is copyrighted
+# material, please read https://www.teckids.org/LICENCE.htm for details.
+
+mws_usesidebar
+mws_setname people 'Vereinsmitglieder'
+mws_setheadline 'Steckbriefe der Vereinsmitglieder'
+mws_putheader
+mws_content <<'EOF'
+
+<p>
+ Auf dieser Seite werden nur Vorstellungen von Mitgliedern angezeigt,
+ die diese verfasst haben und der Veröffentlichung zugestimmt haben!
+</p>
+
+EOF
+
+mws_maketoc
+mws_content <<'EOF'
+
+EOT
+
+asso_loadk users
+i=-1
+for user_dn in "${asso_y[@]}"; do
+       sn=$(asso_getv users "$user_dn" sn 0)
+       givenName=$(asso_getv users "$user_dn" givenName 0)
+       print -r -- "$sn $givenName $((++i))"
+done | LC_ALL=de_DE.UTF-8 sort -f | while IFS= read -r x; do
+       i=${x##* }
+       user_dn=${asso_y[i]}
+       uid=$(xhtml_escape "$(asso_getv users "$user_dn" uid 0)")
+       cn=$(xhtml_escape "$(asso_getv users "$user_dn" cn 0)")
+       displayName=$(xhtml_escape "$(asso_getv users "$user_dn" displayName 0)")
+       mail=$(xhtml_escape "$(asso_getv users "$user_dn" mail 0)")
+       title=$(xhtml_escape "$(asso_getv users "$user_dn" title 0)")
+       description=$(asso_getv users "$user_dn" description 0)
+       jpegPhoto=$(asso_getv users "$user_dn" jpegPhoto 0)
+       displayName=${displayName:-$cn}
+       img=pics/people.jpg
+       alt="kein Foto vorhanden"
+       if [[ -n $jpegPhoto ]]; then
+               img=people/$uid.jpg
+               alt="Foto von $displayName"
+               fn="people/$(asso_getv users "$user_dn" uid 0).jpg"
+               Lb64decode "$jpegPhoto" >"$fn"
+               print -ru3 -- "$fn"
+       fi
+       if [[ -z $description ]]; then
+               description="<i>Vorstellungstext folgt!</i>"
+       fi
+       if [[ -e ../data/blog_$uid.cfg ]]; then
+               blog=", <a href=\"@@RELPATH@@blog_$uid.htm\">Mitglieds-Blog</a>"
+       else
+               blog=
+       fi
+       cat <<EOT
+<h2 id="$uid">Steckbrief von $displayName</h2>
+<table border="1" style="width:98%;">
+<tr>
+ <td style="width:200px; height:267px; padding:0px; vertical-align:top; text-align:left;"
+  rowspan="3"><img src="$img" alt="$alt" style="height:267px; width:200px;" /></td>
+ <th style="width:64px; height:1em; vertical-align:top; text-align:left;">Name:</th>
+ <td style="height:1em; vertical-align:top; text-align:left;">$displayName (<a
+  href="mailto:$mail">E-Mail</a>$blog)</td>
+</tr><tr>
+ <th style="width:64px; height:1em; vertical-align:top; text-align:left;">Rolle:</th>
+ <td style="height:1em; vertical-align:top; text-align:left;">$title</td>
+</tr><tr>
+ <td style="vertical-align:top; text-align:left;" colspan="2">$description</td>
+</tr></table>
+
+EOT
+done
+
+cat <<'EOT'
+<p>Alle Rechte auf die Bilder und Profildaten bleiben den jeweiligen
+ Mitgliedern vorbehalten; eine weitergehende Nutzung ist nicht gestattet
+ ohne explizite Einwilligung der betroffenen Mitglieder!</p>
+EOF
+
+#DEPEND tpl/tosidebar
+mws_calltemplate tosidebar
+
+mws_content <<'EOF'
+<h2>Mitglieder-Blogs</h2>
+EOF
+
+#DEPEND OBJ:blogs.cut
+. "${DST}/blogs.cut"
+
+#DEPEND tpl/footerwithsidebar
+mws_putfooter withsidebar
+exit 0
+EOT
+exit 0
diff --git a/mksh/teckids/www/auth_vote.cgi b/mksh/teckids/www/auth_vote.cgi
new file mode 100755 (executable)
index 0000000..dbc8cdd
--- /dev/null
@@ -0,0 +1,517 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010,
+#              2011, 2012, 2013, 2014
+#      Thorsten “mirabilos” Glaser <tg@mirbsd.org>
+# Copyright © 2013, 2014, 2015
+#      Thorsten Glaser <thorsten.glaser@teckids.org>
+# Copyright © 2015
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+unset HTTP_PROXY
+
+set +U
+cd "$(dirname "$0")"
+
+function sed_escape {
+       REPLY=$1
+       REPLY=${REPLY//\\/\\\\}
+       REPLY=${REPLY//[&]/\\&}
+       REPLY=${REPLY//$'\n'/\\n}
+}
+
+#{{{ magic from MirOS: www/mk/common,v 1.7
+# escape XHTML characters (three mandatory XML ones plus double quotes,
+# the latter in an XML safe fashion numerically though)
+function xhtml_escape {
+       if (( $# )); then
+               print -nr -- "$@"
+       else
+               cat
+       fi | sed \
+           -e 's\ 1&\ 1\&amp;\ 1g' \
+           -e 's\ 1<\ 1\&lt;\ 1g' \
+           -e 's\ 1>\ 1\&gt;\ 1g' \
+           -e 's\ 1"\ 1\&#34;\ 1g'
+}
+#}}}
+
+cr=$'\r'
+lf=$'\n'
+crlf=$'\r\n'
+
+function xdie {
+       local body x funcname=$1 rc=$2; shift; shift
+
+       for x in "$@"; do
+               body+="<p class=\"cgierr\">$(xhtml_escape "$x")</p>$nl"
+       done
+       body+='
+<p>Kontaktieren Sie uns ggfs. direkt per eMail unter <a
+ href="mailto:vorstand@teckids.org">&lt;vorstand@teckids.org&gt;</a>,
+ oder per IRC, Jabber oder telefonisch unter <a
+ href="tel:+49-228-92934160">+49 228 92934160</a>, falls
+ dieser Fehler bestehenbleibt.</p>'
+
+       print Content-type: text/html
+       print
+       sed \
+           -e "s\ 1@!name!@\ 1CGI-Fehler\ 1" \
+           -e 's\ 1@!head!@\ 1CGI-Fehler\ 1' \
+           -e "s\ 1@!body!@\ 1${|sed_escape "$body";}\ 1" \
+           -e "s\ 1^.*TECKIDS_HTSCONV_GENDATE_TAG.*$\ 1<p class=\"rcsdiv\">Erstellt am <span class=\"rcsid\">$(date +'%F um %T Uhr').</span></p>\ 1" \
+           <EMPTY.htm
+       exit $rc
+}
+
+function die {
+       xdie die 1 "Fehler: $*"
+}
+
+[[ $HTTP_HOST = staging.teckids.org ]] || [[ $HTTPS = on ]] || die Keine gesicherte Verbindung.
+
+function dofield {
+       # Parse one field from the query string
+       if [[ $1 != *=* ]]; then
+               #print -r -- "D: non-field '$1' found"
+               return
+       fi
+       fldk=${1%%=*}
+       fldv=${1#*=}
+       # unescape spaces
+       fldv=${fldv//'+'/ }
+       # unescape percent via backslash-unescaping ksh print builtin
+       fldv=${fldv//\\/\\\\}
+       fldv=${fldv//@(%)/\\x}
+       fldv=$(print -- "$fldv"x)
+       fldv=${fldv%x}
+       for x in "${fields[@]}"; do
+               [[ $fldk = "$x" ]] || continue
+               eval $x=\$fldv
+               break
+       done
+}
+
+# Parse the GET query string
+set -A fields -- vote mode
+inp=$QUERY_STRING
+while [[ $inp = *'&'* ]]; do
+       fld=${inp%%'&'*}
+       inp=${inp#*'&'}
+       dofield "$fld"
+done
+[[ -n $inp ]] && dofield "$inp"
+[[ -z $mode ]] && mode=vote
+
+# Validate name of vote
+[[ $vote = +([a-z]|[A-Z]|[0-9]|_|-) ]] || die Ungültiger Abstimmungsname!
+
+# Look for vote definition file
+vd=/var/lib/teckids/vote/$vote
+[[ -e "$vd/vote" ]] || die Ungültiger Abstimmungsname!
+
+# Parse vote definition file in state machine
+s=0; q=0; set -A q_questions; set -A q_types; set -A q_choices; set -A q_detail
+while IFS= read -r line; do
+       case $s in
+       0)
+               # First block: global metadata
+               k=${line%%: *}
+               v=${line#*: }
+
+               # FIXME do something useful on missing tags
+               case $k in
+               Title)
+                       v_title=$v
+                       ;;
+               Description)
+                       v_desc=$v
+                       ;;
+               Initiator)
+                       v_initiator=$v
+                       ;;
+               Group|Groups)
+                       v_groups="|$v|"
+                       ;;
+               Anonymous)
+                       v_anonymous=$v
+                       ;;
+               Until)
+                       v_until=$v
+                       ;;
+               esac
+
+               [[ -z $k ]] && s=1
+               ;;
+       1)
+               # Following blocks: question definitions
+               k=${line%%: *}
+               v=${line#*: }
+
+               # FIXME do something useful on missing tags
+               case $k in
+               Question)
+                       q_questions+=("$v")
+                       eval set -A q_answers_$q
+                       ;;
+               Detail)
+                       q_detail+=("$v")
+                       ;;
+               Type)
+                       q_types+=("$v")
+                       ;;
+               Choice)
+                       q_choices+=("$v")
+                       c=0
+                       sIFS=$IFS; IFS="|"; set -A choices -- ${q_choices[q]}; IFS=$sIFS
+                       j=0; while (( j < ${#choices[@]} )); do
+                               eval "q_answers_${q}_$j=0"
+                               eval "set -A q_answers_${q}_${j}_names"
+                               (( j++ ))
+                       done
+                       ;;
+               esac
+
+               [[ -z $k ]] && (( q++ ))
+               ;;
+       esac
+done <"$vd/vote"
+(( q++ ))
+
+# Check authenticated user is in the group defined in the vote definition
+group_ok=0
+id -nGz "$REMOTE_USER" |&
+while IFS= read -r -d '' -p group; do
+       if [[ $v_groups = *"|$group|"* ]]; then
+               group_ok=1
+               break
+       fi
+done
+(( group_ok )) || [[ $v_initiator = "$REMOTE_USER" ]] || \
+    die Nicht zur Abstimmung berechtigt!
+
+# If we got a GET request and mode is vote, render the form
+if [[ $REQUEST_METHOD = GET && $mode = vote ]]; then
+       # Bail out if end date has passed
+       [[ -n $v_until ]] && (( $(date +"%s") > $v_until )) && die Abstimmung abgelaufen!
+
+       # Check whether the authenticated user has not yet voted
+       [[ -e "$vd/replies/$REMOTE_USER" ]] && die Bereits abgestimmt!
+
+       # Get full name of vote initiator from nss/GECOS
+       fn_initiator=$(getent passwd "$v_initiator" | cut -d: -f5)
+
+       out=
+       out+="<p>Diese Abstimmung wurde von $(xhtml_escape "$fn_initiator") gestartet.</p>"
+       if [[ -n $v_until ]]; then
+               out+="<p>Die Abstimmung ist bis <b>$(LC_ALL=de_DE.UTF-8 date -d @$v_until +'%A, den %d.%m.%Y, um %H:%M Uhr')</b> möglich.</p>"
+       fi
+       out+="<p>$(xhtml_escape "$v_desc")</p>"
+       out+="<hr /><form method=\"post\" enctype=\"application/x-www-form-urlencoded\" accept-charset=\"utf-8\">"
+
+       i=0
+       while (( i < q )); do
+               out+="<h2>Frage Nr. $(( i+1 ))</h2>"
+               out+="<p style=\"font-weight:bold\">$(xhtml_escape "${q_questions[i]}")</p>"
+               out+="<p>$(xhtml_escape "${q_detail[i]}")</p>"
+               case ${q_types[i]} in
+               radio)
+                       sIFS=$IFS; IFS="|"; set -A choices -- ${q_choices[i]}; IFS=$sIFS
+                       j=0
+                       for c in "${choices[@]}"; do
+                               out+="<input type=\"radio\" name=\"q_$i\" value=\"$j\" /> $(xhtml_escape "$c")<br />"
+                               (( j++ ))
+                       done
+                       out+="<input type=\"radio\" name=\"q_$i\" value=\"-1\" /> Enthaltung<br />"
+                       ;;
+               check)
+                       sIFS=$IFS; IFS="|"; set -A choices -- ${q_choices[i]}; IFS=$sIFS
+                       j=0
+                       for c in "${choices[@]}"; do
+                               out+="<input type=\"checkbox\" name=\"q_${i}_$j\" value=\"$j\" /> $(xhtml_escape "$c")<br />"
+                               (( j++ ))
+                       done
+                       ;;
+               text)
+                       out+="<textarea name=\"q_$i\" cols=\"60\" rows=\"10\"></textarea><br />"
+                       ;;
+               esac
+               (( i++ ))
+       done
+
+       out+="<hr /><input type=\"submit\" value=\"Abschicken\" /></form>"
+
+       print Content-type: text/html
+       print
+       sed \
+           -e "s\ 1@!name!@\ 1Abstimmung: $(xhtml_escape "$v_title")\ 1" \
+           -e "s\ 1@!head!@\ 1Abstimmung: $(xhtml_escape "$v_title")\ 1" \
+           -e "s\ 1@!body!@\ 1$out\ 1" \
+           -e "s\ 1^.*TECKIDS_HTSCONV_GENDATE_TAG.*$\ 1<p class=\"rcsdiv\">Erstellt am <span class=\"rcsid\">$(date +'%F um %T Uhr').</span></p>\ 1" \
+           <EMPTY.htm
+# If we got a GET request and mode is results, render the results
+elif [[ $REQUEST_METHOD = GET && $mode = results ]]; then
+       # Parse all results
+       set -A a_dates; set -A a_users; set -A a_names
+       for f in $vd/replies/*; do
+               [[ $f = "$vd/replies/*" ]] && continue
+
+               s=0
+               while IFS= read -r line; do
+                       case $s in
+                       0)
+                               # First block: global metadata
+                               k=${line%%: *}
+                               v=${line#*: }
+
+                               # FIXME do something useful on missing tags
+                               case $k in
+                               Date)
+                                       a_dates+=($v)
+                                       ;;
+                               User)
+                                       u=$v
+                                       a_users+=($v)
+                                       fn=$(getent passwd "$v" | cut -d: -f5)
+                                       a_names+=("$fn")
+                                       ;;
+                               esac
+
+                               [[ -z $k ]] && s=1
+                               ;;
+                       1)
+                               # Following blocks: answer definitions
+                               k=${line%%: *}
+                               v=${line#*: }
+
+                               # Check for paragraph end
+                               if [[ $line = " ." ]]; then
+                                       eval "q_answers_${i}+=(\"$a\")"
+                                       eval "q_answers_${i}_names+=($u)"
+                                       continue
+                               # Check for line continuation
+                               elif [[ $line = " "* ]]; then
+                                       a+="\ 1${line# }"
+                                       continue
+                               fi
+
+
+                               # FIXME do something useful on missing tags
+                               case $k in
+                               Question)
+                                       i=$v
+                                       a=
+                                       ;;
+                               Answer)
+                                       case ${q_types[i]} in
+                                       radio|check)
+                                               sIFS=$IFS; IFS="|"; set -A choices -- $v; IFS=$sIFS
+                                               for c in "${choices[@]}"; do
+                                                       eval "(( q_answers_${i}_$c++ ))"
+                                                       eval "q_answers_${i}_${c}_names+=($u)"
+                                               done
+                                               ;;
+                                       text)
+                                               a=$v
+                                               ;;
+                                       esac
+                                       ;;
+                               esac
+                               ;;
+                       esac
+               done <"$f"
+       done
+
+       # Find max values
+       i=0
+       while (( i < q )); do
+               case ${q_types[i]} in
+               radio|check)
+                       eval q_answers_${i}_max=0
+                       sIFS=$IFS; IFS="|"; set -A choices -- ${q_choices[i]}; IFS=$sIFS
+                       j=0
+                       for c in "${choices[@]}"; do
+                               eval r=\$q_answers_${i}_$j
+                               eval m=\$q_answers_${i}_max
+                               (( r > m )) && eval q_answers_${i}_max=$r
+                               (( j++ ))
+                       done
+                       ;;
+               esac
+
+               out+="</table>"
+               (( i++ ))
+       done
+
+       # Get full name of vote initiator from nss/GECOS
+       fn_initiator=$(getent passwd "$v_initiator" | cut -d: -f5)
+
+       out=
+       out+="<p>Diese Abstimmung wurde von $(xhtml_escape "$fn_initiator") gestartet.</p>"
+       out+="<p>$(xhtml_escape "$v_desc")</p>"
+       out+="<hr />"
+       out+="<h2>Abstimmungsteilnehmer</h2>"
+       out+="<ul>"
+       for n in "${a_names[@]}"; do
+               out+="<li>$n</li>"
+       done
+       out+="</ul>"
+
+       i=0
+       while (( i < q )); do
+               out+="<h2>Frage Nr. $(( i+1 ))</h2>"
+               out+="<p style=\"font-weight:bold\">$(xhtml_escape "${q_questions[i]}")</p>"
+               out+="<p>$(xhtml_escape "${q_detail[i]}")</p>"
+               out+="<table border=\"1\" style=\"width: 100%\">"
+
+               case ${q_types[i]} in
+               radio|check)
+                       out+="<tr><th>Antwort</th><th>Personen</th><th>Anzahl</th></tr>"
+                       sIFS=$IFS; IFS="|"; set -A choices -- ${q_choices[i]}; IFS=$sIFS
+                       j=0
+                       for c in "${choices[@]}"; do
+                               eval r=\$q_answers_${i}_$j
+                               eval m=\$q_answers_${i}_max
+                               rowstyle=
+                               (( r == m)) && rowstyle=" style=\"background-color: #66ff66\""
+                               out+="<tr$rowstyle><td style=\"text-align: left\">$c</td><td><ul>"
+                               if [[ $v_anonymous = no ]]; then
+                                       eval "nameref us=q_answers_${i}_${j}_names"
+                                       for u in "${us[@]}"; do
+                                               out+="<li>$(getent passwd "$u" | cut -d: -f5)</li>"
+                                       done
+                               fi
+                               out+="</ul></td><td style=\"text-align: right\">$r</td></tr>"
+                               (( j++ ))
+                       done
+                       ;;
+               text)
+                       out+="<tr><th>Antwort</th><th>Person</th></tr>"
+                       eval "nameref as=q_answers_${i}"
+                       l=0
+                       for a in "${as[@]}"; do
+                               if [[ -n $a ]]; then
+                                       eval "u=\${q_answers_${i}_names[l]}"
+                                       out+="<tr><td>$(xhtml_escape "${a//"$lf"/\ 1}" | sed 's!\ 1!<br />!g')</td><td>"
+                                       if [[ $v_anonymous = no ]]; then
+                                               out+=$(getent passwd "$u" | cut -d: -f5)
+                                       fi
+                                       out+="</td></tr>"
+                               fi
+                               (( l++ ))
+                       done
+               esac
+
+               out+="</table>"
+               (( i++ ))
+       done
+
+       out+="<hr />"
+
+       print Content-type: text/html
+       print
+       sed \
+           -e "s\ 1@!name!@\ 1Abstimmungsergebnis: $(xhtml_escape "$v_title")\ 1" \
+           -e "s\ 1@!head!@\ 1Abstimmungsergebnis: $(xhtml_escape "$v_title")\ 1" \
+           -e "s\ 1@!body!@\ 1$out\ 1" \
+           -e "s\ 1^.*TECKIDS_HTSCONV_GENDATE_TAG.*$\ 1<p class=\"rcsdiv\">Erstellt am <span class=\"rcsid\">$(date +'%F um %T Uhr').</span></p>\ 1" \
+           <EMPTY.htm
+elif [[ $REQUEST_METHOD = POST ]]; then
+       # Parse POST data
+       set -A fields --
+
+       inp=$(cat)
+       i=0
+       while (( i < q )); do
+               if [[ ${q_types[i]} = check ]]; then
+                       sIFS=$IFS; IFS="|"; set -A choices -- ${q_choices[i]}; IFS=$sIFS
+                       j=0
+                       for c in "${choices[@]}"; do
+                               fields+=(q_${i}_$j)
+                               (( j++ ))
+                       done
+               else
+                       fields+=(q_$i)
+               fi
+               (( i++ ))
+       done
+       while [[ $inp = *'&'* ]]; do
+               fld=${inp%%'&'*}
+               inp=${inp#*'&'}
+               dofield "$fld"
+       done
+       [[ -n $inp ]] && dofield "$inp"
+
+       {
+               print -r -- "Date: $(date)"
+               print -r -- "User: $REMOTE_USER"
+               print
+
+               i=0
+               while (( i < q )); do
+                       if [[ ${q_types[i]} = radio ]]; then
+                               eval v=\$q_$i
+                               if [[ $v = -1 ]]; then
+                                       # Enthaltung; do not write to file at all
+                                       (( i++ ))
+                                       continue
+                               fi
+                       fi
+
+                       print -r -- "Question: $i"
+                       if [[ ${q_types[i]} = check ]]; then
+                               sIFS=$IFS; IFS="|"; set -A choices -- ${q_choices[i]}; IFS=$sIFS
+                               j=0
+                               a=
+                               for c in "${choices[@]}"; do
+                                       eval v=\$q_${i}_$j
+                                       [[ $v = $j ]] && a+="$j|"
+                                       (( j++ ))
+                               done
+                               a=${a%\|}
+                               print -r -- "Answer: $a"
+                       elif [[ ${q_types[i]} = text ]]; then
+                               eval v=\$q_$i
+                               if [[ $v != *"$crlf"* ]]; then
+                                       print -r -- "Answer: $v$lf ."
+                               else
+                                       v=${v//$crlf/$lf }
+                                       print -r -- "Answer: $v$lf ."
+                               fi
+                       else
+                               eval v=\$q_$i
+                               print -r -- "Answer: $v"
+                       fi
+                       (( i++ ))
+
+                       print
+               done
+       } >"$vd/replies/$REMOTE_USER"
+
+       print Content-type: text/html
+       print
+       sed \
+           -e "s\ 1@!name!@\ 1Abstimmung: $(xhtml_escape "$v_title")\ 1" \
+           -e "s\ 1@!head!@\ 1Abstimmung: $(xhtml_escape "$v_title")\ 1" \
+           -e "s\ 1@!body!@\ 1<p>Deine Abstimmung wurde gespeichert!</p><p><a href=\"auth_votes.cgi\">Zurück zu der Liste der Abstimmungen</a></p>\ 1" \
+           -e "s\ 1^.*TECKIDS_HTSCONV_GENDATE_TAG.*$\ 1<p class=\"rcsdiv\">Erstellt am <span class=\"rcsid\">$(date +'%F um %T Uhr').</span></p>\ 1" \
+           <EMPTY.htm
+fi
+
+exit 0
diff --git a/mksh/teckids/www/auth_votes.cgi b/mksh/teckids/www/auth_votes.cgi
new file mode 100755 (executable)
index 0000000..1049a70
--- /dev/null
@@ -0,0 +1,193 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010,
+#              2011, 2012, 2013, 2014
+#      Thorsten “mirabilos” Glaser <tg@mirbsd.org>
+# Copyright © 2013, 2014, 2015
+#      Thorsten Glaser <thorsten.glaser@teckids.org>
+# Copyright © 2015
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+unset HTTP_PROXY
+
+exec 2>/tmp/a
+set -x
+
+set +U
+cd "$(dirname "$0")"
+
+function sed_escape {
+       REPLY=$1
+       REPLY=${REPLY//\\/\\\\}
+       REPLY=${REPLY//[&]/\\&}
+       REPLY=${REPLY//$'\n'/\\n}
+}
+
+#{{{ magic from MirOS: www/mk/common,v 1.7
+# escape XHTML characters (three mandatory XML ones plus double quotes,
+# the latter in an XML safe fashion numerically though)
+function xhtml_escape {
+       if (( $# )); then
+               print -nr -- "$@"
+       else
+               cat
+       fi | sed \
+           -e 's\ 1&\ 1\&amp;\ 1g' \
+           -e 's\ 1<\ 1\&lt;\ 1g' \
+           -e 's\ 1>\ 1\&gt;\ 1g' \
+           -e 's\ 1"\ 1\&#34;\ 1g'
+}
+#}}}
+
+cr=$'\r'
+lf=$'\n'
+crlf=$'\r\n'
+
+function xdie {
+       local body x funcname=$1 rc=$2; shift; shift
+
+       for x in "$@"; do
+               body+="<p class=\"cgierr\">$(xhtml_escape "$x")</p>$nl"
+       done
+       body+='
+<p>Kontaktieren Sie uns ggfs. direkt per eMail unter <a
+ href="mailto:vorstand@teckids.org">&lt;vorstand@teckids.org&gt;</a>,
+ oder per IRC, Jabber oder telefonisch unter <a
+ href="tel:+49-228-92934160">+49 228 92934160</a>, falls
+ dieser Fehler bestehenbleibt.</p>'
+
+       print Content-type: text/html
+       print
+       sed \
+           -e "s\ 1@!name!@\ 1CGI-Fehler\ 1" \
+           -e 's\ 1@!head!@\ 1CGI-Fehler\ 1' \
+           -e "s\ 1@!body!@\ 1${|sed_escape "$body";}\ 1" \
+           -e "s\ 1^.*TECKIDS_HTSCONV_GENDATE_TAG.*$\ 1<p class=\"rcsdiv\">Erstellt am <span class=\"rcsid\">$(date +'%F um %T Uhr').</span></p>\ 1" \
+           <EMPTY.htm
+       exit $rc
+}
+
+function die {
+       xdie die 1 "Fehler: $*"
+}
+
+[[ $HTTP_HOST = staging.teckids.org ]] || [[ $HTTPS = on ]] || die Keine gesicherte Verbindung.
+
+# Look for vote definition file
+vd=/var/lib/teckids/vote
+cd "$vd"
+
+# Iterate over votes, sorted by date
+set -A votes_open; set -A votes_voted; set -A votes_missed
+for v in *; do
+       [[ $v = "*" ]] && continue
+       print -r -- "$(sed -n '/^Until: /s///p' "$v/vote") $v"
+done | LC_ALL=de_DE.UTF-8 sort -k1,1nr -k2 |&
+exec 3>&p; exec 3>&-; exec 3<&p
+while read -u3 v v; do
+       # Get vote group names
+       groups=$(grep "^Groups: " "$vd/$v/vote")
+       groups="|${groups#Groups: }|"
+
+       # Find end date
+       until=$(grep "^Until: " "$vd/$v/vote")
+        until=${until#Until: }
+
+       # Get vote initiator
+       initiator=$(grep "^Initiator: " "$vd/$v/vote")
+        initiator=${initiator#Initiator: }
+
+       # Check authenticated user is in the group defined in the vote definition
+       group_ok=0
+       id -nGz "$REMOTE_USER" |&
+       while IFS= read -r -d '' -p group; do
+               if [[ $groups = *"|$group|"* ]]; then
+                       group_ok=1
+                       break
+               fi
+       done
+       (( group_ok )) || [[ $initiator = $REMOTE_USER ]] || continue
+
+       if [[ -e "$vd/$v/replies/$REMOTE_USER" ]]; then
+               votes_voted+=($v)
+       elif [[ -z $until ]] || (( $(date +"%s") < $until )); then
+               votes_open+=($v)
+       else
+               votes_missed+=($v)
+       fi
+done
+cd - >/dev/null
+
+out=
+
+out+="<h2>Offene Abstimmungen - bitte abstimmen!</h2>"
+out+="<table border=\"1\" style=\"width: 100%\"><tr><th>Abstimmung</th><th>Enddatum</th><th>Aktion</th></tr>"
+for v in "${votes_open[@]}"; do
+       name=$(grep "^Title: " "$vd/$v/vote")
+        name=${name#Title: }
+       until=$(grep "^Until: " "$vd/$v/vote")
+        until=${until#Until: }
+       out+="<tr><td>$(xhtml_escape "$name")</td><td>"
+       if [[ -n $until ]]; then
+               out+=$(LC_ALL=de_DE.UTF-8 date -d @$until +'%A, den %d.%m.%Y, um %H:%M Uhr')
+       fi
+       out+="</td><td><a href=\"auth_vote.cgi?vote=$v\">Jetzt abstimmen</a></td></tr>"
+done
+out+="</table>"
+
+out+="<h2>Abstimmungen, an denen du teilgenommen hast</h2>"
+out+="<table border=\"1\" style=\"width: 100%\"><tr><th>Abstimmung</th><th>Enddatum</th><th>Aktion</th></tr>"
+for v in "${votes_voted[@]}"; do
+       name=$(grep "^Title: " "$vd/$v/vote")
+        name=${name#Title: }
+       until=$(grep "^Until: " "$vd/$v/vote")
+        until=${until#Until: }
+       out+="<tr><td>$(xhtml_escape "$name")</td><td>"
+       if [[ -n $until ]]; then
+               out+=$(LC_ALL=de_DE.UTF-8 date -d @$until +'%A, den %d.%m.%Y, um %H:%M Uhr')
+       fi
+       out+="</td><td><a href=\"auth_vote.cgi?vote=$v\\&amp;mode=results\">Ergebnis anzeigen</a></td></tr>"
+done
+out+="</table>"
+
+out+="<h2>Verpasste Abstimmungen</h2>"
+out+="<table border=\"1\" style=\"width: 100%\"><tr><th>Abstimmung</th><th>Enddatum</th><th>Aktion</th></tr>"
+for v in "${votes_missed[@]}"; do
+       name=$(grep "^Title: " "$vd/$v/vote")
+        name=${name#Title: }
+       until=$(grep "^Until: " "$vd/$v/vote")
+        until=${until#Until: }
+       out+="<tr><td>$(xhtml_escape "$name")</td><td>"
+       if [[ -n $until ]]; then
+               out+=$(LC_ALL=de_DE.UTF-8 date -d @$until +'%A, den %d.%m.%Y, um %H:%M Uhr')
+       fi
+       out+="</td><td><a href=\"auth_vote.cgi?vote=$v\\&amp;mode=results\">Ergebnis anzeigen</a></td></tr>"
+done
+out+="</table>"
+
+print Content-type: text/html
+print
+sed \
+    -e "s\ 1@!name!@\ 1Abstimmungen\ 1" \
+    -e "s\ 1@!head!@\ 1Abstimmungen\ 1" \
+    -e "s\ 1@!body!@\ 1$out\ 1" \
+    -e "s\ 1^.*TECKIDS_HTSCONV_GENDATE_TAG.*$\ 1<p class=\"rcsdiv\">Erstellt am <span class=\"rcsid\">$(date +'%F um %T Uhr').</span></p>\ 1" \
+    <EMPTY.htm
+
+exit 0
diff --git a/mksh/teckids/www/betreuung_2016_herbst_anmeldung.cgi b/mksh/teckids/www/betreuung_2016_herbst_anmeldung.cgi
new file mode 100755 (executable)
index 0000000..814507d
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+
+conf=betreuung_2016_herbst
+kurz=BHerbst16
+lang="Ferienbetreuung Herbst 2016"
+
+set -A pflichtfeld   1      1       1        1            1              1               1                1             1         1        1      0      0      0      0      0      1     1            1             0     0           1         1
+set -A fieldnames -- FormID Vorname Nachname Geburtsdatum Vorname_Eltern Nachname_Eltern Anschrift_Eltern PLZOrt_Eltern Schulname Schulort Klasse Tag_Mo Tag_Di Tag_Mi Tag_Do Tag_Fr eMail Eltern_eMail Kontaktumfang Kanal Bemerkungen OK_Eltern OK_Datenschutz
+
+. "$(dirname "$0")/webform.sh"
+
+mail_extra="Mein Kind hat das Anmeldeformular gemeinsam mit mir ausgefüllt und ich bin
+mit der Teilnahme einverstanden. Darüberhinaus erkläre ich mich einverstanden, den
+Teilnehmerbeitrag vor Ort zu begleichen."
+
+automail_and_out
diff --git a/mksh/teckids/www/bwsrlang.cgi b/mksh/teckids/www/bwsrlang.cgi
new file mode 100755 (executable)
index 0000000..e28a159
--- /dev/null
@@ -0,0 +1,111 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2013, 2015
+#      Thorsten “mirabilos” Glaser <thorsten.glaser@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Language selector/redirector, based on user’s preferences.
+
+unset HTTP_PROXY
+
+nl='
+'
+set -o noglob
+while :; do
+       defaultlanguage=de
+       set -A languages -- de en
+
+       set -A hdrlang
+       set -A hdrqval
+       nlang=0
+       IFS=" ""        ${nl},"
+       set -A langs -- $HTTP_ACCEPT_LANGUAGE
+       IFS=" ""        ${nl}"
+       for lang in "${langs[@]}"; do
+               if [[ $lang = *';'* ]]; then
+                       qval=${lang#*';'}
+                       lang=${lang%%';'*}
+                       lang=${lang%%*([         ])}
+               else
+                       qval=q=1
+               fi
+               [[ $lang = '*' || $lang = +([A-Za-z])?(-+([A-Za-z])) ]] || continue
+               hdrlang[nlang]=${lang%-*}
+               qval=${qval##*([         ])}
+               [[ $qval = q*([  ])=*([  ])+([0-9.]) ]] || continue
+               qval=${qval##*=*([       ])}
+               if [[ $qval = 0?(.) ]]; then
+                       qval=0
+               elif [[ $qval = 1?(.?(0)?(0)?(0)) ]]; then
+                       qval=1000
+               elif [[ $qval = 0.?([0-9])?([0-9])?([0-9]) ]]; then
+                       qval=${qval#0.}000
+                       qval=${qval::3}
+               else
+                       continue
+               fi
+               hdrqval[nlang++]=$((10#$qval))
+       done
+       (( nlang )) || break
+       # anything found at all
+       set -A userlang
+       i=-1
+       while (( ++i < nlang )); do
+               curqval=-1
+               curidx=-1
+               j=-1
+               while (( ++j < nlang )); do
+                       [[ -n ${hdrlang[j]} ]] || continue
+                       (( hdrqval[j] > curqval )) || continue
+                       curqval=${hdrqval[j]}
+                       curidx=$j
+               done
+               userlang[i]=${hdrlang[curidx]}
+               unset hdrlang[curidx]
+       done
+       # got a sorted list of prefs
+       changeddefault=0
+       for lang in "${userlang[@]}"; do
+               found=0
+               for x in "${languages[@]}"; do
+                       [[ $lang = "$x" ]] || continue
+                       found=1
+                       break
+               done
+               if (( found )); then
+                       defaultlanguage=$lang
+                       break
+               fi
+               (( changeddefault )) && continue
+               # if not in list and not “*” switch fallback to en
+               # if “*” is first not-in-list keep fallback as de
+               [[ $lang = '*' ]] || defaultlanguage=en
+               changeddefault=1
+       done
+       # when we arrive here, it's from one of these scenarios:
+       # - one of the ${languages[@]} was found: first match wins
+       # - none was found; first was *: keep de
+       # - none was found; first was not *: fall back to en
+       # when no valid lang pref was given by the user we break much earlier
+       # in all cases, $defaultlanguage is set
+       break
+done
+
+print 'Content-type: text/plain'
+print
+print -nr -- $defaultlanguage
diff --git a/mksh/teckids/www/schultab.cgi b/mksh/teckids/www/schultab.cgi
new file mode 100755 (executable)
index 0000000..8cd7bba
--- /dev/null
@@ -0,0 +1,147 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2015
+#      Thorsten Glaser <thorsten.glaser@teckids.org>
+# Copyright © 2007, 2008, 2012, 2013, 2014
+#      Thorsten “mirabilos” Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Backend for auto-completion of schools.
+
+unset HTTP_PROXY
+
+export LC_ALL=C
+unset LANGUAGE
+set +U
+
+function die {
+       print 'Status: 400\r'
+       print 'Content-Type: text/plain; charset="UTF-8"\r'
+       print '\r'
+       print -r -- "$@" | sed $'s/$/\r/'
+       exit 0
+}
+
+# escape string into JSON string (with surrounding quotes)
+function json_escape {
+       local o=\" s
+       if (( $# )); then
+               read -raN-1 s <<<"$*"
+               unset s[${#s[*]}-1]
+       else
+               read -raN-1 s
+       fi
+       local -i i=0 n=${#s[*]} wc
+       local -Uui16 -Z7 x
+       local -i1 ch
+
+       while (( i < n )); do
+               (( ch = x = wc = s[i++] ))
+               case $wc {
+               (8) o+=\\b ;;
+               (9) o+=\\t ;;
+               (10) o+=\\n ;;
+               (12) o+=\\f ;;
+               (13) o+=\\r ;;
+               (34) o+=\\\" ;;
+               (92) o+=\\\\ ;;
+               (*)
+                       if (( wc < 0x20 || wc > 0xFFFD || \
+                           (wc >= 0xD800 && wc <= 0xDFFF) || \
+                           (wc > 0x7E && wc < 0xA0) )); then
+                               o+=\\u${x#16#}
+                       else
+                               o+=${ch#1#}
+                       fi
+                       ;;
+               }
+       done
+       REPLY="$o\""
+}
+
+set -A fields -- q
+
+function dofield {
+       if [[ $1 != *=* ]]; then
+               #print -r -- "D: non-field '$1' found"
+               return
+       fi
+       fldk=${1%%=*}
+       fldv=${1#*=}
+       # unescape spaces
+       fldv=${fldv//'+'/ }
+       # unescape percent via backslash-unescaping ksh print builtin
+       fldv=${fldv//\\/\\\\}
+       fldv=${fldv//@(%)/\\x}
+       fldv=$(print -- "$fldv"x)
+       fldv=${fldv%x}
+       for x in "${fields[@]}"; do
+               [[ $fldk = "$x" ]] || continue
+               eval $x=\$fldv
+               break
+       done
+}
+
+inp=$QUERY_STRING
+while [[ $inp = *'&'* ]]; do
+       fld=${inp%%'&'*}
+       inp=${inp#*'&'}
+       dofield "$fld"
+done
+[[ -n $inp ]] && dofield "$inp"
+
+set -U
+# trim
+q=${q##+([       \r])}
+q=${q%%+([       \r])}
+# check
+[[ -n $q ]] || die 'Leerer Suchbegriff'
+export LC_ALL=C.UTF-8
+[[ $q = +([ !\"&-*,-:A-Za-z ®ÄÖÜßäçèéöüž]) ]] || die 'Ungültiges Zeichen im Suchbegriff'
+
+# ugh…
+q=${q//\\/\\5C}
+q=${q//'('/\\28}
+q=${q//')'/\\29}
+q=${q//'*'/\\2A}
+
+# words to wildcards
+q=\*${q//+([  ])/*}\*
+
+# to LDAP
+. /usr/local/share/teckids/mk/assockit.ksh
+. /usr/local/share/teckids/mk/assoldap.ksh
+
+asso_setldap_sasl r -- -b ou=Schulen,ou=Contacts,dc=teckids,dc=org \
+    "(o=$q)" o l
+asso_loadk r
+
+# JSON output
+print 'Content-Type: application/json; charset="UTF-8"\r'
+print '\r'
+print -n '['
+sep=
+for dn in "${asso_y[@]}"; do
+       o=$(asso_getv r "$dn" o 0)
+       l=$(asso_getv r "$dn" l 0)
+       [[ -n $o ]] || continue
+       print -nr -- "$sep{\"o\":${|json_escape "$o";},\"l\":${|json_escape "$l";}}"
+       sep=,
+done
+print -n ']'
+exit 0
diff --git a/mksh/teckids/www/sponsoren-vcard.cgi b/mksh/teckids/www/sponsoren-vcard.cgi
new file mode 100755 (executable)
index 0000000..4844542
--- /dev/null
@@ -0,0 +1,119 @@
+#!/bin/mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010,
+#              2011, 2012, 2013, 2014
+#      Thorsten “mirabilos” Glaser <tg@mirbsd.org>
+# Copyright © 2013, 2014, 2015
+#      Thorsten Glaser <thorsten.glaser@teckids.org>
+# Copyright © 2015, 2016
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+unset HTTP_PROXY
+
+set +U
+cd "$(dirname "$0")"
+
+function sed_escape {
+       REPLY=$1
+       REPLY=${REPLY//\\/\\\\}
+       REPLY=${REPLY//[&]/\\&}
+       REPLY=${REPLY//$'\n'/\\n}
+}
+
+#{{{ magic from MirOS: www/mk/common,v 1.7
+# escape XHTML characters (three mandatory XML ones plus double quotes,
+# the latter in an XML safe fashion numerically though)
+function xhtml_escape {
+       if (( $# )); then
+               print -nr -- "$@"
+       else
+               cat
+       fi | sed \
+           -e 's\ 1&\ 1\&amp;\ 1g' \
+           -e 's\ 1<\ 1\&lt;\ 1g' \
+           -e 's\ 1>\ 1\&gt;\ 1g' \
+           -e 's\ 1"\ 1\&#34;\ 1g'
+}
+#}}}
+
+cr=$'\r'
+lf=$'\n'
+crlf=$'\r\n'
+
+function dofield {
+       # Parse one field from the query string
+       if [[ $1 != *=* ]]; then
+               #print -r -- "D: non-field '$1' found"
+               return
+       fi
+       fldk=${1%%=*}
+       fldv=${1#*=}
+       # unescape spaces
+       fldv=${fldv//'+'/ }
+       # unescape percent via backslash-unescaping ksh print builtin
+       fldv=${fldv//\\/\\\\}
+       fldv=${fldv//@(%)/\\x}
+       fldv=$(print -- "$fldv"x)
+       fldv=${fldv%x}
+       for x in "${fields[@]}"; do
+               [[ $fldk = "$x" ]] || continue
+               eval $x=\$fldv
+               break
+       done
+}
+
+# Parse the GET query string
+set -A fields -- who where
+inp=$QUERY_STRING
+while [[ $inp = *'&'* ]]; do
+       fld=${inp%%'&'*}
+       inp=${inp#*'&'}
+       dofield "$fld"
+done
+[[ -n $inp ]] && dofield "$inp"
+
+if [[ -n $where ]]; then
+        bei=" bei $(xhtml_escape "$where")"
+fi
+
+out+="<p>Vielen Dank, dass wir Sie als potentiellen Sponsor ansprechen durften!</p>"
+
+if [[ -n $who ]]; then
+       fn=$(getent passwd "$who" | cut -d: -f5)
+       out+="<h3>Sie sprachen mit…</h3>"
+       out+="<table style=\"border: 1px solid; width: 700px\"><tr><td><img src=\"/people/$who.jpg\" alt=\"$(xhtml_escape "$fn")\" style=\"width: 250px\" /></td>"
+       out+="<td><p style=\"font-weight: bold; text-size: 150%\">$(xhtml_escape "$fn")</p>"
+       out+="<p>Teckids e.V.<br />c/o tarent solutions GmbH<br />Rochusstr. 2-4<br />53123 Bonn</p>"
+       out+="<p>Telefon: +49 228 9293416 0<br />E-Mail> <a href=\"mailto:verein@teckids.org\">verein@teckids.org</a></p></td></tr></table>"
+fi
+
+out+="<h3>Informationen über das Sponsoring</h3>"
+out+="<p>Alle Informationen über die Möglichkeiten des Sponsorings haben wir in unseren <b>Sponsoring Facts</b> zusammengefasst. Sie finden das Dokument <a href=\"/docs/verein/docs/sponsoring-facts-allgemein.pdf\">hier zum Download</a></p>"
+out+="<p>Bitte sprechen Sie uns jederzeit per Telefon oder E-Mail an!</p>"
+
+print Content-type: text/html
+print
+sed \
+    -e "s\ 1@!name!@\ 1Ihr Sponsoring-Gespräch\ 1" \
+    -e "s\ 1@!head!@\ 1Ihr Sponsoring-Gespräch$bei\ 1" \
+    -e "s\ 1@!body!@\ 1$out\ 1" \
+    -e "s\ 1^.*TECKIDS_HTSCONV_GENDATE_TAG.*$\ 1<p class=\"rcsdiv\">Erstellt am <span class=\"rcsid\">$(date +'%F um %T Uhr').</span></p>\ 1" \
+    <EMPTY.htm
+
+exit 0
diff --git a/mksh/teckids/www/webform.sh b/mksh/teckids/www/webform.sh
new file mode 100644 (file)
index 0000000..23932e1
--- /dev/null
@@ -0,0 +1,712 @@
+# -*- mode: sh -*-
+#-
+# Copyright © 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010,
+#              2011, 2012, 2013, 2014
+#      Thorsten “mirabilos” Glaser <tg@mirbsd.org>
+# Copyright © 2013, 2014, 2015, 2016
+#      Thorsten Glaser <thorsten.glaser@teckids.org>
+# Copyright © 2015
+#      Dominik George <dominik.george@teckids.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+
+unset HTTP_PROXY
+
+: "${suffix:=anmeldung}"
+: "${sepa_gid:=ZZZ}"
+: "${sepa_betrag:=0}"
+
+unset LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+    LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+    LC_TELEPHONE LC_TIME
+set +U
+cd "$(dirname "$0")"
+nl=$'\n'
+
+function sed_escape {
+       REPLY=$1
+       REPLY=${REPLY//\\/\\\\}
+       REPLY=${REPLY//[&]/\\&}
+       REPLY=${REPLY//$'\n'/\\$'\n'}
+}
+
+function sed_escape_re {
+       REPLY=$(sed -e 's\ 1[^^]\ 1[&]\ 1g; s\ 1\^\ 1\\^\ 1g; $!a\'$'\n''\\n' <<<"$1" | \
+           tr -d '\n')
+}
+
+#{{{ magic from MirOS: www/mk/common,v 1.7
+# escape XHTML characters (three mandatory XML ones plus double quotes,
+# the latter in an XML safe fashion numerically though)
+function xhtml_escape {
+       if (( $# )); then
+               print -nr -- "$@"
+       else
+               cat
+       fi | sed \
+           -e 's\ 1&\ 1\&amp;\ 1g' \
+           -e 's\ 1<\ 1\&lt;\ 1g' \
+           -e 's\ 1>\ 1\&gt;\ 1g' \
+           -e 's\ 1"\ 1\&#34;\ 1g'
+}
+
+# magic from MirOS: src/kern/c/mirtime.c,v 1.3 2011/11/20 23:40:10 tg Exp $
+
+# struct tm members and (POSIX) time functions
+typeset -ir tm_sec=0           # seconds [0-59]
+typeset -ir tm_min=1           # minutes [0-59]
+typeset -ir tm_hour=2          # hours [0-23]
+typeset -ir tm_mday=3          # day of month [1-31]
+typeset -ir tm_mon=4           # month of year - 1 [0-11]
+typeset -ir tm_year=5          # year - 1900
+typeset -ir tm_wday=6          # day of week [0 = sunday]      input:ignored
+typeset -ir tm_yday=7          # day of year [0-365]           input:ignored
+typeset -ir tm_isdst=8         # summer time act.? [0/1] (0)   input:ignored
+typeset -ir tm_gmtoff=9                # seconds offset from UTC (0)
+typeset -ir tm_zone=10         # abbrev. of timezone ("UTC")   input:ignored
+
+set -A mirtime_months -- Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
+set -A mirtime_wdays -- Sun Mon Tue Wed Thu Fri Sat
+readonly mirtime_months[*] mirtime_wdays[*]
+
+# $ timet2mjd posix_timet
+# ⇒ mjd sec
+function timet2mjd {
+       local -i10 mjd=$1 sec
+
+       (( sec = mjd % 86400 ))
+       (( mjd = (mjd / 86400) + 40587 ))
+       while (( sec < 0 )); do
+               (( --mjd ))
+               (( sec += 86400 ))
+       done
+
+       print -- $mjd $sec
+}
+
+# $ mjd2timet mjd sec
+# ⇒ posix_timet
+function mjd2timet {
+       local -i10 t=$1 sec=$2
+
+       (( t = (t - 40587) * 86400 + sec ))
+       print -- $t
+}
+
+# $ mjd_explode mjd sec
+# ⇒ tm_sec tm_min tm_hour tm_mday tm_mon tm_year \
+#   tm_wday tm_yday "0" "0" "UTC"
+function mjd_explode {
+       local tm
+       set -A tm
+       local -i10 sec=$2 day yday mon year=$1
+
+       while (( sec < 0 )); do
+               (( --year ))
+               (( sec += 86400 ))
+       done
+       while (( sec >= 86400 )); do
+               (( ++year ))
+               (( sec -= 86400 ))
+       done
+
+       (( day = year % 146097 + 678881 ))
+       (( year = 4 * ((year / 146097) + (day / 146097)) ))
+       (( day %= 146097 ))
+       (( tm[tm_wday] = (day + 3) % 7 ))
+       if (( day == 146096 )); then
+               (( year += 3 ))
+               (( day = 36524 ))
+       else
+               (( year += day / 36524 ))
+               (( day %= 36524 ))
+       fi
+       (( year = 4 * ((year * 25) + (day / 1461)) ))
+       (( day %= 1461 ))
+       (( yday = (day < 306) ? 1 : 0 ))
+       if (( day == 1460 )); then
+               (( year += 3 ))
+               (( day = 365 ))
+       else
+               (( year += day / 365 ))
+               (( day %= 365 ))
+       fi
+       (( yday += day ))
+       (( day *= 10 ))
+       (( mon = (day + 5) / 306 ))
+       (( day = ((day + 5) % 306) / 10 ))
+       if (( mon >= 10 )); then
+               (( mon -= 10 ))
+               (( yday -= 306 ))
+               (( ++year ))
+       else
+               (( mon += 2 ))
+               (( yday += 59 ))
+       fi
+       (( tm[tm_sec] = sec % 60 ))
+       (( sec /= 60 ))
+       (( tm[tm_min] = sec % 60 ))
+       (( tm[tm_hour] = sec / 60 ))
+       (( tm[tm_mday] = day + 1 ))
+       (( tm[tm_mon] = mon ))
+       (( tm[tm_year] = (year < 1 ? year - 1 : year) - 1900 ))
+       (( tm[tm_yday] = yday ))
+       (( tm[tm_isdst] = 0 ))
+       (( tm[tm_gmtoff] = 0 ))
+       tm[tm_zone]=UTC
+
+       print -r -- "${tm[@]}"
+}
+
+# $ mjd_implode tm_sec tm_min tm_hour tm_mday tm_mon tm_year \
+#   ignored ignored ignored tm_gmtoff [ignored]
+# ⇒ mjd sec
+function mjd_implode {
+       local tm
+       set -A tm -- "$@"
+       local -i10 day x y sec
+
+       (( sec = tm[tm_sec] + 60 * tm[tm_min] + 3600 * tm[tm_hour] - \
+           tm[tm_gmtoff] ))
+       (( (day = tm[tm_year] + 1900) < 0 )) && (( ++day ))
+       (( y = day % 400 ))
+       (( day = (day / 400) * 146097 - 678882 + tm[tm_mday] ))
+       while (( sec < 0 )); do
+               (( --day ))
+               (( sec += 86400 ))
+       done
+       while (( sec >= 86400 )); do
+               (( ++day ))
+               (( sec -= 86400 ))
+       done
+       (( x = tm[tm_mon] ))
+       while (( x < 0 )); do
+               (( --y ))
+               (( x += 12 ))
+       done
+       (( y += x / 12 ))
+       (( x %= 12 ))
+       if (( x < 2 )); then
+               (( x += 10 ))
+               (( --y ))
+       else
+               (( x -= 2 ))
+       fi
+       (( day += (306 * x + 5) / 10 ))
+       while (( y < 0 )); do
+               (( day -= 146097 ))
+               (( y += 400 ))
+       done
+       (( day += 146097 * (y / 400) ))
+       (( y %= 400 ))
+       (( day += 365 * (y % 4) ))
+       (( y /= 4 ))
+       (( day += 1461 * (y % 25) + 36524 * (y / 25) ))
+
+       print -- $day $sec
+}
+
+# convenience function to check (German) date input (no time-of-day)
+# input is $2, MJD is written to $$1, normalised date to $$3 if $3 is set
+function dtchk {
+       local tm mjd x saveIFS r=${2//[  \r]}
+       set -A tm
+       set -A mjd
+       set -A x
+
+       errstr="'$r' not in DD.MM.YYYY format"
+       [[ $r = +([0-9]).+([0-9]).+([0-9]) ]] || return 1
+       saveIFS=$IFS
+       IFS=.
+       set -A x -- $r
+       IFS=$saveIFS
+       if (( x[2] > 0 && x[2] < 30 )); then
+               # accept year w/o leading 20xx
+               (( x[2] += 2000 ))
+       elif (( x[2] >= 30 && x[2] <= 99 )); then
+               # accept year w/o leading 19xx
+               (( x[2] += 1900 ))
+       fi
+       (( x[2] > 1000 )) || return 1
+       set -A tm -- 0 0 0 $((x[0])) $((x[1] - 1)) $((x[2] - 1900)) \
+           - - - 0 -
+       set -A mjd -- $(mjd_implode "${tm[@]}")
+       set -A x -- $(mjd_explode "${mjd[0]}" 0)
+       local -i10 -Z2 rd rm
+       local -i10 -Z4 ry
+       (( rd = x[tm_mday] ))
+       (( rm = x[tm_mon] + 1 ))
+       (( ry = x[tm_year] + 1900 ))
+       errstr="invalid date $r, normalises to $rd.$rm.$ry ${x[tm_hour]}:${x[tm_min]}:${x[tm_sec]}"
+       [[ ${tm[tm_hour]} = ${x[tm_hour]} ]] || return 1
+       [[ ${tm[tm_min]} = ${x[tm_min]} ]] || return 1
+       [[ ${tm[tm_sec]} = ${x[tm_sec]} ]] || return 1
+       errstr="bogus date $r, normalises to $rd.$rm.$ry"
+       [[ ${tm[tm_mday]##*(0)} = ${x[tm_mday]##*(0)} ]] && tm[tm_mday]=${x[tm_mday]}   # accept day w/o leading zeroes
+       [[ ${tm[tm_mday]} = ${x[tm_mday]} ]] || return 1
+       [[ ${tm[tm_mon]##*(0)} = ${x[tm_mon]##*(0)} ]] && tm[tm_mon]=${x[tm_mon]}       # accept month w/o leading zeroes
+       [[ ${tm[tm_mon]} = ${x[tm_mon]} ]] || return 1
+       [[ ${tm[tm_year]} = ${x[tm_year]} ]] || return 1
+       errstr=
+       nameref r=$1
+       r=${mjd[0]}
+       if [[ -n $3 ]]; then
+               nameref rr=$3
+               rr="$rd.$rm.$ry"
+       fi
+       return 0
+}
+#}}} magic from MirOS: www/mk/common,v 1.7
+
+cr=$'\r'
+lf=$'\n'
+crlf=$'\r\n'
+
+function xdie {
+       local body x funcname=$1 rc=$2; shift; shift
+
+       for x in "$@"; do
+               body+="<p class=\"cgierr\">$(xhtml_escape "$x")</p>$nl"
+       done
+       body+='
+<p>Kontaktieren Sie uns ggfs. direkt per eMail unter <a
+ href="mailto:vorstand@teckids.org">&lt;vorstand@teckids.org&gt;</a>,
+ per Jabber oder telefonisch unter <a
+ href="tel:+49-228-92934160">+49 228 92934160</a>, falls
+ dieser Fehler bestehenbleibt.</p>'
+
+       print Content-type: text/html
+       print
+       sed \
+           -e "s\ 1@!name!@\ 1Fehler im Anmeldeformular ${kurz}\ 1" \
+           -e 's\ 1@!head!@\ 1Anmeldefehler\ 1' \
+           -e "s\ 1@!body!@\ 1${|sed_escape "$body";}\ 1" \
+           -e "s\ 1^.*TECKIDS_HTSCONV_GENDATE_TAG.*$\ 1<p class=\"rcsdiv\">Erstellt am <span class=\"rcsid\">$(date +'%F um %T Uhr').</span></p>\ 1" \
+           <EMPTY.htm
+       logger -t ${conf}_${suffix}.cgi "<${eltern_email:-$email}> $funcname($rc);"
+       exit $rc
+}
+
+function die {
+       xdie die 1 "Fehler: $*"
+}
+
+whence -p php >/dev/null || die Interner Fehler auf dem Server.
+
+[[ $HTTP_HOST = staging.teckids.org ]] || [[ $HTTPS = on ]] || die Keine gesicherte Verbindung.
+[[ $REQUEST_METHOD = POST ]] || die Formulareinsendung nicht gefunden.
+# evtl. weglassen
+[[ $HTTP_REFERER = @(https://www|http://staging).teckids.org/${conf}_${suffix}.@(cgi|htm) ]] || \
+    die Unerwarteter Aufrufer.
+
+set -A Lb64encode_code -- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \
+    a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 + /
+function Lb64encode {
+       [[ -o utf8-mode ]]; local u=$?
+       set +U
+       local c s t
+       if (( $# )); then
+               read -raN-1 s <<<"$*"
+               unset s[${#s[*]}-1]
+       else
+               read -raN-1 s
+       fi
+       local -i i=0 n=${#s[*]} j v
+
+       while (( i < n )); do
+               (( v = s[i++] << 16 ))
+               (( j = i < n ? s[i++] : 0 ))
+               (( v |= j << 8 ))
+               (( j = i < n ? s[i++] : 0 ))
+               (( v |= j ))
+               t+=${Lb64encode_code[v >> 18]}${Lb64encode_code[v >> 12 & 63]}
+               c=${Lb64encode_code[v >> 6 & 63]}
+               if (( i <= n )); then
+                       t+=$c${Lb64encode_code[v & 63]}
+               elif (( i == n + 1 )); then
+                       t+=$c=
+               else
+                       t+===
+               fi
+               if (( ${#t} == 76 || i >= n )); then
+                       print $t
+                       t=
+               fi
+       done
+       (( u )) || set -U
+}
+
+function automail_and_out {
+       automail
+       rv=$?
+       (( rv )) && xdie email_fail $rv \
+           'Fehler beim Versenden, bitte probieren Sie es später nochmals!'
+
+       # oder weiterleiten auf Erfolgsseite (ggf. wenn $1)
+       print Content-type: text/html
+       print
+       sed \
+           -e "s\ 1@!name!@\ 1Anmeldeformular ${kurz} erfolgreich\ 1" \
+           -e 's\ 1@!head!@\ 1Anmeldung erfolgreich\ 1' \
+           -e "s\ 1@!body!@\ 1<p>Anmeldeinformationen erfolgreich an das Teckids-Team versandt. Es kann bis zu 48 Stunden dauern, bis du eine Nachricht von uns erhältst!</p>${info}\ 1" \
+           -e "s\ 1^.*TECKIDS_HTSCONV_GENDATE_TAG.*$\ 1<p class=\"rcsdiv\">Erstellt am <span class=\"rcsid\">$(date +'%F um %T Uhr').</span></p>\ 1" \
+           <EMPTY.htm
+       logger -t ${conf}_${suffix}.cgi "<${eltern_email:-$email}> success();"
+       exit 0
+}
+
+function automail {
+       local body k x keys values v f=$-
+       set -A keys
+       set -A values
+       set -U
+       local -l kl
+       # zmax: length of max("Formular-ID", "Referer", "Alter")
+       local -i n=0 zmax=11 z
+       local -i has_dob=-1 has_age=-1 has_rem=0 has_sepa=0
+
+       for k in "${fieldnames[@]}" "${mail_show[@]}"; do
+               kl=${k//-}
+               nameref V=$kl
+               for x in "${mail_hide[@]}" formid; do
+                       # nicht "$x": wildcards können verwendet werden
+                       [[ $kl = $x ]] && continue 2
+               done
+               x=$V
+               case $kl {
+               (age|alter)
+                       has_age=$n
+                       ;;
+               (bemerkungen)
+                       has_rem=1
+                       continue
+                       ;;
+               (email*)
+                       x="<$x>"
+                       ;;
+               (geburtsdatum)
+                       has_dob=$n
+                       ;;
+               (ok_*)
+                       x=${x:-nein}
+                       ;;
+               (username)
+                       k=Benutzername
+                       ;;
+               (sepa|sepa_zahler|sepa_iban|sepa_bic)
+                       has_sepa=1
+                       continue
+                       ;;
+               }
+               z=${%k}
+               (( z = z == -1 ? ${#k} : z ))
+               (( zmax = z > zmax ? z : zmax ))
+               keys[n]=$k
+               values[n++]=$x
+       done
+
+       typeset -R$zmax k
+       typeset -R$((zmax + 2)) sep='| '
+       z=-1
+       while (( ++z < n )); do
+               k=${keys[z]}
+               v=${values[z]}
+               if [[ $v = *"$nl"* ]]; then
+                       body+="$nl$k:$nl$sep${v//"$nl"/"$nl$sep"}"
+               else
+                       body+="$nl$k: $v"
+               fi
+               if (( (has_age == -1) && (has_dob == z) )); then
+                       k=Alter
+                       body+="$nl$k: ${age:-?}"
+               fi
+       done
+
+       if (( has_rem )); then
+               k=Bemerkungen
+               body+="$nl$nl$k:$nl| ${bemerkungen//"$nl"/"$nl| "}"
+       fi
+
+       if (( has_sepa )) && [[ -n $sepa ]]; then
+               body+="$nl${nl}SEPA-Lastschriftmandat:$nl"
+               body+="| Ich ermächtige den Gläubiger Teckids e.V., Rochusstr. 2-4, 53123 Bonn$nl"
+               body+="| mit der Gläubiger-ID DE70${sepa_gid}00001497650, den Betrag von $sepa_betrag € von$nl"
+                body+="| meinem Konto einmalig mit dem SEPA-Basislastschriftverfahren einzu-$nl"
+                body+="| ziehen. Gleichzeitig weise ich meine Bank an, die vom Teckids e.V.$nl"
+                body+="| von meinem Konto eingezogene SEPA-Basislastschrift einzulösen.$nl"
+                body+="|$nl"
+                body+="|   Name des Zahlers:   $sepa_zahler$nl"
+                body+="|   IBAN des Zahlers:   $sepa_iban$nl"
+                body+="|   BIC des Zahlers:    $sepa_bic$nl"
+                body+="|$nl"
+                body+="| Die Mandatsreferenz wird mit der Zahlungsaufforderung mitgeteilt."
+       fi
+
+       if [[ -n $mail_extra ]]; then
+               body+=$nl$nl
+               body+=$mail_extra
+               body+=$nl
+       fi
+
+       k=Referer
+       body+="$nl$k: $HTTP_REFERER"
+       k=Formular-ID
+       body+="$nl$k: $formid"
+
+       case $suffix {
+       (anmeldung)
+               typ=Anmeldung
+               ;;
+       (elternformular)
+               typ=Elternformular
+               ;;
+       }
+
+       mymail "$typ [$kurz] $vorname $nachname" <<EOF
+Eingesendetes Anmeldeformular${encoding_ok:+; Kodierung nicht ok: $encoding_ok}
+$body
+EOF
+       rv=$?
+
+       # only necessary for mksh < R51
+       [[ $f = *U* ]] || set +U
+
+       return $rv
+}
+
+function addtorpl {
+       nameref d=$1
+       local namepart=$2 mailpart=$3 u=$- x y z
+       integer i
+
+       x=,
+       set -U
+       while [[ -n $namepart ]]; do
+               i=45
+               y=${namepart::i}
+               set +U
+               while (( ${#y} > 45 )); do
+                       let i--
+                       set -U
+                       y=${namepart::i}
+                       set +U
+               done
+               z=" =?utf-8?B?$(Lb64encode "$y")?="
+               x+=$crlf$z
+               set -U
+               namepart=${namepart: i}
+       done
+       set +U
+       (( ${#z} + 3 + ${#mailpart} > 77 )) && x+=$crlf
+       x+=" <$mailpart>"
+       [[ $u = *U* ]] && set -U
+       d+=$x
+}
+
+function mymail {
+       local msg subj="Subject: $1" replto="Reply-To: $to"
+
+       [[ -n $email ]] && addtorpl replto "$vorname $nachname" "$email"
+       [[ -n $eltern_email ]] && addtorpl replto "$vorname_eltern $nachname_eltern" "$eltern_email"
+       [[ -z $to ]] && to="\"Teckids e.V. - Anmeldung\" <anmeldung@teckids.org>"
+
+       [[ -n $email ]] && from="\"$vorname $nachname\" <$email>"
+       [[ -z $from && -n $eltern_email ]] && from="\"$vorname_eltern $nachname_eltern\" <$eltern_email>"
+       [[ -z $from ]] && from="\"Teckids e.V. Anmeldeformular\" <www-data@terra.teckids.org>"
+
+       while IFS= read -r line; do
+               msg+=${line%"$cr"}$crlf
+       done
+       msg=$(Lb64encode "$msg")
+       msg=${msg//"$lf"/"$crlf"}
+
+       subj=$(print -nr -- "$subj" | php -r '
+               mb_internal_encoding("UTF-8");
+               echo mb_encode_mimeheader(file_get_contents("php://stdin"),
+                   "UTF-8", "Q", "\015\012");')
+
+       /usr/sbin/sendmail -t <<EOF
+MIME-Version: 1.0$cr
+Content-Type: text/plain; charset=utf-8$cr
+Content-Transfer-Encoding: base64$cr
+$subj$cr
+To: $to$cr
+From: $from$cr
+$replto$cr
+X-Mailer: Teckids-Webseite: Anmeldeformular$cr
+X-OTRS-DynamicField-TeckidsEvent: $lang
+Date: $(date +'%a, %d %b %Y %H:%M:%S %z')$crlf$crlf$msg$cr
+EOF
+}
+
+encoding_ok=unknown
+
+set -A fields -- "${fieldnames[@]}"
+typeset -l fields[*]
+for x in "${fields[@]}"; do
+       eval $x=
+done
+
+age=
+function dofield {
+       if [[ $1 != *=* ]]; then
+               #print -r -- "D: non-field '$1' found"
+               return
+       fi
+       fldk=${1%%=*}
+       fldv=${1#*=}
+       # unescape spaces
+       fldv=${fldv//'+'/ }
+       # unescape percent via backslash-unescaping ksh print builtin
+       fldv=${fldv//\\/\\\\}
+       fldv=${fldv//@(%)/\\x}
+       fldv=$(print -- "$fldv"x)
+       fldv=${fldv%x}
+       #print -r -- "D: field '$fldk' with value '$fldv' found"
+       if [[ $fldk = utf8 ]]; then
+               if [[ $encoding_ok != unknown ]]; then
+                       encoding_ok='multiple values found'
+               elif [[ $fldv = ✓ ]]; then
+                       encoding_ok=yes
+               else
+                       encoding_ok=no
+               fi
+               return
+       elif [[ $fldk = geburtsdatum ]]; then
+               if [[ $fldv = *([        ]) ]]; then
+                       : handled further below
+               elif ! dtchk dtJ "$fldv" dtv; then
+                        handled further below
+               elif ! dtchk dtJ "$fldv" dtv; then
+                       fldv+=" (invalid: $errstr)"
+               else
+                       fldv=$dtv
+                       if (( ${#alter_am[*]} < 3 )); then
+                               set -A alter_am -- $(date +'%d %m %Y')  # Alter heute
+                       fi
+                       set -A tmGeb -- $(mjd_explode "$dtJ" 0)
+                       set -A tmNow -- $(mjd_explode $(mjd_implode 0 0 0 \
+                           ${alter_am[0]} $((alter_am[1] - 1)) \
+                           $((alter_am[2] - 1900)) 0 0 0 0 UTC))
+                       (( age = tmNow[tm_year] - tmGeb[tm_year] - \
+                           ((tmNow[tm_yday] < tmGeb[tm_yday]) ? 1 : 0) ))
+               fi
+       fi
+       for x in "${fields[@]}"; do
+               [[ $fldk = "$x" ]] || continue
+               eval $x=\$fldv
+               break
+       done
+}
+#print Content-type: text/plain; print; print Debugging:
+inp=$(cat)
+while [[ $inp = *'&'* ]]; do
+       fld=${inp%%'&'*}
+       inp=${inp#*'&'}
+       dofield "$fld"
+done
+[[ -n $inp ]] && dofield "$inp"
+#print D: encoding_ok=$encoding_ok
+
+i=-1
+for x in "${pflichtfeld[@]}"; do
+       let ++i
+       (( x )) || continue
+       eval v=\$${fields[i]}
+       [[ $v = *([      ]) ]] && pflichtfelder_fehlen+=,\ ${fieldnames[i]//_/ }
+done
+
+text=
+logt=
+info=
+
+if [[ " ${fieldnames[*]} " = *" Username Pw1 Pw2 "* ]]; then
+       if [[ " ${fieldnames[*]} " != *" Account "* || $account = new ]]; then
+               nu=$username
+               typeset -l nu
+
+               if [[ -z $skipchecks ]]; then
+                       if [[ $nu = *[!a-z0-9]* ]]; then
+                               text+="<p class=\"cgierr\">Dein Benutzername darf nur Kleinbuchstaben und Ziffern enthalten!</p>"
+                               logt+=" username_invalid()"
+                       elif (( ${#nu} < 3 )); then
+                               text+="<p class=\"cgierr\">Dein Benutzername muss mindestens drei Zeichen lang sein!</p>"
+                               logt+=" username_short()"
+                       elif [[ $nu = [0-9]* ]]; then
+                               text+="<p class=\"cgierr\">Dein Benutzername muss mit einem Buchstaben anfangen!</p>"
+                               logt+=" username_invalid()"
+                       elif getent passwd $nu >/dev/null 2>&1; then
+                               text+="<p class=\"cgierr\">Der Benutzername ist leider schon vergeben!</p>"
+                               logt+=" username_taken()"
+                       else
+                               info+="<p class=\"cgierr\">Dein neuer Benutzername lautet <b>$nu</b> (nur Kleinbuchstaben und Ziffern).</p>"
+                               username=$nu
+                       fi
+               fi
+
+               if [[ -z $pw1 || -z $pw2 || $pw1 != "$pw2" ]]; then
+                       text+="<p class=\"cgierr\">Du musst zwei mal das gleiche Passwort eingeben!</p>"
+                       logt+=" pw_mismatch()"
+               fi
+
+               pwhash=$(print -rn -- "$pw1" | /usr/sbin/slappasswd -T/dev/stdin)
+       fi
+fi
+
+#XXX IBAN und BIC formatprüfen
+[[ -n $sepa$sepa_zahler$sepa_iban$sepa_bic ]] && if [[ -z $sepa || \
+    -z $sepa_zahler || -z $sepa_iban || -z $sepa_bic ]]; then
+       text+="<p class=\"cgierr\">Falls SEPA Lastschrifteinzug gewünscht wird müssen <em>alle</em> relevanten Felder ausgefüllt werden!</p>"
+       logt+=" sepa(teilweise)"
+fi
+
+typeset -l lc
+for lc in "$email" "$eltern_email"; do
+       if [[ $lc = *'@'@(gemskro.de|mail4kid[sz].*|kidzmail.@(de|eu)|schuelerpost.de|waldschule-quickborn.de) ]]; then
+               text+="<p class=\"cgierr\">Dein Anbieter $(xhtml_escape "${lc#*@}") versteckt wichtige Nachrichten vor dir und ist nicht für E-Mails im Internet geeignet! Bitte verwende eine andere Mailadresse.</p>"
+               logt+=" bogusmail($lc)"
+       fi
+done
+
+if [[ -n $pflichtfelder_fehlen ]]; then
+       text+="<p class=\"cgierr\">Bitte alle Pflichtfelder ausfüllen, es fehlt: ${pflichtfelder_fehlen#, }</p>"
+       logt+=" pflichtfelder_fehlen(${pflichtfelder_fehlen#, })"
+fi
+if [[ -n $text ]]; then
+       print Content-type: text/html
+       print
+       set -A repls -- -e "s\ 1<!-- REPL -->\ 1${text}\ 1" \
+           -e "s\ 1checked=\"checked\"\ 1\ 1g"
+       for x in "${fields[@]}"; do
+               eval y=\$$x
+               [[ $y = *"\ 1"* ]] && continue
+               if [[ $x = bemerkungen ]]; then
+                       y=$(xhtml_escape "$y")
+                       set -A repls+ -- -e \
+                           "s\ 1name=\"$x\"></textarea>\ 1name=\"$x\">${|sed_escape "$y";}</textarea>\ 1"
+                       continue
+               fi
+               [[ $y = *"$lf"* ]] && continue
+               y=$(xhtml_escape "$y")
+               set -A repls+ -- -e "s\ 1name=\"$x\" type=\"text\"\ 1& value=\"${|sed_escape "$y";}\"\ 1"
+               set -A repls+ -- -e "s\ 1name=\"$x\" value=\"${|sed_escape_re "$y";}\"\ 1& checked=\"checked\"\ 1"
+       done
+       sed "${repls[@]}" <${conf}_${suffix}.htm
+       logger -t ${conf}_${suffix}.cgi "<${eltern_email:-$email}>${logt};"
+       exit 0
+fi
+
+[[ $encoding_ok = yes ]] && encoding_ok=