Initial checkin of upstream version 4.09. 4.0.9
authorThomas Hochstein <thh@inter.net>
Mon, 16 Aug 2010 20:16:26 +0000 (22:16 +0200)
committerThomas Hochstein <thh@inter.net>
Mon, 16 Aug 2010 20:16:26 +0000 (22:16 +0200)
Signed-off-by: Thomas Hochstein <thh@inter.net>
47 files changed:
CHANGES [new file with mode: 0644]
COPYING [new file with mode: 0644]
README [new file with mode: 0644]
UVconfig.pm [new file with mode: 0644]
UVformats.pm [new file with mode: 0644]
UVmenu.pm [new file with mode: 0644]
UVmessage.pm [new file with mode: 0644]
UVreadmail.pm [new file with mode: 0644]
UVrules.pm [new file with mode: 0644]
UVsendmail.pm [new file with mode: 0644]
UVtemplate.pm [new file with mode: 0644]
bdsgtext.cfg [new file with mode: 0644]
mailpatterns.cfg [new file with mode: 0644]
messages.cfg [new file with mode: 0644]
templates/ack-mail [new file with mode: 0644]
templates/address-not-registered [new file with mode: 0644]
templates/ballot [new file with mode: 0644]
templates/ballot-personal [new file with mode: 0644]
templates/ballot-request [new file with mode: 0644]
templates/bdsg-error [new file with mode: 0644]
templates/bouncelist [new file with mode: 0644]
templates/cancelled [new file with mode: 0644]
templates/invalid-account [new file with mode: 0644]
templates/invalid-name [new file with mode: 0644]
templates/mailheader [new file with mode: 0644]
templates/multiple-votes [new file with mode: 0644]
templates/no-ballot [new file with mode: 0644]
templates/no-ballotid [new file with mode: 0644]
templates/no-votes [new file with mode: 0644]
templates/result-multi [new file with mode: 0644]
templates/result-proportional [new file with mode: 0644]
templates/result-single [new file with mode: 0644]
templates/rule-violated [new file with mode: 0644]
templates/voterlist [new file with mode: 0644]
templates/votes-multi [new file with mode: 0644]
templates/votes-single [new file with mode: 0644]
templates/wrong-ballotid [new file with mode: 0644]
tmp/ergebnis-1191790177 [new file with mode: 0644]
tmp/stimmen-1191790177 [new file with mode: 0644]
uidlcache [new file with mode: 0644]
usevote.cfg [new file with mode: 0644]
usevote.rul [new file with mode: 0644]
uvballot.pl [new file with mode: 0644]
uvbounce.pl [new file with mode: 0644]
uvcfv.pl [new file with mode: 0644]
uvcount.pl [new file with mode: 0644]
uvvote.pl [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
new file mode 100644 (file)
index 0000000..2d0d6d2
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,484 @@
+UseVoteGer Versionshistorie (aktuelle Version: 4.09, released 07.10.2007)
+=========================================================================
+
+TODO:
+- aussortieren von Bounces aus Stimmenliste
+- Unterstuetzung von Maildir
+- [Zugschlus] ich fänd es klasse, wenn man eine Kopie des Wahlscheines ins
+Abstimmungsverzeichnis legt und Usevote einem dann auf Abruf ein diff
+oder wdiff zwischen Sollwahlschein und wirklich eingreichtem
+Wahlschein macht. Auf diese Weise erwischt man auch kleine Änderungen
+am Datenschutzhinweis (da bin ich eben in eine Falle von th-h getappt).
+
+
+Version 4.09 (14.09.2007):
+- "votefile"-Option in usevote.cfg an passendere Stelle verschoben und
+  den Kommentar korrigiert (natuerlich gilt pop3=0 als Bedingung, nicht
+  etwa wie vorher angegeben smtp=0)
+- Fehler in Template für Ergebnisausgabe korrigiert (fehlendes Newline
+  nach umgebrochenen Abstimmungspunkten)
+- Fehler in Doku der Kommandozeilenoptionen von uvcount.pl behoben
+  (--voters statt --votes)
+- Date-Header in Englisch erzeugen (statt in eingestellter locale)
+  (verwendet nun Modul Email::Date)
+- Message-ID-Header selbst erzeugen
+
+Version 4.08 (06.10.2005):
+- beim Ignorieren von Regelverletzungen (im Menue mit "Stimmen OK" bestaetigt)
+  wird jetzt keine (dann ja unangebrachte) Fehlermail mehr verschickt.
+- neuer Buchstabe I bzw. i in usevote.rul, der auf NEIN und ENTHALTUNG matcht.
+  Damit lässt sich eine Stichwahl realisieren, bei der nur für eine von
+  zwei Möglichkeiten mit JA gestimmt werden darf und im anderen Feld entweder
+  NEIN oder ENTHALTUNG (bzw. garnichts, was Enthaltung enspricht)
+  eingetragen werden muss.
+- analog neuer Buchstabe H bzw. h für JA/ENTHALTUNG, der Vollstaendigkeit halber
+- uvvote.pl sortiert die Liste der Ergebnisdateien jetzt vorm Zusammenfuegen
+  zur neuen ergebnis.alle, so dass die Reihenfolge auf jeden Fall stimmt,
+  auch wenn das System die Dateien unsortiert liefert
+- wenn uvcount.pl in der ergebnis.alle auf eine falsche Anzahl von Abstimmungspunkten
+  bei einer Stimme trifft (z.B. versehentlich Leerzeichen oder Buchstabe 
+  am Ende einer Zeile zuviel, wenn manuell editiert wurde), bricht es ab und
+  weist auf die fehlerhafte Stimme hin. Vorher wurde das als weiterer
+  Abstimmungspunkt gezählt.
+- Doku ergänzt: "envelopefrom" bezieht sich nur auf SMTP, ansonsten muss
+  das in "mailcmd" konfiguriert werden
+- Bug bei Eingruppenmodus behoben: Es wurde immer das selbe ausgegeben,
+  unabhaengig vom Ergebnis (keine 60 Stimmen, keine 2/3 Mehrheit)
+
+Version 4.07 (26.09.2004):
+- wenn "nodup=1" gesetzt war, wurden auch keine Annullierungen aussortiert.
+  Ausserdem wurde ansonsten der Wahlleiter unnoetig gefragt, welche
+  Stimme aussortiert werden soll, auch wenn letztlich beide annulliert
+  waren (die Annullierung aber erst spaeter eingegangen war).
+  Um diese Fehler zu beheben, wurde ein zusaetzlicher Verarbeitungsschritt
+  in uvcount.pl eingefuehrt, der sich nur um Annullierungen kuemmert und
+  die gleich am Anfang verarbeitet.
+- es ist jetzt auch moeglich, nach einer Annullierung mit derselben
+  Mailadresse nochmal abzustimmen. Vorher wurde so eine Stimmabgabe
+  durch die vorher erfolgte Annullierung mit erfasst
+- Template result-proportional korrigiert. Es kam zu Darstellungsfehlern
+  bei umgebrochenen Gruppennamen/Wahlgegenstaenden (falsche Einrueckung,
+  falscher Umbruch)
+- Formatfunktion 'generate-date-header' fuer Templates eingefuehrt
+- Template 'mailheader' um Date-Header ergaenzt
+- chomp auf Message-ID nur noch machen, wenn eine Message-ID vorhanden ist
+  (gibt sonst Warnung wegen undef)
+- wenn die Option "mailcc" gesetzt ist, wurden die Hochkommata in der
+  domail-Datei (siehe Changelog von Version 4.06) um beide Adressen gesetzt.
+  Jetzt wird in einer Schleife jede Adresse einzeln gequotet.
+- RegEx fuer Realnamenserkennung um den Bindestrich erweitert, damit
+  Doppelnamen anerkannt werden
+
+Version 4.06 (18.06.2004):
+- Es werden nun "In-Reply-To:" und "References:" Header in den
+  generierten Mails erzeugt
+- beim Schreiben des domail-Scripts (Verschicken von Mails ohne SMTP)
+  wurde ein fehlerhafter Zeilenumbruch eingefuegt
+- Leerzeichen am Zeilenende in der usevote.cfg hatten dazu geführt,
+  dass Einstellungen nicht korrekt eingelesen wurden. Jetzt werden
+  beim Einlesen der Konfiguration solche Leerzeichen gelöscht (außer
+  wenn der Teil rechts vom Gleichzeichen durch Anführungsstriche umschlossen
+  ist)
+- Mailadresse und Waehlername werden jetzt korrekt zurueckgesetzt, so
+  dass bei fehlendem From-Header nicht noch die Daten der vorherigen Mail
+  in den Variablen stehen
+- Mailadresse wird nun in Hochkommata eingeschlossen, wenn sie in die
+  "domail"-Datei geschrieben wird (bei smtp=0 in usevote.cfg), damit
+  Shell-Metazeichen nicht beim Ausfuehren des MTA interpretiert und damit
+  die Mailadresse veraendert bzw. potentiell schaedlicher Code ausgefuehrt
+  wird
+- Es ist jetzt moeglich, bei der Warnung "Es wurden nicht alle Fehler behoben,
+  der Waehler wird eine Fehlermail erhalten" zurueck ins Menue zu gehen
+  und die Fehler doch noch zu beheben
+- in uvcfv.pl (Verschicken von personalisierten Wahlscheinen) einige
+  Bugs behoben [warum hatte das ueberhaupt so funktioniert? Schroedinger
+  laesst gruessen.] und den Hinweistext "Wahlschein wurde bereits einmal
+  zugeschickt" aus dem Perlcode in das Template ballot-personal verlagert
+- mittels "uvcfv.pl -t" laesst sich jetzt ein personalisierter
+  Dummy-Wahlschein ausgeben (wenn "personal=1" in usevote.cfg), um ihn
+  vorab der dana-Moderation zur Pruefung zukommen lassen zu koennen
+
+Version 4.05 (27.12.2003):
+- Aendern von Mailadressen oder Namen im Menue fuehrte zu "keine Scheinkennung"
+  Fehlern, auch wenn die personalisierten Wahlscheine nicht aktiviert waren
+  (personal=0 in usevote.cfg). 
+- Fehler beim Verarbeiten von Mailbox-Files behoben. In den letzten
+  Versionen funktionierte nur POP3.
+- In Wahlschein-Templates "kann fuer ungueltig erklaert werden" in
+  "wird fuer ungueltig erklaert werden" geaendert (bei falschem Realname)
+- kosmetische Aenderungen in Templates (Anpassung an neue Rechtschreibung,
+  Entfernung von Umlauten fuer einheitliches Schriftbild)
+
+Version 4.04 (22.11.2003):
+- uvcount.pl: Fehler beim Aussortieren von Duplikaten behoben, was
+  i.d.R. nur bei doppelten Mailadressen, nicht aber bei doppelten Namen
+  funktionierte (falsche Regular Expression und Probleme bei
+  unterschiedlicher Gross-/Kleinschreibung der Namen)
+
+Version 4.03 (19.10.2003):
+- UVsendmail.pm: Vernuenftige Fehlerbehandlung bei SMTP implementiert:
+  Bei fehlgeschlagenen Zustellversuchen wird jetzt die Datei ack.control
+  passend neu geschrieben, so dass mit "uvvote.pl clean" ein neuer
+  Versuch unternommen werden kann. Vorher wurde zwar eine Fehlermeldung
+  angezeigt, die Mail aber einfach geloescht...
+- es wird kein Fehler mehr angezeigt, wenn ack.control bereits existiert,
+  da der Code durchaus damit umgehen kann (es wird einfach an die
+  Datei angehaengt). Entsprechende Fehlermeldung aus messages.txt
+  entfernt
+- Schreibfehler in messages.cfg behoben (ggf. statt ggfls.)
+- me@privacy.net in mailpatterns.cfg aufgenommen
+- UIDLs werden jetzt in der Reihenfolge gespeichert, in der die
+  Mails auf dem POP3-Server lagen. Dadurch ist bei einem Abbruch
+  waehrend der Auswertung leichter kontrollierbar, welche Mails noch
+  einmal abgerufen werden sollen (einfach die letzten X UIDLs aus
+  der Datei uidlcache loeschen)
+- uvcfv.pl, uvbounce.pl und uvvote.pl besitzen jetzt einen Locking-
+  Mechanismus, der ein gleichzeitiges bzw. mehrfaches Starten dieser
+  Programme unterbindet. Andernfalls koennte es zu Inkonsistenzen
+  im Datenbestand kommen (gleichzeitiger Abruf derselben Mailbox,
+  Auswertung noch nicht fertig geschriebener Ergebnisdateien)
+- es koennen jetzt zusaetzliche Konfigurationsdateien in usevote.cfg
+  eingebunden werden, um z.B. die immer gleichen Einstellungen nur
+  einmal zentral abzulegen. Hierzu einfach eine Zeile
+  include dateiname
+  einfuegen. Die Position ist wichtig: Bei mehrfacher Definition
+  der selben Option gilt die letzte. Daher sollte eine globale
+  Konfigurationsdatei am Anfang eingebunden werden, um die
+  Einstellungen bei Bedarf mit wahlspezifischen ueberschreiben zu
+  koennen
+
+Version 4.02 (31.05.2003):
+- UVpath.pm wieder entfernt, da mittlerweile eine bessere Loesung
+  gefunden: Das Modul FindBin wird eingesetzt, um den Pfad der
+  ausgefuehrten .pl Datei zu ermitteln. Wenn die .pm Dateien im selben
+  Verzeichnis liegen, werden sie dort gefunden. Ausserdem wurde der
+  Hinweis auf die Umgebungsvariable PERL5LIB in die README Datei
+  aufgenommen, die ansonsten auch auf den Pfad zu den Usevote-Perlmodulen
+  gesetzt werden kann.
+
+Version 4.01 (29.05.2003):
+- Wahlschein-, Result- und Bestaetigungsmail-Templates angepasst, so dass
+  bei langem "votename" ein Umbruch im Wahlschein erfolgt und auch bei einer
+  zweistelligen Anzahl von Wahlgegenstaenden eine buendige Ausgabe erfolgt
+- uvballot.pl und Template "result-multi" angepasst, so dass bei
+  Mehrgruppenabstimmungen die Anzahl der Enthaltungen nicht ausgegeben
+  wird (laesst sich nicht als Gesamtzahl ermitteln, koennte man hoechstens
+  fuer jede Gruppe einzeln angeben)
+- Es brauchen jetzt nur noch die Konfigurationsdateien sowie die
+  UVpath.pm in einem Abstimmungsverzeichnis zu liegen, die .pl und .pm
+  Dateien koennen zentral fuer mehrere Abstimmungen abgelegt werden.
+- Formatierungsfunktion "replace" in UVformats.pm implementiert, mit
+  deren Hilfe die Ersetzung von Zeichen oder Zeichenketten in Templates
+  moeglich ist. Praktische Anwendung ist z.B. die Verfremdung von
+  Mailadressen im Result als trivialer Spamschutz. Wie die Templates
+  dafuer geaendert werden muss, ist in der README Datei im Abschnitt 10
+  beschrieben
+- Bei den Standard-Funktionen append und justify (inkl. justify-before
+  und justify-behind) wird der uebergebene Key jetzt rekursiv ueber die
+  Formatdefinitionen im Template aufgeloest. Das ermoeglicht die
+  Vorbehandlung eines Wertes, z.B.:
+    mail := value mail | replace '@' '-at-'
+    line := value name | justify-before mail 70
+  Hier wurde vorher die unveraenderte Mailadresse benutzt, jetzt wird
+  die obere Definition beachtet und zunaechst die Ersetzung durchgefuehrt.
+- Formatierungsfunktion "sprintf" in UVformats.pm implementiert, um
+  z.B. Verhaeltnisse in Results formatiert ausgeben zu koennen
+- Auswertung nach Verhaeltnis Ja- zu Nein-Stimmen implementiert
+  (in usevote.cfg proportional=1 setzen und prop_formula passend waehlen).
+  Damit ist z.B. fuer jeden Abstimmungsgegenstand das Verhaeltnis oder
+  auch die Differenz zwischen Ja- und Nein-Stimmen ermittelbar. Letzteres
+  wird fuer Moderationsnachwahlen benoetigt. Kombiniert werden kann dies
+  mit einer weiteren Bedingung, z.B. mindestens soviele Ja- wie Nein-Stimmen.
+
+Version 4.0 (22.03.2003):
+- UVformats.pm dokumentiert
+- Defaultwert fuer "formats" korrigiert (UVconfig.pm)
+- Defaultwert fuer "bdsgfile" fehlte (UVconfig.pm)
+- kosmetische Aenderung (fehlende Leerzeichen) an Template result-multi
+- Windows-Pager-Empfehlung in README und usevote.cfg geaendert (vorher
+  wurde "more" empfohlen, da mitgeliefert, aber more ist so buggy, dass
+  jetzt die Installation von "less" nahegelegt wird
+- Fehler in UVsendmail.pm behoben: Wenn beim "uvvote.pl clean" Aufruf keine
+  Mails zu verschicken waren, wurde das Programm in UVsendmail::send()
+  einfach mit "exit 0" beendet (korrigiert in "return 0"). Ausserdem
+  wurde die Fehlermeldung wegen eines Schreibfehlers im Konstantennamen
+  nicht angezeigt.
+
+Version 4.0beta15:
+- Fehlermeldung bei nicht vorhandener messages.cfg korrigiert
+  (Dateiname wurde wegen falschen Configschluessels nicht angezeigt)
+- Bei manuell eingegebener Scheinkennung wurde irrtuemlicherweise eine
+  Fehlermeldung angezeigt, auch wenn die Kennung zur Mailadresse passte
+- Bei neu eingegebener Mailadresse wurde die Zugehoerigkeit der
+  Scheinkennung nicht neu geprueft
+- bei nicht erkannten Abstimmungspunkten im Wahlschein wurde eine
+  Warnmeldung angezeigt, die auf Nicht-Wertung hinwies. In Wirklichkeit
+  wurde die Stimme aber normal bestaetigt und lediglich alle nicht erkannten
+  Punkte als "Enthaltung" gewertet. Die Warnmeldung erscheint jetzt nicht
+  mehr, um keine Verwirrung zu stiften.
+- Fehler in UVmessage.pm behoben: Der Wert 0 wurde durch den leeren String
+  ersetzt. Jetzt wird defined() eingesetzt statt auf true/false zu pruefen.
+- Fehler in UVsendmail.pm behoben: Wenn kein SMTP aktiviert war, wurde
+  die "domail" Datei mit den MTA-Aufrufen zwar geschrieben, aber nicht
+  ausgefuehrt. Dadurch schlug auch das Loeschen der Temp-Dateien fehl,
+  was beim naechsten Aufruf Fehlermeldungen verursachte.
+- Templates eingebaut
+- acktext.txt durch Templates ersetzt. BDSG-Text ist jetzt in der
+  Datei bdsgtext.cfg
+- "cfvfile" Option entfernt (nicht mehr noetig, durch Templates abgeloest)
+- Bedingungen fuer Wahlerfolg ueber usevote.cfg konfigurierbar gemacht.
+  Standardwerte:
+  condition1 = $yes>=2*$no
+  condition2 = $yes>=60
+- usevote.cfg bzgl. der Ueberschriften "jedes Mal anpassen" / "nur einmal
+  anpassen" ein wenig umsortiert
+- wenn kein "smtphelo" definiert wurde, wird jetzt der eigene
+  Hostname genommen
+- README an aktuelle Aenderungen angepasst (Dateilisten, Beschreibungen
+  der Menues) und vervollstaendigt
+
+Version 4.0beta14:
+- Fehler in UVmenu.pm behoben, der beim Auswaehlen von
+  "Stimmen vom Waehler annulliert" im Menue auftrat
+- Erkennung von doppelten, sich widersprechenden Stimmabgaben in einer
+  Mail funktioniert jetzt
+- beim Verschicken per SMTP gibt es die neue Option envelopefrom, die
+  die Absenderadresse im Envelope (Return-Path) enthaelt, an die auch
+  Bounces zurueckgehen
+- Neu: Erkennung von fehlenden Abstimmungspunkten im Wahlschein, Behandlung
+  wie bei unleserlichen Stimmabgaben mit entsprechendem Hinweis im Menue
+- Fehler in uvvote.pl behoben, der bei unleserlichen Stimmabgaben auftrat
+- statt encode_mimewords wird jetzt encode_mimeword verwendet und das
+  "Drumherum" komplett selbst gemacht. Man schaue in den Code von
+  MIME::Words::encode_mimewords(), dann weiss man, warum ;-)
+- Weitere Texte in messages.txt ausgelagert (uvvote.pl, uvcount.pl)
+- config test (-t Option) gibt jetzt auch Auskunft ueber die Konfiguration
+  (falls Option nicht in usevote.cfg gesetzt, wird der Standardwert
+  ausgegeben)
+
+Version 4.0beta13:
+- Fehlerbehandlung bei SMTP eingefuehrt, so dass keine Mails verloren gehen
+- es laesst sich jetzt ein anderer Port fuer SMTP/POP3 angeben
+- uvbounce.pl benutzt jetzt auch POP3, falls dieses in usevote.cfg aktiviert
+  wurde. Mit der Option -f ist aber unabhaengig davon das Einlesen der
+  Bounces aus einer Datei in jedem Fall moeglich
+- auftretende Fehler beim Ausfuehren von uvvote.pl werden jetzt in eine
+  Datei geschrieben und beim Verlassen wird darauf hingewiesen
+- Wenn das interaktive Menue ausgeblendet und dazu der Bildschirm geloescht
+  wird, informiert jetzt eine Meldung darueber, dass Mails verarbeitet werden
+- Menues so umgestellt, dass [a] immer fuer "alles OK" steht, egal ob
+  Mailadresse, Name, Stimmen oder die BDSG-Klausel strittig sind
+- Ausgaben/Texte von UVreadmail.pm und UVsendmail.pm nach messages.txt
+  ausgelagert
+- Verzeichnisnamen "fertig" und "tmp" jetzt konfigurierbar
+- Zeilen "Waehleradresse: " und "Wahlscheinkennung: " im Wahlschein
+  konfigurierbar gemacht
+- Pager konfigurierbar gemacht (vorher immer "more") und standardmaessig
+  auf "less" gesetzt, weil "more" mit der Umleitung von STDERR Probleme hat
+- kleinere Bugs behoben
+
+Version 4.0beta12:
+- Fehler in Menue behoben
+- im Menue kann man jetzt explizit Stimmen ungueltig werten, indem man
+  den Namen, die Adresse oder die Stimmen ungueltig macht. Es wird eine
+  passende Fehlermail generiert.
+- genauso kann man jetzt explizit annullieren (sinnvoll, falls der Waehler
+  z.B. "annullierung" falsch buchstabiert hat *g*), wobei automatisch
+  diverse andere Probleme als irrelevant erkannt werden (z.B. braucht man
+  in dem Fall keinen Datenschutzhinweis zu akzeptieren und nicht unbedingt
+  einen Namen anzugeben, falls die Adresse stimmt)
+- MIME-Kodierung fuer Subject- und From-Header in UVsendmail.pm eingefuehrt
+- In Bestaetigungsmails gibt es jetzt kein gesondertes Feld mehr zum
+  Korrigieren des Namens, sondern es kann einfach die ohnehin vorhandene
+  Zeile "Wahlername: Vorname Nachname" editiert werden.
+- "nametext2" in usevote.cfg ist jetzt "Waehlername:" und wird auch
+  statt des fest kodierten Strings an den entsprechenden Codestellen
+  verwendet
+- uvcount.pl: Bugs bei Annullierungen und fehlendem Namen behoben
+- uvbounce an geaendertes UVreadmail.pm angepasst (funktioniert bei
+  Aktivierung des POP3-Zugriffs nicht mehr)
+- saemtliche Ausgaben/Texte in UVmenu.pm nach messages.txt ausgelagert
+
+Version 4.0beta11:
+- kompletter Rewrite der Ueberpruefungsfunktionen in uvvote.pl und UVmenu.pl.
+  Es werden jetzt alle Fehler an ein Array angehaengt und in einem Rutsch
+  von der Menue-Funktion verarbeitet. Fuer Darstellung der Votemail wird
+  "more" benutzt.
+
+Version 4.0beta10:
+- Zeilenumbrueche richten sich jetzt nach der "rightmargin"-Einstellung
+  aus usevote.cfg (vorher waren die Zeilenlaengen teilweise noch hartkodiert)
+- Fehler beim Erstellen des domail-Scripts behoben
+- kosmetische Code-Aenderungen
+
+Version 4.0beta9:
+- Auch bei zurueckgeschickten (korrigierten) Wahlbestaetigungen wird
+  jetzt der Name automatisch im Body erkannt (Zeile "Waehlername:").
+- Regular Expressions zur Stimmerkennung geaendert: Manche komische
+  Mailprogramme benutzen zum Kodieren von Leerzeichen =A0, was aber
+  nach der Dekodierung nicht als \s erkannt wird. \W ist nicht optimal,
+  aber funktioniert.
+- uvbounce.pl: Bounces von Antworten auf Wahlscheinanforderungen werden
+  jetzt erkannt und mit einem gesonderten Hinweis gekennzeichnet
+  ("Wahlschein nicht zustellbar")
+- in den Config Files koennen die Kommentarzeichen escaped werden: \#
+
+Version 4.0beta8:
+- beim Einlesen aus usevote.cfg wird ein eventuelles \r geloescht
+- es werden nicht mehr jedes Mal saemtliche Mails abgerufen (bei POP3),
+  sondern es wird mit dem UIDL Kommando geprueft, ob schon ein vorheriger
+  Abruf stattfand. Ausserdem ist es jetzt moeglich, die Mails vom Server
+  zu loeschen.
+- uvcfv.pl kann jetzt auch richtig mit POP3 umgehen
+- Platzhalter im Wahlschein bei personalisierten Wahlscheinen geaendert
+- Aktuelle Werte werden teilweise jetzt im Menue angezeigt (wenn man
+  Stimmen, Name oder Mailadresse neu gesetzt hat)
+- Dokumentation verbessert
+
+Version 4.0beta7:
+- kosmetische Code-Aenderungen (Vereinfachungen, Verschoenerungen, ...)
+- Inhalt der Datei bdsgtext.txt als Abschnitt [BDSG Hinweis] in
+  acktext.txt uebernommen (es gibt keinen Grund dafuer, dass dieser
+  Text eine eigene Datei bekommen sollte...)
+- Testweise einige Programm-Meldungen in externe Datei (meldungen.cfg)
+  ausgelagert, um eine leichtere Anpassung zu ermoeglichen (z.B.
+  Uebersetzung in andere Sprachen). Nach und nach werden saemtliche
+  Meldungen in diese Konfigurationsdatei wandern.
+- acktext.txt in acktext.cfg umbenannt: Alle Konfigurationsdateien
+  haben damit die Endung .cfg
+- POP3-Abruf und Verschicken per SMTP eingebaut
+- Shellbefehl-Aufrufe (chmod- und mkdir) durch Perl-Pendants ersetzt,
+  um Plattformunabhaengigkeit zu bieten
+- In uvcfv.pl Doppelung im Mailsubject geloescht
+- In uvcount.pl stimmte die Zuordnung von Abstimmungsgegenstand zur
+  einzelnen Stimmabgabe nicht (umgekehrte Reihenfolge)
+- in UVmenu.pm entstand bei Mehrgruppenabstimmungen der Kommentar
+  "Wahlleiter setzte Stimmen, Stimmen, Stimmen" (jetzt nur noch
+  einmal gesetzt statt fuer jede Gruppe)
+- Falls keine Scheinkennung und keine BDSG-Zustimmung: Bislang wurden
+  dann zwei Mails generiert (keine Abfrage auf bereits aufgetretenen
+  Fehler), nun behoben
+
+Version 4.0beta6:
+- RegExp fuer Namensangabe im Body verbessert (wenn kein Name angegeben
+  wurde und auch im Header keiner zu finden war, wurde der nachfolgende
+  Hinweissatz "Wenn Du keinen Namen angibst..." als Realname erkannt
+- RegExp fuer Namenserkennung konfigurierbar gemacht (usevote.cfg)
+  und um Accents erweitert
+- RegExp fuer Erkennung verdaechtiger Adressen trifft jetzt nur noch
+  zu, wenn der String direkt am Anfang der Adresse steht. Gegenteiliges
+  Verhalten kann durch Wildcards herbeigefuehrt werden
+- Statt manuellen Trennens von Header und Body wird in UVreadmail.pm
+  jetzt das Modul MIME::Parser eingesetzt
+- kleinere Bugs behoben (z.B. einfache vs. doppelte Anfuehrungsstriche)
+- Fehler bei UVmenu::menu-Aufruf im Falle von "keine Stimmen" behoben
+  (eine Variable fehlte in der Uebergabeliste)
+- uvcount.pl um Eingruppen-Format ergaenzt
+- uvballot.pl um Option -t ergaenzt, um eine Vorlage fuer cfv.txt bei
+  Verwendung von personalisierten Wahlscheinen zu erzeugen
+- uvbounce.pl zur Generierung von ungueltigen Adressen aus einer
+  Mailbox mit Bounces implementiert
+- Bei Annullierungen wird jetzt nicht mehr die BDSG-Klausel geprueft
+  (es erfolgt ja ohnehin eine Loeschung der Stimmabgabe)
+- Statt Mail::Field wird nun eine eigene RegExp verwendet (Danke an
+  Marc Brockschmidt fuer die Idee), Mail::Field hat einige unschoene Bugs.
+- Bei nicht erkannten Stimmabgaben bei Abstimmungen mit nur einem
+  Abstimmungsgegenstand wird nun auch die Ungueltigwertung angeboten
+  (als Alternative zu "Enthaltung").
+
+Version 4.0beta5:
+- die Mailboxdatei wird nun vor der Verarbeitung verschoben, so dass waehrend
+  des uvvote-Laufs keine neuen Mails angehaengt werden koennen
+- es wird jetzt fuer jeden Durchlauf eine gesonderte Ergebnisdatei angelegt
+- neu eingefuehrter Parameter "clean", der Bestaetigungen verschickt,
+  Ergebnisdatei und die Mailbox in das Verzeichnis fertig/ verschiebt,
+  temporaere Dateien loescht und aus allen Einzelergebnissen eine neue
+  Gesamtergebnisdatei (normalerweise "ergebnis.alle") erstellt
+- dadurch sind jetzt zwei Durchgaenge erforderlich: Erster Aufruf ohne
+  die Option "clean" (Erzeugt die Ergebnisse und Mailvorlagen), dann kann
+  eine Kontrolle erfolgen, anschliessend ein weiterer Aufruf mit der Option
+  "clean" zum Aufraeumen und Verschicken der Bestaetigungen
+- neues Modul UVsendmail.pm, in das die Funktion zum Erstellen von
+  Mails ausgelagert wurde
+- neues Modul UVmenu.pm mit der Menuefunktionalitaet zum interaktiven
+  Eingriff in die Stimmwertung
+- Bugs bei Stimmaufzeichnung behoben: Fehlerhafte Stimmen wurden teilweise
+  nicht in der Ergebnisdatei vermerkt. Ausserdem wurde nicht konsequent
+  auf die Option "voteack" geprueft
+- Reply-To kann jetzt beachtet werden (muss in usevote.cfg eingeschaltet
+  werden) und ueberschreibt das From. Mit Vorsicht zu geniessen, da so
+  jeder Waehler fuer andere Stimmen abgeben und die Bestaetigungen zu
+  sich umlenken kann!
+- Umgang mit personalisierten Wahlscheinen (Abschnitt 6a der Wahlregeln
+  fuer de.*), Generierung und Pruefung von Scheinkennungen. Siehe Optionen
+  "personal" und "idfile" in usevote.cfg sowie das Programm uvcfv.pl zum
+  Verschicken der persoenlichen Wahlscheine
+- Das Flag beim Aufruf der Menuefunktion ist jetzt ein Hash, in dem
+  diverse Werte ueber- und zurueckgegeben werden. Dadurch auch feine
+  Steuerung der aktiven Menuepunkte moeglich
+- Ausgabe der Fehlermeldung in UVmenu.pm verlagert (wird in besagtem
+  Hash uebergeben)
+- Kommentarfeld fuer Ergebisdatei ("Wahlleiter setzte xyz") wird erst
+  zum Schluss erzeugt, vorher werden nur die vom Wahlleiter manuell
+  veraenderten Felder in einem Array mitgefuehrt
+- uvcfv.pl zum Generieren der persoenlichen Wahlscheine und Verschicken
+  des CfVs implementiert
+- Bisherige Funktionalitaet von uvack, uvcount, uvdup in uvcount.pl
+  implementiert
+- uvballot.pl zum Erstellen eines Musterwahlscheins
+- uvcfv.pl: Es wird nun auch die Vollstaendigkeit von $config{bdsgtext}
+  im Wahlschein geprueft und bei Auswahl von "Ende+Speichern" wird
+  noch einmal zur Sicherheit gefragt, ob die Stimme wirklich
+  gespeichert und verarbeitet werden soll
+- Kompatibilitaetsprobleme mit Perl 5.6.1 bei Variablenzuweisungen behoben
+
+Version 4.0beta4:
+- bei Regelverletzung wird jetzt ein interaktives Menue aufgerufen,
+  so dass der Wahlleiter entscheiden kann, was er machen will
+- Stimmerkennung legt unbekannte Vote-Strings (nicht Ja, Nein,
+  Enthaltung oder Annullierung) jetzt dem Wahlleiter vor und
+  laesst ihn entscheiden (Default ist Enthaltung). Fehlermail ist
+  in so einem Fall bislang nicht vorgesehen, laesst sich aber noch
+  einbauen, falls erforderlich. Wenn der Waehler die Bestaetigung
+  ueberprueft, reicht es auch, wenn dort "Enthaltung" auftaucht...
+- Pruefung auf vollstaendige Bestaetigung der Datenschutz-Klausel
+  ist implementiert und ueber usevote.cfg und bdsgtext.txt konfigurierbar
+- Regelpruefung kompakter und dreimal schneller gemacht (Idee von
+  Cornell Binder), dafuer Code schlechter lesbar... aber dokumentiert ;-)
+- Alle Regel-Subs in UVrules.pm ausgelagert
+
+Version 4.0beta3:
+- Einlesen und Testen der Konfiguration sowie Ausgabe der Regeln
+  im Klartext sind jetzt im externen Modul UsevoteConfig.pm untergebracht
+- Das Einlesen der Mail und MIME-Bearbeitung erfolgt wurde in das
+  Modul UsevoteReadmail.pm ausgelagert
+- verdaechtige Mailadressen werden nun in einer gesonderten Datei
+  konfiguriert (Default: mailpatterns.cfg)
+- Fehler bei Parsing von %body% und %headbody% in acktext.txt behoben
+
+Version 4.0beta2:
+- Auslagerung des Abschnittes [Realname Info] nach acktext.txt
+  (vorher hardcoded)
+- Einfuehrung des Platzhalters %version% fuer acktext.txt
+- Einfuehrung der Option voteack (Einzelbestaetigung kann deaktiviert werden)
+- saemtliche Konfigurationsoptionen sind nun mit Defaultwerten belegt,
+  aber wahlspezifische Optionen (Gruppennamen etc.) muessen natuerlich
+  auf jeden Fall gesetzt werden
+- Aenderung der Stimmerkennungsmethode. Um problemlos mit eigenwilligen
+  Zeilenumbruechen diverser schrottiger Software umgehen zu koennen,
+  wird ein Identifier an den Zeilenanfang gesetzt und direkt dahinter
+  die Stimme. Der Gruppenname kann dann ruhig umgebrochen sein.
+
+Version 4.0beta1:
+- kompletter Rewrite in Perl. Noch ziemlich unvollstaendig (nur uvvote.pl)
+
+bis Version 3.1beta7 (Wolfgang Behrens):
+- Funktionialitaet fuer persoenliche Wahlschein eingebaut
+- Reply-To Auswertung u.a. auf GVV-Beduerfnisse angepasst
+
+Version Usevote 3.0a
+- Uebersetzung ins Deutsche und Anpassung an Wahlregeln in de.*
+  (Frederik Ramm)
+
+Version Usevote 3.0
+- Urversion von Ron Dippold (Englisch), nicht an hiesige Wahlregeln angepasst
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..e77696a
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                          675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..e2a7e66
--- /dev/null
+++ b/README
@@ -0,0 +1,1284 @@
+UseVoteGer 4.09  (c) 2001-2007 Marc Langer
+
+UseVoteGer is a voting software for usenet votes.
+
+This script package is free software; you can redistribute it and/or
+modify it under the terms of the GNU Public License as published by the
+Free Software Foundation.
+
+Many thanks to:
+- Ron Dippold (Usevote 3.0, 1993/94)
+- Frederik Ramm (German translation, 1994)
+- Wolfgang Behrens (UseVoteGer 3.1, based on Frederik's translation, 1998/99)
+- Cornell Binder for some good advice and code fragments
+  (e.g. UVtemplate.pm, UVformats.pm)
+
+This is a complete rewrite of UseVoteGer 3.1 in Perl (former versions were
+written in C). Not all functions of Usevote/UseVoteGer 3.x are implemented!
+-------------------------------------------------------------------------------
+
+UseVoteGer 4.09 - Usenet-Abstimmungssoftware
+===========================================
+
+von Marc Langer <uv@marclanger.de>
+
+aktuelle Versionen: http://www.usevote.de
+
+
+Inhaltsverzeichnis
+==================
+
+1.  Kurzbeschreibung
+2.  Usevote fuer Ungeduldige
+3.  Kompatibilitaet zu frueheren Versionen
+4.  Liste der Dateien
+5.  Anfangs-Konfiguration
+6.  Konfiguration fuer einzelne Abstimmungen
+7.  Wahlschein generieren
+8.  Stimmen verarbeiten
+9.  Unzustellbare Bestaetigungen
+10. 2. CfV / Result
+11. Datenschutz
+12. Personalisierte Wahlscheine
+13. Die einzelnen Programme
+14. Die Konfigurationsdateien
+15. Templates
+
+
+1. Kurzbeschreibung
+===================
+
+Diese Software vereinfacht die Durchfuehrung von Usenet-Abstimmungen.
+Usevote in der Urversion wurde von Ron Dippold fuer die Reorganisation
+der Gruppe comp.sys.ibm.pc.games geschrieben und spaeter fuer die
+allgemeine Verwendung verbessert.
+
+Die deutsche Uebersetzung von Frederik Ramm diente jahrelang als
+Grundlage fuer CfV-Auswertungen im deutschsprachigen Raum.
+Wolfgang Behrens von den German Volunteer Votetakers (GVV) passte
+es spaeter an neue Begebenheiten (z.B. personalisierte Wahlscheine)
+an. Jedoch liefen die UseVoteGer 3.1beta-Versionen aufgrund eines
+unbekannten Fehlers nicht auf jedem System (Segmentation Fault).
+
+Um groessere Flexibilitaet und Plattformunabhaengigkeit zu bieten,
+habe ich mich entschlossen, UseVoteGer in Perl neu zu implementieren.
+Getestet wurde diese Version mit Perl 5.x unter Linux sowie
+ActiveState Perl unter Windows 98.
+
+
+2. Usevote fuer Ungeduldige
+===========================
+
+I. Einmalig bei der Usevote-Erstinstallation
+   (1) usevote.cfg nach Bedarf anpassen (selbsterklaerend)
+       ACHTUNG: Unter Windows gibt es das Programm "less" nicht, da "more"
+       aber einigermassen broken ist, sollte man sich less fuer Windows
+       herunterladen: http://www.greenwoodsoftware.com/less/less381d.zip
+       Notfalls laesst sich auch in usevote.cfg die Einstellung "pager"
+       auf "more" setzen, aber viel Freude hat man damit nicht...
+   (2) Evtl. die Templates im gleichnamigen Unterverzeichnis fuer
+       eigenen Geschmack anpassen. Die Templates enthalten saemtliche
+       Texte fuer Bestaetigungs-/Fehlermails sowie die Ausgabeformate
+       und Texte fuer die CfVs und das Result.
+   (3) Perlmodule MIME::Parser, MIME::QuotedPrint, Digest::MD5,
+       Date::Parse und Email::Date installieren, falls noch nicht vorhanden.
+       Bei Benutzung von POP3 oder SMTP (siehe usevote.cfg) muss
+       Libnet installiert sein (Net::POP3 und Net::SMTP).
+       (Fuer Perl-Einsteiger: Unter Unix geht die Modul-Installation
+       sehr einfach mit "perl -MCPAN -e shell", ActivePerl fuer
+       Windows hat einen eigenen Paketmanager)
+
+       Achtung: Bei meinem ActivePerl 5.6.1 war eine alte Libnet-Version
+       enthalten, die noch keine SMTP-Authentication unterstuetzte.
+       Bei Benutzung dieses Features muss evtl. erst upgedated werden.
+
+II. Fuer jedes Voting
+   (1) usevote.cfg auf das durchzufuehrende Voting anpassen
+       (selbsterklaerend)
+   (2) bei Sonderregeln (Abhaengigkeiten der einzelnen Abstimmungspunkte)
+       in usevote.rul Regeln definieren (selbsterklaerend)
+   (3) CfV erstellen und Wahlschein mit uvballot.pl generieren
+   (4) Fertigen CfV einreichen und auf Veroeffentlichung warten ;-)
+   (5) Bis zum 2. CfV regelmaessig (an den ersten Tagen mehrmals taeglich,
+       spaeter einmal taeglich) dies tun:
+       - uvvote.pl durchlaufen lassen
+       - im Verzeichnis tmp/ die Datei ergebnis.* (Ergebnis der Auswertung)
+         und evtl. ack.* (Bestaetigungsmails an die Waehler) kontrollieren
+       - Falls es Probleme gab: Bitte den Abschnit "Stimmen verarbeiten"
+         weiter unten in dieser Datei genau durchlesen. Dort steht
+         beschrieben, wie Fehler korrigiert werden koennen
+       - "uvvote.pl clean" aufrufen, um die Bestaetigungen zu verschicken
+         und das Ergebnis zu speichern/archivieren
+       - Falls Wahlbestaetigungen als unzustellbar zurueckkommen, solltest
+         Du diese zusammen in einer Datei speichern oder in ein spezielles
+         POP3-Postfach leiten
+   (6) Zwischenbestaetigung fuer den 2. CfV mit "uvcount.pl -l" erzeugen und
+       veroeffentlichen. Falls Wahlbestaetigungen als unzustellbar
+       zurueckkamen, kannst Du mit "uvbounce.pl dateiname" automatisch eine
+       Liste der ungueltigen Adressen erzeugen ("dateiname" ist die Datei,
+       in die Du zuvor die Bounces gespeichert hattest). Siehe auch Abschnitt
+       "Unzustellbare Bestaetigungen" weiter unten in dieser Datei, dort
+       ist auch erklaert, wie die Bounces aus einem POP3-Postfach gelesen
+       werden koennen.
+   (7) Bis zum Result wieder wie (5)
+   (8) Zum vorgegebenen Zeitpunkt die Wahl abschliessen,
+       mit "uvcount.pl -r -v" Endergebnis erzeugen und veroeffentlichen
+       (zuvor wiederum Liste der ungueltigen Mailadressen erzeugen, siehe
+       Punkt 6)
+
+Weitere Details finden sich im Folgenden.
+Jeder Nutzer dieser Software sollte sich diesen Text wenigstens einmal
+komplett durchlesen.
+
+Abschnitt 6 beschreibt, wie mehrere Abstimmungen gleichzeitig durchgefuehrt
+werden koennen, ohne saemtliche Programmdateien zu kopieren.
+
+
+3. Kompatibilitaet zu frueheren Versionen
+=========================================
+
+- englische Usevote-Versionen: keine Kompatibilitaet
+
+- deutsche Usevote-Versionen (Usevote 3.0a, UseVoteGer 3.1):
+  * Wahlscheine haben neue Markierungen zum sicheren Erkennen
+    der Stimmen. Bisherige Wahlscheine sind mit dieser Version
+    nicht mehr einsetzbar!
+
+  * acktext.txt gibt es nicht mehr, statt dessen sind im Unterverzeichnis
+    "templates" Vorlagen zu finden, die diese Texte enthalten.
+
+  * usevote.cfg ist komplett inkompatibel
+
+  * usevote.rul: voll kompatibel, ohne Einschraenkungen
+
+  * Ergebnisdateien: Inhalt kompatibel, aber Namensgebung geaendert
+    (obwohl irrelevant, da Umstieg auf diese Version waehrend
+    einer laufenden Wahl nicht moeglich)
+
+  * scheinkennungen: Dateiformat gaendert (aus selbigen Gruenden
+    nicht weiter wichtig)
+
+  * uvshell: integriert in uvvote.pl, ueber usevote.cfg steuerbar
+    
+  * uvvote.pl: Aufrufparameter geaendert / Zusammenfuehrung mit uvshell
+  
+  * uvcfv.pl: Alte Funktionalitaet ist nicht mehr implementiert,
+    statt dessen dient das Tool jetzt zum Generieren der persoenlichen
+    Wahlscheine und Verschicken des CfV an die Waehler
+  * uvcount.pl: Beinhaltet auch uvack und uvdup. Drei Einzelprogramme
+    schienen mir ineffizient.
+
+  * uvbounce.pl: Ausgabeformat etwas anders
+
+
+4. Liste der Dateien
+====================
+
+* Ausgelieferte Dateien:
+
+CHANGES           Versionsaenderungen
+COPYING           GNU General Public License
+README            Diese Datei
+UVconfig.pm       Routinen zum Einlesen und Pruefen der Konfiguration
+UVformats.pm      Routinen zum Formatieren von Texten in Templates
+UVmenu.pm         Routinen zur Interaktion mit dem Wahlleiter
+UVmessage.pm      Routinen zum Parsen von Texten und Ersetzen von Platzhaltern
+UVreadmail.pm     Routinen zum Einlesen und MIME-Decodieren der Mails
+UVrules.pm        Routinen zur Regelverarbeitung (usevote.rul)
+UVsendmail.pm     Routinen zum Erzeugen von Mails
+UVtemplate.pm     Routinen zur Verarbeitung von Vorlagen (Templates)
+bdsgtext.cfg      Spezieller Text fuer den Wahlschein (Hinweis auf
+                  Datenschutzgesetz), muss ausserhalb Deutschlands ggfls.
+                  angepasst oder kann ignoriert werden (bdsg=0 in usevote.cfg)
+mailpatterns.cfg  Wildcards fuer verdaechtige Mailadressen
+messages.cfg      Programm-Meldungen (Ressourcen-Datei)
+scheinkennungen   Speicherung der Scheinkennungen bei personalisierten
+                  Wahlscheinen. Muss zu Beginn der Abstimmung leer sein.
+usevote.cfg       Konfigurationsdatei
+usevote.rul       Abstimmungsregeln
+uvballot.pl       Wahlschein aus Abstimmungsdaten erstellen
+uvbounce.pl       Listen von unzustellbaren Bestaetigungen erzeugen
+uvcfv.pl          Erstellen und Verschicken von personalisierten Wahlscheinen
+uvcount.pl        Stimmenzaehlung, Erstellen von Waehlerlisten und
+                  Aussortieren von doppelten Stimmabgaben
+uvvote.pl         Stimmen verarbeiten (fuer die taegliche Arbeit)
+
+templates/                Verzeichnis mit Templates (Vorlagen)
+  ack-mail                       Template fuer Bestaetigungsmails
+  address-not-registered  Fehlermail: Noch keine Scheinkennung fuer Adresse
+                          vergeben
+  ballot                  Template fuer einen Wahlschein
+  ballot-personal         Template fuer personalisierten Wahlschein
+  ballot-request          Template fuer eine Wahlscheinanforderung
+  bdsg-error              Fehlermail: Datenschutzklausel nicht akzeptiert
+  bouncelist              Template fuer Liste der ungueltigen Mailadressen
+  cancelled               Template fuer Bestaetigung der Stimmannullierung
+  invalid-account         Fehlermail: Fragwuerdige Mailadresse (Role Account)
+  invalid-name            Fehlermail: Kein gueltiger Name in Mail/Wahlschein
+                          gefunden
+  mailheader              Template fuer den Header von Mails an die Waehler
+  multiple-votes          Fehlermail: Widerspruechliche Stimmen im Wahlschein
+  no-ballot               Fehlermail: Kein Wahlschein in Mail gefunden
+  no-ballotid             Fehlermail: Keine Wahlscheinkennung gefunden
+  no-votes                Fehlermail: Keine Stimmen abgegeben
+  result-multi            Template fuer ein Wahlergebnis mit mehreren Gruppen
+  result-single           Template fuer ein Wahlergebnis mit einer Gruppe
+  result-proportional     Template fuer ein Wahlergebnis, bei dem das Verhaeltnis
+                          oder die Differenz von Ja- und Nein-Stimmen entscheidet
+                          (z.B. Moderationsnachwahlen, "proportional=1" gesetzt)
+  rule-violated           Fehlermail: Wahlregeln aus usevote.rul verletzt
+  voterlist               Template fuer eine Waehlerliste (2. CfV)
+  votes-multi             Template fuer eine Stimmenliste mit mehreren Gruppen
+  votes-single            Template fuer eine Stimmenliste bei einer Gruppe
+  wrong-ballotid          Fehlermail: Falsche Wahlscheinkennung angegeben
+
+* Beim Programmlauf erstellte Dateien (Name ist konfigurierbar):
+
+ergebnis.alle     Saemtliche Einzelergebnisse zusammengefasst (wird 
+                  bei jedem Lauf von uvvote.pl neu erstellt)
+errors.log        Fehlermeldungen, die beim Programmlauf auftauchen,
+                  werden hier abgelegt. Normalerweise bleibt die Datei
+                  leer, ansonsten wird beim Programmende eine Warnung
+                  mit Verweis auf die Datei ausgegeben.
+uidlcache         Eindeutige IDs der abgerufenen Mails (bei Benutzung
+                  von POP3). Darf waehrend einer laufenden Abstimmung
+                  keinesfalls geloescht werden, um Mehrfachzaehlung von
+                  Stimmen zu verhindern.
+uidlcache_req     dto, fuer die Mailbox mit den Wahlscheinanforderungen
+                  (bei Verwendung von personalisierten Wahlscheinen)
+uidlcache_bounce  dto, fuer die Mailbox mit den Bounces (wird von
+                  uvbounce.pl ausgewertet)
+usevote.lock      Temporaere Lockdatei, um gleichzeitiges Ausfuehren
+                  mehrerer Instanzen zu verhindern. Kann (falls vorhanden)
+                  gefahrlos geloescht werden, wenn gerade keines der
+                  Usevote-Scripts laeuft.
+
+* Folgende Unterverzeichnisse werden von Usevote im aktuellen
+  Verzeichnis (CWD) angelegt:
+
+fertig/           Verzeichnis fuer Archivierung verarbeiteter Dateien
+tmp/              Verzeichnis fuer temporaere Dateien
+
+* Von Usevote dort erstellte Dateien:
+
+ack.*             Bestaetigungsmails (in tmp/)
+ack.control       Steuerungsdatei fuer Bestaetigungsmails (in tmp/)
+domail            Shellscript zum Verschicken der Mails (in tmp/)
+ergebnis.*        Ergebnisdateien einzelner Durchlaeufe (zunaecht in tmp/,
+                  spaeter nach fertig/ verschoben und archiviert)
+stimmen.*         Archivierte Stimmendateien (dto.)
+
+
+5. Anfangs-Konfiguration
+========================
+Zuerst solltest Du "usevote.cfg" fuer Dein System anpassen (Details
+dort in den Kommentarzeilen und weiter hinten in dieser Dokumentation).
+Unter Windows werden die Regular Expressions fuer die Stimmenerkennung
+moeglicherweise lauter unlesbare Zeichen enthalten (anderer Zeichensatz),
+dort sollten saemtliche in Namen vorkommende Umlaute und Buchstaben mit
+Accents etc. aufgefuehrt werden. Weiter ist bei Nutzung von Windows zu
+beachten, dass das Kommando zum Loeschen des Bildschirms "cls" und nicht
+"clear" heisst. Dazu gibt es auch eine Einstellung in usevote.cfg. Den
+standardmaessig eingestellten Pager "less" gibt es unter Windows zunaechst
+nicht, more ist aber so buggy, dass man sich am besten "less" fuer
+Windows herunterladen sollte:
+http://www.greenwoodsoftware.com/less/less381d.zip
+
+Im Unterverzeichnis "templates" findest Du Vorlagen, die fuer die
+einzelnen Bestaetigungstexte, Mails, Wahlscheine und Waehlerlisten
+verwendet werden. Hier kannst Du unter Beachtung der Syntax (siehe
+gesonderte Dokumentation) alle Texte an Deinen Geschmack und Deine
+Beduerfnisse anpassen.
+
+
+6. Konfiguration fuer einzelne Abstimmungen
+===========================================
+Fuer jede Abstimmung muss eine eigene Konfigurationsdatei geschrieben
+werden. Aendere einfach usevote.cfg entsprechend der Regeln fuer die
+betreffende Abstimmung. Falls Du haeufiger Wahlleiter spielst, solltest
+Du Dir eine globale Konfigurationsdatei mit Standardeinstellungen
+(z.B. als ~/.usevote-defaults.cfg) anlegen und in der usevote.cfg nur
+noch die Einstellungen setzen, die sich fuer jede Abstimmunge aendern.
+Die globale Datei kann am Anfang der usevote.cfg mit folgender Zeile
+eingebunden werden:
+
+include ~/.usevote-defaults.cfg
+
+Zu beachten ist aber, dass jeweils die letzte Definition einer Option
+benutzt wird. Die usevote.cfg sollte daher wirklich nur noch
+abstimmungsspezifische Einstellungen enthalten, um nicht versehentlich
+die globalen Optionen zu überschreiben.
+
+Ausserdem muss die Datei "usevote.rul" geprueft werden. Wenn Deine
+Abstimmung irgendwelche speziellen Regeln enthaelt (z.B. man kann
+nur fuer Gruppe x stimmen, wenn man auch fuer y gestimmt hat, oder man
+muss fuer den .misc-Abschnitt stimmen, wenn man fuer irgendeinen
+anderen Abschnitt gestimmt hat), dann solltest Du diese Datei lesen
+und entsprechend aendern. Allerdings empfiehlt es sich, bereits im
+RfD deutlich auf die Regeln hinzuweisen, die zur Anwendung kommen sollen.
+Die eingegebenen Regeln koennen dann mit "uvvote -t" getestet werden.
+
+Es ist auch moeglich, mehrere Abstimmungen gleichzeitig durchzufuehren,
+ohne die .pl und .pm Dateien jedesmal mitzukopieren. Folgende Dateien
+sollten im Abstimmungsverzeichnis vorhanden sein (notwendig ist nur
+usevote.cfg, darin können alle anderen Pfade angepasst werden):
+
+bdsgtext.cfg
+mailpatterns.cfg
+messages.cfg
+scheinkennungen
+usevote.cfg
+usevote.rul
+
+Ggf. sollte das "templates"-Verzeichnis kopiert werden, falls
+individuelle Templates fuer einzelne Abstimmungen verwendet werden.
+
+Außerdem muessen i.d.R. einige Pfade in der usevote.cfg angepasst
+werden. Dadurch ist auch die Angabe eines anderen Verzeichnisses
+fuer einige der oben genannten Konfigurationsdateien moeglich,
+falls diese zentral gepflegt werden sollen (z.B. messages.cfg und
+mailpatterns.cfg).
+
+Damit die Usevote-Perlmodule gefunden werden, sollten sie im
+selben Verzeichnis wie die Programmdateien liegen bleiben. Ansonsten
+muss eine Umgebungsvariable gesetzt werden:
+
+PERL5LIB=..
+
+.. steht für das übergeordnete Verzeichnis. Ggf. muss hier ein
+absoluter Pfad eingetragen werden, wenn das Abstimmungsverzeichnis
+kein Unterverzeichnis des Usevote-Verzeichnisses ist.
+
+Je nach Betriebssystem kann die obige Zeile etwas anders aussehen,
+unter Windows sollte z.B. "SET PERL5LIB=C:\PERL\USEVOTE" in die
+autoexec.bat aufgenommen oder sonstwie ausgeführt werden (Pfad
+natürlich entsprechend anpassen).
+
+
+7. Wahlschein generieren
+========================
+Der Wahlschein ist ein Abschnitt des CfV, den die Leute zur Stimmabgabe
+ausfuellen und zurueckschicken sollen. Am einfachsten erzeugst Du
+diesen nach Anpassung der Konfiguration und ggfls. des Templates
+mit "uvballot.pl". Folgendes Format ist erforderlich:
+
+#1  [ Stimme ]  
+#2  [ Stimme ]
+[...]
+
+Eine zusaetzliche Beschreibung rechts daneben macht es dem Waehler
+einfacher, ist aber fuer Usevote nicht zwingend erforderlich. Wichtig
+sind nur die fortlaufenden Nummern, die entsprechend der Eintraege
+in usevote.cfg zugeordnet werden.
+
+Je nach Konfiguration koennen weitere Zeilen noetig sein, dazu spaeter.
+
+Als Stimme in den eckigen Klammern werden diverse Strings erkannt,
+die in usevote.cfg definiert werden koennen. JA, NEIN und ENTHALTUNG
+sind Standard, einige Abwandlungen sind ebenfalls moeglich.
+Zusaetzlich kann ANNULLIERUNG verwendet werden, um eine schon abgegebene
+Stimme zu loeschen und somit nicht im Result aufzutauchen.
+
+Bei der Verwendung von persoenlichen Wahlscheinen (Abschnitt 6a der
+Wahlregeln fuer de.*) ist das Vorgehen etwas anders, siehe hierzu
+den gesonderten Abschnitt weiter hinten.
+
+
+8. Stimmen verarbeiten
+======================
+
+Falls POP3 nicht benutzt werden soll (siehe usevote.cfg), werden alle
+Wahlmails zu einer Abstimmung aus einer Mailboxdatei gelesen
+(Name/Pfad der Datei in usevote.cfg einstellbar). Standardmaessig
+wird das Unix-Mailboxformat erkannt, dieses ist aber ueber "mailstart"
+in usevote.cfg konfigurierbar (bei komplett anderen Formaten muesste
+der Quellcode geaendert werden).
+
+Wechsele nun in das Verzeichnis, in dem die passenden
+Konfigurationsdateien liegen (normalerweise werden diese aus
+dem aktuellen Verzeichnis gelesen, was mehrere gleichzeitige
+Verfahren moeglich macht) und starte uvvote.pl. Bei Problemen
+mit einzelnen Stimmen wirst Du interaktiv durch Menues gefuehrt.
+Meistens gibt es folgende Moeglichkeiten, die durch die
+eingeklammerten Buchstaben und Zahlen aufgerufen werden koennen:
+
+(1) Anzeigen der Wahlmail
+
+Bestaetigen oder Aendern von Wahlschein-Eigenschaften:
+(2) Mailadresse
+(3) Waehlername
+(4) Stimmen
+(5) Scheinkennung
+(6) Datenschutzklausel
+
+(i) Diese Stimme ignorieren (ohne Benachrichtigung verwerfen)
+(w) Weiter
+
+Teilweise gibt es Unteroptionen:
+  (a) OK (Zweifel sind unbegründet, ist alles in Ordnung)
+  (b) Aendern (z.B. wenn der Realname von Usevote nicht vollstaendig
+      erkannt wurde)
+  (c) Ungueltig (es handelt sich nach Deinem Ermessen nicht um
+      eine gueltige Stimmabgabe)
+
+Nun solltest Du zunaechst mit (1) die Mail ansehen und anhand
+der bemaengelten Punkte entscheiden, ob der Wahlschein gueltig
+oder ungueltig ist bzw. welche Maengel tatsaechlich zutreffen.
+
+Mit den Optionen (2) bis (6) kannst Du einzelne Eigenschaften
+des Wahlscheins nachbessern (wenn Usevote z.B. einen Namen oder
+eine Stimme nicht korrekt erkannt hat) oder auch einfach die
+Korrektheit bestaetigen.
+
+Wenn Du alle oben auf der Seite aufgelisteten Maengel beseitigst,
+indem Du die entsprechenden Optionen waehlst, wird die Stimme
+akzeptiert. Bleiben Zweifel unausgeraeumt, wird eine entsprechende
+Fehlermail verschickt (Du erhaeltst dann noch eine gesonderte
+Warnung).
+
+Zum Schluss kannst Du (w) wählen, um fortzufahren. Möchtest Du,
+dass eine Stimme komplett ignoriert wird (weil es sich z.B.
+um Spam handelt), kannst Du (i) waehlen und die folgende
+Sicherheitsfrage mit "JA" beantworten.
+
+Ist uvvote.pl durchgelaufen, koennen im Unterverzeichnis tmp/
+(wird ebenfalls aktuellen Verzeichnis angelegt!) die ermittelten
+Ergebnisse und erzeugten Mails an die Waehler noch einmal geprueft
+werden. Ist alles in Ordnung, werden mittels "uvvote.pl clean"
+die Mails verschickt und die Ergebnisse gespeichert sowie die
+Originalmails archiviert. Hierzu wird das Verzeichnis fertig/
+verwendet.
+
+Falls der komplette uvvote-Lauf verworfen werden soll, kann
+die Datei mit den Waehlermails (stimmen-xyz im tmp-Verzeichnis,
+xyz entspricht der aktuellen Unixtime waehrend des Durchlaufs)
+wieder an den urspruenglichen Platz zurueckkopiert werden
+(bei POP3: fuer den Neuversuch POP3 abschalten und die 
+Maildatei einlesen lassen, da auf dem Server die Mails
+moeglicherweise geloescht wurden) und das komplette tmp-Verzeichnis
+geloescht werden. Anschliessend den uvvote-Lauf erneut durchfuehren.
+Wichtig ist nur, dass das Verzeichnis fertig/ waehrend der ganzen
+Abstimmung mitsamt seines Inhaltes erhalten bleibt.
+
+Noch ein kurzes Wort zum Verschicken der Mails: Jede Mail wird
+in einer gesonderten durchnummerierten Datei (ack.1, ack.2, ...)
+gespeichert, zusaetzlich wird eine Steuerungsdatei ("ack.control")
+erzeugt, welche zu jeder Mail Dateinamen und Empfaenger auflistet.
+Je nach Einstellung werden die Mails dann per SMTP verschickt oder
+es wird ein Shellscript ("domail") erzeugt, welches nach und 
+nach die Mails in Sendmail einspeist. Zwecks Serialisierung ist
+in usevote.cfg die Einstellung "sleepcmd" konfigurierbar,
+normalerweise wird ein "sleep 1" nach jeder Mail ausgefuehrt.
+Das Verschicken der Mails (und damit ggfls. der Aufruf von "domail")
+erfolgt automatisch mit "uvvote.pl clean".
+
+Unter Windows ist nur SMTP moeglich, es sei denn, es gibt
+die Befehle sendmail, sleep und rm ;-)
+
+
+9. Unzustellbare Bestaetigungen
+===============================
+
+Leider kommt es immer mal wieder vor, dass jemand bei der Abstimmung
+eine ungueltige Mailadresse angegeben hat. In diesem Fall erhaeltst Du
+einen Bounce an die Absenderadresse zurueck (diese laesst sich in
+usevote.cfg mit der Option "envelopefrom" einstellen bzw. bei 
+direkten Aufruf eines Mailers über die entsprechende Kommandozeilenoption
+in der mailcmd-Einstellung, z.B. bei Sendmail -f). Du solltest
+solche Fehlermails in einer gesonderten Datei speichern oder in
+einem gesonderten POP3-Postfach halten und beim Erstellen
+des 2. CfVs und des Results uvbounce.pl aufrufen.
+
+Ausgegeben wird von uvbounce.pl eine Liste mit ungueltigen Adressen,
+die einfach an den Abschnitt "ungueltige Stimmen" des 2. CfVs bzw.
+Results angehaengt werden kann. Nicht vergessen, die Stimmen manuell
+aus der Liste der gueltigen Stimmen zu loeschen (falls Du die
+Policy verfolgst, nur Stimmen mit gueltiger Absenderadresse zu werten,
+was empfehlenswert ist) und bei Bedarf das Ergebnis zu korrigieren!
+
+
+10. 2.CfV / Result
+===================
+
+Zum Erstellen des 2. CfVs kannst Du erst einmal den 1. CfV als Vorlage
+benutzen. Am Ende sollte allerdings eine Liste der Personen folgen,
+die bereits abgestimmt haben. Diese laesst sich mit "uvcount.pl -l"
+erzeugen. Moeglicherweise fragt uvcount.pl wegen scheinbar doppelter
+Stimmabgaben nach und bietet an, eine oder beide Stimmen zu ignorieren.
+Du solltest das dann genauer untersuchen und die passende Wahl treffen.
+
+Vorsicht bei doppelten Namen: Es gibt einige doppelt vorkommende
+Namen, hinter denen sich voellig unterschiedliche Personen
+verbergen! Die Mailadresse sollte in solchen Faellen Auskunft
+geben koennen, notfalls einmal nachfragen.
+
+Das Endergebnis kannst Du mit "uvcount.pl -r" ausgeben lassen.
+Bei Verfahren mit mehreren Abstimmungspunkten wird automatusch
+das folgende tabellarische Format verwendet:
+
+ Ja  Nein : 2/3? >=60? : ang.? : Gruppe
+==== ==== : ==== ===== : ===== : =======================================
+ 100   70 : Nein   Ja  :  Nein : Einrichtung von xyz
+
+Bei Abstimmungen ueber nur einen Punkt gibt es auch die Moeglichkeit,
+einen einfachen beschreibenden Text zu verwenden:
+"Es gab 100 JA und 70 NEIN-Stimmen ...".
+
+Falls in usevote.cfg die Einstellung "multigroup = 1" gesetzt ist,
+wird immer die tabellarische Form gewaehlt, bei "multigroup = 0"
+benutzt uvcount.pl bei nur einem Abstimmungspunkt den beschreibenden
+Text. Überschreiben laesst sich dieses mit Aufrufparametern:
+
+uvcount.pl -r -m     = Tabellarisch (m = multigroup)
+uvcount.pl -r -o     = Text (o = onegroup)
+
+Falls in usevote.cfg die Option "proportional = 1" aktiviert ist,
+wird standardmaessig folgendes Ausgabeformat angewandt:
+
+ Ja  Nein : J>=N? Ja/Nein : ang.? : Gruppe
+==== ==== : ===== ======= : ===== : ====================================
+ 100   70 :  Ja    1.429  :       : Einrichtung von abc
+  80   90 : Nein   0.888  :       : Einrichtung von xyz
+
+Das genaue Aussehen ist im Template "result-proportional" aenderbar,
+die Bedingungen und Auswertungsformeln sind ueber usevote.cfg
+einzustellen (prop_formula und condition1). Die Spalte "ang.?" muss
+in diesem Fall manuell ausgefüllt werden, da manchmal auch mehrere
+Abstimmungsgegenstände angenommen werden, z.B. bei Moderationsnachwahlen
+für verschiedene Posten.
+
+Die Waehlerliste mit abgegebenen Stimmen erzeugst Du mit
+"uvcount -v". Diese Option laesst sich auch mit -r kombinieren,
+so dass Du Dir das doppelte Aussortieren von Duplikaten sparst.
+Wichtig: Diese Liste darf erst im Result veroeffentlicht werden,
+nicht im 2. CfV!
+
+Falls ein trivialer Schutz vor Spammern gewuenscht ist, koennen durch
+eine Aenderung in den Template "voterlist", "votes-single", "votes-multi"
+und "bouncelist" alle Mailadressen verfremdet werden. Entweder Du fuegst
+folgende Definition ein:
+
+mail            := value mail | replace '@' ' -at - '
+
+Oder Du veraenderst direkt die vorgegebenen Formate, indem Du
+die Mailadresse mit Hilfe des Pipe-Symbols durch die replace-Funktion
+schickst:
+
+multi-line := value mail | replace '@' ' -at- ' | justify-behind name 72
+
+Die Replace-Funktion erwartet wie im Beispiel gezeigt zwei Werte,
+die zu ersetztende Zeichenfolge (hier das '@') und eine neue Zeichenfolge,
+die statt dessen eingesetzt werden soll und frei gewaehlt werden kann.
+
+Wie bereits beschrieben, sollte am Ende des 2. CfVs bzw. Results noch die
+Liste der gebouncten Mailadressen angehaengt werden (z.B. mit dem Programm
+uvbounce.pl) und bei Bedarf die urspruenglichen Stimmen geloescht und das
+Ergebnis korrigiert werden.
+
+
+11. Datenschutz
+===============
+
+Zum Schutz der deutschen Wahlleiter wurden spezielle Klauseln
+aufgenommen, die vom Waehler die Erlaubnis zur Verarbeitung
+der Stimme verlangen. Die Option "bdsg" in usevote.cfg schaltet
+diese Sonderbehandlung ein oder aus. Folgende Besonderheiten
+gelten bei Aktivierung:
+
+- Im Wahlschein ist ein spezieller Hinweistext enthalten, der
+  aus der Datei bdsgtext.cfg genommen wird. Beim Verarbeiten
+  abgegebener Stimmen wird der Abschnitt wieder mit der Datei
+  verglichen. Fehlt er oder wurde er veraendert, wird der
+  Wahlschein dem Wahlleiter zur Kontrolle vorgelegt.
+  Die Fehlerpruefung ignoriert Quote- und Leerzeichen sowie
+  Zeilenumbrueche, kann aber versagen, wenn dem Quotezeichen
+  Initialen vorangestellt sind (wie in manchen Mailboxnetzen
+  ueblich) oder eine Silbentrennung ueber den Text gelaufen ist.
+
+- Ausserdem enthaelt der Wahlschein eine spezielle Frage,
+  die mit JA zu beantworten ist. Anderenfalls muss ebenfalls
+  Kontrolle durch den Wahlleiter erfolgen. Der Text dieser
+  Frage ist ueber usevote.cfg einzustellen.
+
+Erfolgt eine derartige Kontroll-Vorlage beim uvvote.pl-Lauf,
+sollte der Wahlleiter sich den Wahlschein genau anschauen
+und dann entweder im Menue die Option (6)(a) waehlen oder
+mit (w) fortfahren. Im letzteren Fall wird die Stimme
+verworfen und eine Hinweismail zurueckgeschickt.
+
+Wenn eine Wahlbestaetigung veraendert und zurueckgeschickt
+wird, fehlt der entsprechende Datenschutzhinweis. Da in
+diesem Fall die Zustimmung aber bereits bei der ersten
+Stimmabgabe gegeben wurde, kann der Wahlleiter beruhigt
+die Korrektheit wie oben beschrieben bestaetigen.
+
+
+12. Personalisierte Wahlscheine
+===============================
+
+Die Wahlregeln fuer de.* sehen im Abschnitt 6a ein Verfahren
+mit persoenlichen Wahlscheinen vor, die mit einer speziellen
+Kennung versehen mehr Sicherheit vor Manipulation bieten.
+
+Um dieses Verfahren einzuschalten, muss zunaechst die Option
+"personal = 1" in usevote.cfg gesetzt werden und das Template
+"ballot-personal" im Unterverzeichnis "templates" um den Text
+des CfVs erweitert werden (einfach statt des von Sternchen
+umrandeten Kommentars in der Datei einfuegen). Bitte eventuell
+im Text vorkommende eckige Klammern wie folgt unschaedlich
+machen: \[Text\] (Backslash voranstellen), da in den Templates
+diese Klammern spezielle Bedeutungen haben (siehe gesonderten
+Abschnitt zu dem Thema in dieser Datei).
+
+Der gepostete CfV enthaelt i.d.R. nur einen Dummy-Abschnitt
+mit Hinweis auf die gesonderte Wahlscheinanforderung. So ein
+Abschnitt kann mit uvballot.pl generiert werden.
+
+Der dana-Moderation sollte beim Einreichen des CfVs auch
+ein Dummy-Wahlschein mitgesendet werden, wie er bei der
+Wahlscheinanforderung verschickt wird. Dieser laesst sich mit
+"uvcfv.pl -t" erzeugen.
+
+Die eingehenden Wahlscheinanforderungen sollten an eine
+spezielle Mailadresse gehen und dann mit uvcfv.pl verarbeitet
+werden. Je nach Konfiguration in usevote.cfg werden die
+Mails aus einer Mailboxdatei oder per POP3 eingelesen und jeweils
+mit einem personalisierten Wahlschein beantwortet. Bitte vor
+dem Posten des CfVs einen Test durchfuehren, um sicherzustellen,
+dass die generierten Wahlscheine korrekt sind und auch bei
+der Auswertung akzeptiert werden (hierbei wird die speziell
+eingefuegte Wahlscheinkennung in Verbindung mit der Absenderadresse
+geprueft).
+
+
+13. Die einzelnen Programme
+===========================
+
+uvballot.pl
+  Generiert den Wahlschein fuer den CfV. Bei personalisierten
+  Wahlscheinen (personal = 1 in usevote.cfg) wird nur ein Dummy-
+  Abschnitt mit Hinweisen zur Wahlscheinanforderung ausgegeben.
+
+  Die Vorlage fuer den Wahlschein ist im templates-Unterverzeichnis
+  konfigurierbar:
+  - ballot               Standard-Wahlschein (personal=0)
+  - ballot-request       Dummy-Abschnitt mit Hinweisen zur
+                         Wahlscheinanforderung (personal=1)
+  - ballot-personal      Wahlschein mit Scheinkennung, wird von
+                         uvcfv.pl verwendet
+
+  Aufrufparameter:
+
+  -c config_file         liest die Konfiguration aus config_file
+                         (usevote.cfg falls nicht angegeben)
+  -h                    
+  --help                 zeigen einen Hilfetext an
+
+
+uvbounce.pl
+  Liest Bounces aus den uebergebenen Dateien oder aus einer POP3-
+  Mailbox ein (Verhalten haengt von usevote.cfg ab) und generiert
+  eine Liste von unzustellbaren Adressen, die an den 2. CfV oder das
+  Result angehaengt werden kann. Falls POP3 in usevote.cfg
+  eingeschaltet und die Option -f (siehe unten) nicht benutzt wurde,
+  werden die uebergebenen Dateinamen ignoriert.
+
+  Die Vorlage fuer die ausgegebene Liste ist im templates-
+  Unterverzeichnis konfigurierbar (Datei "bouncelist").
+
+  Aufrufparamter:
+
+  -c config_file         liest die Konfiguration aus config_file
+  -f dateiname(n)   
+  --file dateiname(n)    lesen die Bounces aus den uebergebenen Dateien,
+                         auch wenn in der Konfigurationsdatei POP3
+                         eingeschaltet ist. Diese Option wird benoetigt,
+                         falls zwar die Stimmzettel per POP3 eingelesen
+                         werden sollen, nicht aber die Bounces.
+                         Die Dateinamen werden durch Leerzeichen getrennt.
+  -h
+  --help                 zeigen einen Hilfetext an
+
+
+uvcfv.pl
+
+  Liest Mailboxen ein und beantwortet alle Mails mit personalisierten
+  CfVs (falls personal=1 in usevote.cfg).
+
+  Die Vorlage fuer die ausgegebene Liste ist im templates-
+  Unterverzeichnis konfigurierbar (Datei "ballot-personal").
+
+  Aufrufparamter:
+
+  -c config_file        liest die Konfiguration aus config_file
+                        (usevote.cfg falls nicht angegeben)
+  -t
+  --test                gibt einen Dummy-Wahlschein mit Scheinkennung
+                        an der Standardausgabe aus, zur Pruefungszwecken
+  -h
+  --help                zeigen einen Hilfetext an
+
+
+uvcount.pl
+
+  Zaehlt Stimmen und gibt Waehlerlisten aus. Dient zur Erstellung
+  von 2.CfV und Result.
+
+  Es werden die folgenden Vorlagen aus dem templates-Unterverzeichnis
+  benutzt:
+
+  - result-multi       Ergebnisauszaehlung bei mehreren Gruppen
+  - result-single      Ergebnisauszaehlung bei nur einem Abstimmungs-
+                       gegenstand (nicht tabellarisch, sondern im
+                       Fliesstext), falls in usevote.cfg multigroup=0
+                       gesetzt ist oder die Option -o verwendet wird
+                       (siehe unten)
+  - voterlist          Reine Auflistung der bisherigen Waehler ohne
+                       Stimmen (fuer 2. CfV)
+  - votes-multi        Stimmenliste im Mehrgruppenformat (eine Liste
+                       mit allen Waehlern, rechts daneben die jeweils
+                       abgegebenen Stimmen)
+  - votes-single       Stimmenliste im Eingruppenformat (getrennte
+                       Listen mit Ja-, Nein- und Enthaltungsstimmen)
+
+  Aufrufparamter:
+
+  -c config_file      liest die Konfiguration aus config_file
+                      (usevote.cfg falls nicht angegeben)
+  -f result_file      liest die Stimmen aus result_file (ueberschreibt
+                      die "resultfile"-Angabe aus der Konfigurationsdatei)
+  -l
+  --list              Geben eine Liste aller Waehler aus (ohne Stimmen).
+  -v
+  --voters            Wie --list, aber mit Angabe der abgegebenen Stimmen.
+  -r
+  --result            Ausgabe des Endergebnisses (kann mit --list oder
+                      --voters kombiniert werden).
+  -m
+  --multigroup        Benutzt auch bei Eingruppenabstimmungen das
+                      Mehrgruppenformat beim Endergebnis (ueberschreibt
+                      die Einstellung aus usevote.cfg).
+                      Nur in Kombination mit --result verwendbar,
+                      schliesst --onegroup aus.
+  -o
+  --onegroup          Benutzt bei Eingruppenabstimmungen immer das
+                      Eingruppenformat beim Endergebnis (ueberschreibt
+                      die Einstellung aus usevote.cfg).
+                      Nur in Kombination mit --result verwendbar,
+                      schliesst --multigroup aus.
+  -n
+  --nodup             Verzichtet auf das Aussortieren von doppelten
+                      Stimmabgaben. Nicht empfohlen!
+  -h
+  --help              zeigen einen Hilfetext an
+
+
+
+uvvote.pl
+
+  Liest Mailboxen aus einer Datei oder per POP3 ein wertet die Mails
+  als Stimmzettel aus. Erst beim Aufruf mit der Option "clean" werden
+  die Ergebnisse endgueltig gespeichert und die Bestaetigungsmails
+  verschickt.
+
+  Es werden diverse Vorlagen aus dem templates-Unterverzeichnis
+  benutzt, um Bestaetigungs- und Fehlermails zu generieren.
+
+  Aufrufparameter:
+
+  -c config_file       liest die Konfiguration aus config_file
+                       (usevote.cfg falls nicht angegeben)
+  -t
+  --test               fuehrt einen Test der Konfiguration durch und
+                       gibt das ermittelte Ergebnis aus.
+  -h
+  --help               zeigen einen Hilfetext an
+
+
+
+14. Die Konfigurationsdateien
+=============================
+
+mailpatterns.cfg
+----------------
+
+Diese Datei enthaelt einen regulaeren Ausdruck (Perl-Syntax) pro
+Zeile. Passt die Mailadresse eines Abstimmenden auf eines der Muster,
+wird dem Wahlleiter bei der Auswertung eine Warnung angezeigt.
+In der Regel kann man diese ignorieren, weil heutzutage viele
+Privatleute ihre eigene Domain haben und Role-Accounts wie news@
+benutzen. Sieht man aber so etwas wie news@t-online.de ist
+Vorsicht angebracht und man sollte durch Auswahl von "(W)eiter" 
+die Stimme ungueltig werten und eine entsprechende Fehlermail
+zurueckschicken lassen.
+
+
+messages.cfg
+------------
+
+Hier sind alle kuerzeren Texte enthalten, die Usevote ausgibt,
+unabhaengig davon, ob sie nur dem Wahlleiter bei der Programmausfuehrung
+ausgegeben werden oder in persoenlichen Mails bzw. dem Result
+auftauchen. Laengere Textpassagen sind als eigene Dateien im
+"templates"-Verzeichnis zu finden und bieten weitergehende
+Formatierungsmoeglichkeiten.
+
+Jede Zeile in der messages.cfg besteht aus einem Identifier, einem
+Gleichheitszeichen und dem eigentlichen Text. Der Identifier ist
+grundsaetzlich in Grossbuchstaben gehalten und vom Programmcode fest
+vorgegeben. Aenderungen duerfen nur rechts vom Gleichzeichen vorgenommen
+werden, wenn Dir z.B. Formulierungen nicht gefallen oder Du anders-
+sprachige Ausgaben haben moechtest.
+
+Variablen sind ueber ${VARIABLE} einzubauen, allerdings sind die
+Variablennamen und deren Inhalte vom Programmcode vorgegeben und
+sollten beibehalten werden. Es gibt keine vordefinierten Variablen,
+die Du selbst einbauen kannst, es sei denn, Du erweiterst den
+Programmcode an der entsprechenden Stelle.
+
+Die einzigen Texte, die fest im Programmcode stehen, sind die
+Hilfetexte der einzelnen Programme (ueber die Option -h oder --help
+aufrufbar) sowie die Startmeldungen (Copyright etc.) und initiale
+Fehlermeldungen (usevote.cfg oder messages.cfg nicht gefunden,
+ungueltige oder falsch kombinierte Aufrufparameter).
+
+
+usevote.rul
+-----------
+
+Mit dieser Datei koennen spezielle Regeln fuer eine gueltige Stimmabgabe
+bei Mehrgruppenabstimmungen erstellt werden, z.B. dass man Punkt 3 oder 4
+waehlen muss, wenn man fuer Punkt 2 gestimmt hat (quasi als weitergehende
+Auswahlmoeglichkeit). Solche Regeln verkomplizieren aber die Abstimmung
+und sollten nur in Ausnahmefaellen benutzt werden. Urspruenglich gedacht
+war diese Moeglichkeit z.B. fuer .misc Gruppen, die zwangsweise bei einer
+Aufteilung mit angelegt werden mussten. Dadurch, dass das in den
+Wahlregeln fuer de.* als Automatismus eingebaut ist, kann man sich
+so etwas meistens sparen. Fuer den Fall, dass bei einer vorgeschlagenen
+neuen Gruppe der Name noch unklar ist, gibt ebenfalls das Benutzen
+von speziellen Wahlregeln wenig Sinn, da man auch eine Meinung ueber
+den Gruppennamen haben kann, wenn man eigentlich gegen die Gruppe ist.
+
+Wenn Du aber ein Verfahren betreust, in dem Du solche Sonderregeln
+benoetigst, kannst Du eine Regel pro Zeile in der folgenden Syntax schreiben:
+
+if .jjjjj then J....
+
+Eine Regel beginnt immer mit "if", und danach folgen eine Anzahl Symbole;
+diese Anzahl muss gleich der Anzahl der Gruppen sein, ueber die abgestimmt
+wird. Oben geht es also um eine Abstimmung ueber sechs Gruppen.
+Die Symbole hinter "if" geben an, in welchem Fall diese Regel Anwendung
+finden soll. Falls sie zutrifft, wird die Bedingung hinter "then"
+geprueft (dort muessen nochmal so viele Symbole folgen, wie Gruppen
+im Wahlschein vorkommen). Ist diese Bedingung nicht erfuellt, wird
+der Wahlschein dem Wahlleiter als regelverletzend vorgelegt, und
+wenn diese mit "(w)eiter" den Fehler bestaetigt, wird eine entsprechende
+Fehlermeldung zurueckgeschickt und die Stimme als ungueltig gewertet.
+
+Folgende Symbole sind erlaubt:
+
+   J   eine JA-Stimme
+   N   eine NEIN-Stimme
+   E   eine Enthaltung
+   S   eine JA- oder NEIN-Stimme
+   H   eine Enthaltung oder JA-Stimme
+   I   eine Enthaltung oder NEIN-Stimme
+   .   egal (Ja, nein oder Enthaltung)
+   j   eine oder mehrere der markierten Gruppen hat JA-Stimme
+   n    ""   ""    ""     ""     ""       ""    ""  NEIN-Stimme
+   e    ""   ""    ""     ""     ""       ""    ""  Enthaltung
+   s    ""   ""    ""     ""     ""       ""    ""  Ja- oder Nein-Stimme
+   h    ""   ""    ""     ""     ""       ""    ""  Enthaltung oder Ja-Stimme
+   i    ""   ""    ""     ""     ""       ""    ""  Enthaltung oder Nein-Stimme
+
+Hier noch ein Beispiel:
+    if S... then .ss.
+    if .S.. then ..E.
+    if ..S. then .E..
+
+Diese Regeln sagen: Wer fuer die erste Gruppe abstimmt, der muss auch
+fuer die zweite und dritte Gruppe abstimmen - egal wie. Ausserdem muss er
+(Regeln 2 und 3) sich bei 3 enthalten, wenn er bei 2 eine Stimme abgibt
+und umgekehrt. Die vierte Gruppe wird hier gar nicht betroffen.
+
+
+usevote.cfg
+-----------
+
+Dieses ist die zentrale Konfigurationsdatei fuer Usevote. Einige
+Einstellungen sind nur einmal auf das System und Deine Gewohnheiten
+anzupassen, andere muessen fuer jede Abstimmung geaendert werden.
+Alle Einstellungen sind mit mehr oder weniger sinnvollen
+Standardwerten vorgegeben, die sich bei leerer usevote.cfg mittels
+"uvvote.pl -t" ermitteln lassen.
+
+Es lassen sich durch eine Zeile "include dateiname" weitere 
+Konfigurationsdateien einbinden. Die Stelle, an der dieses geschieht,
+ist dabei wichtig, da die usevote.cfg von oben nach unten gelesen
+wird (zusätzlich eingebundene Konfigurationsdateien werden an der
+Stelle der "include"-Zeile eingefügt) und bei mehrmals vorkommenen
+Optionen der jeweils letzte gesetzte Wert benutzt wird.
+Daher sollte eine Datei mit systemweiten Defaults am besten am
+Anfang der usevote.cfg eingebunden werden, die dort gesetzten
+Optionen sollten aber unbedingt aus der usevote.cfg geloescht werden,
+damit die systemweiten Einstellungen nicht gleich wieder ueberschrieben
+werden.
+
+Das Format ist allgemein:
+
+Option = Wert
+
+Der Wert muss in manchen Faellen 0 (=aus) oder ungleich 0 (=an) sein,
+in anderen Faellen muss eine exakte Zahl oder Zeichenkette eingegeben
+werden. 
+
+Alles, was hinter einem # Zeichen steht, wird als Kommentar gewertet
+und ignoriert. Steht hinter einem Wert so ein Kommentar, wird
+normalerweise alles bis zum Kommentarzeichen noch in den String
+einbezogen. In so einem Fall sollte der Wert von Anfuehrungszeichen
+umschlossen werden. Im folgenden Beispiel wird ein Dateiname
+definiert:
+
+# Der Dateiname endet mit 10 Leerzeichen (bis zum Kommentarzeichen):
+tpl_mailheader = mailheader          # Kommentar
+
+# Der Dateiname enthaelt keine Leerzeichen, da mit Anfuehrungsstrichen
+# begrenzt:
+tpl_mailheader = "mailheader"        # Kommentar
+
+Folgt kein Kommentar in der selben Zeile, koennen die Anfuehrungsstriche
+weggelassen werden.
+
+Hier eine Auflistung der immer anzupassenden Optionen
+(BOOL bedeutet, dass 0 (aus) oder 1 (an) eingetragen werden muss):
+
+votename         Name der Abstimmung
+group1           Erster Abstimmungsgegenstand
+group2           Zweiter (und so weiter durchnummerieren)
+votefile         Mailbox mit eingehenden Stimmen (falls POP3 ausgeschaltet)
+personal         Personalisierte Wahlscheine verwenden? [BOOL]
+                 (siehe gesonderten Abschnitt weiter oben)
+voteaccount      Mailadresse, unter der abgestimmt werden kann
+                 (Reply-To im CfV auf diese Adresse setzen)
+requestfile      Datei mit Anforderungsmails bei "personal=1", falls
+                 POP3 ausgeschaltet ist
+bdsg             Datenschutzklausel generieren und auf deren Existenz 
+                 pruefen? [BOOL] (siehe gesonderten Abschnitt weiter oben)
+replyto          Reply-To Header auswerten? [BOOL] (nicht empfohlen und in
+                 de.* von den GVV nicht praktiziert)
+voteack          Jede Stimme mit einer Bestaetigungsmail beantworten? [BOOL]
+                 (in de.* so ueblich)
+mailcc           weitere Mailadresse, an die eine Kopie jeder Bestaetigungs-
+                 oder Fehlermail geschickt wird (fuer Archivzwecke)
+onestep          Mails direkt verschicken und Ergebnisse speichern? [BOOL]
+                 ("uvvote clean" Aufruf entfaellt)
+multigroup       Fuer das Ergebnis auch bei Eingruppenabstimmung des
+                 Mehrgruppenformat waehlen? [BOOL]
+condition1       Bedingungen fuer einen Erfolg der Abstimmung in Perl-Syntax
+condition2       (normalerweise "$yes>=2*$no" und "$yes>=60"
+resultfile       Datei fuer Gesamtergebnis (normalerweise ergebnis.alle)
+idfile           Datei mit Scheinkennungen (bei "personal=1")
+pop3             POP3 benutzen? [BOOL] (andernfalls Stimmen aus Datei lesen)
+pop3server       POP3-Server fuer eingehende Wahlscheine
+pop3port         POP3-Serverport (normalerweise 110)
+pop3user         Benutzername
+pop3pass         Passwort
+pop3delete       Mails nach Abruf vom Server loeschen? [BOOL]
+pop3uidlcache    Dateiname zum Speichern bereits abgerufener Mail-IDs (UIDL)
+pop3*_req        entsprechende Optionen fuer Mailbox mit eingehenden
+                 Wahlscheinanforderungen (bei "personal=1")
+pop3*_bounce     entsprechende Optionen fuer Mailbox mit Bounces (Fehlermails)
+                 fuer die Verarbeitung durch uvbounce.pl
+smtp             SMTP zum Verschicken von Mails benutzen? [BOOL]
+                 Andernfalls wird direkt an den MTA uebergeben (nur Unix)
+smtpserver       SMTP-Server
+smtpport         SMTP-Serverport (normalerweise 25)
+smtpauth         SMTP-Authentication nach RFC 2554 benutzen? [BOOL]
+smtpuser         SMTP-Benutzername
+smtppass         SMTP-Passwort
+smtphelo         String fuer die HELO-Greeting bei SMTP (Default: Hostname)
+fqdn             String fuer den Host-Teil der Message-ID
+                 (Fully Qualified Domain Name) (Default: Hostname)
+archivedir       Verzeichnis fuer Archivierung von verarbeiteten Stimmen
+tmpdir           temporaeres Verzeichnis
+templatedir      Verzeichnis mit Templates (Vorlagen) fuer diverse Zwecke
+                 (normalerweise "templates", dort liegen bereits vordefinierte
+                 Templates, die i.d.R. nicht angepasst zu werden brauchen)
+formats          Konvertierungsfunktionen fuer die Templates, mit Komma getrennt
+                 (mitgelieferte UVformats.pm muss hier aufgefuehrt werden,
+                 desweiteren koennen eigene Module mit weiteren Funktionen
+                 hier eingebunden werden)
+controlfile      Name der Steuerungsdatei fuer den Mailversand (tmp/ack.control)
+domailfile       Name des Shellscripts zum Versenden der Bestaetigungsmails
+                 (falls smtp=0, normalerweise tmp/domail)
+mailcmd          Aufruf des Mail Transfer Agents (MTA) zum Verschicken der
+                 Bestaetigungsmails (falls smtp=0), z.B.
+                 "sendmail -oi -oem -femail@adresse"
+sleepcmd         Weiteres Kommando, welches nach jeder Mail aufgerufen werden
+                 soll (falls smtp=0). Sinnvoll ist ein "sleep x", wobei x bei
+                 langsamen Systemen hoeher gewaehlt werden sollte.
+clearcmd         Shellbefehl zum Loeschen des Bildschirms (Standard: clear),
+                 muss unter Windows auf "cls" geaendert werden. Falls Shell
+                 oder Betriebssystem keinen solchen Befehl bereitstellen,
+                 sollte ein Kommando verwendet werden, welches eine Trennlinie
+                 oder aehnliches auf dem Bildschirm ausgibt, z.B. mit "echo"
+pager            Shellbefehl zum seitenweisen Darstellen von Mails auf dem
+                 Bildschirm (normalerweise "less"). Unter Windows muss "more"
+                 benutzt werden, unter Unix hingegen gibt es moeglicherweise
+                 bei "more" Probleme mit der Umleitung von STDERR in eine Datei
+                 (wie es z.B. von uvvote.pl benoetigt wird), daher ist "less"
+                 der Standardwert.
+mailfrom         Absender fuer den From-Header der Bestaetigungsmails. Wird
+                 auch in Bestaetigungsmails als Unterschrift eingefuegt und
+                 sollte daher am besten in folgendem Format sein:
+                 Vorname Nachname <gvv@foo.bar>
+                 (ohne Anfuehrungsstriche, falls rfc-konform moeglich)
+envelopefrom     Absender fuer den Envelope (Return-Path) der Bestaetigungsmails
+                 bei Verwendung von SMTP (bitte nur die Adresse eintragen, ohne
+                 Klammern und Zusaetze). Wenn SMTP ausgeschaltet ist, muss
+                 diese Einstellung entsprechend der Mailer-Doku in "mailcmd"
+                 gesetzt werden (z.B. bei Sendmail und kompatiblen mit -f)
+messagefile      Datei mit diversen Meldungen und Textfragmenten (messages.cfg)
+rulefile         Datei mit Wahlregeln (usevote.rul)
+badaddrfile      Datei mit verdaechtigen Mailadressen (mailpatterns.cfg)
+errorfile        Datei fuer Fehlermeldungen beim Programmlauf (errors.log)
+mailstart        Einleitungszeile fuer naechste Mail (RegExp), falls Mails
+                 aus einer Datei eingelesen werden (smtp=0), normalerweise
+                 "^From"
+begin_divider    Trennlinien vor und nach dem Wahlschein
+end_divider      ("Alles vor/nach dieser Zeile bitte loeschen")
+nametext         Text fuer die Namens-Angabe im Wahlschein. Muss im
+                 Wahlschein genauso stehen und wird beim Standardtemplate
+                 auch aus dieser Einstellung uebernommen. Beispieltext:
+                 "Dein Realname, falls nicht im FROM-Header:"
+nametext2        Text fuer Namens-Angabe in Bestaetigungsmails
+                 (dito, Standardwert "Waehlername:")
+addresstext      Text fuer die Adress-Angabe im Wahlschein
+                 (dito, Standardwert "Waehleradresse:")
+ballotidtext     Text für die Angabe der Wahlscheinkennung (falls personal=1)
+                 (dito, Standardwert "Wahlscheinkennung:")
+bdsgtext         Text fuer Datenschutzklausel (falls bdsg=1),  erscheint als
+                 Abstimmungspunkt (z.B. "Datenschutzklausel - Zustimmung: Ich
+                 bin mit der Verarbeitung meiner Daten wie oben beschrieben
+                 einverstanden")
+bdsgfile         Datei mit Erklaerungstext fuer BDSG-Klausel (bdsgtext.cfg)
+rightmargin      Zeilenbreite bei Ausgaben, die nicht durch Templates
+                 gesteuert werden. Hat vor allem auf Bildschirmausgaben
+                 Auswirkung.
+name_re          Regulaerer Ausdruck fuer Erkennung eines gueltigen Realnamens.
+                 Beispiel: "[a-zA-ZäöüáàâéèêíìîóòôúùûÄÖÜÁÁÂÉÈÊÍÌÎÓÒÔÚÙÛß-]{2,} +
+                 .*[a-zA-ZäöüáàâéèêíìîóòôúùûÄÖÜÁÁÂÉÈÊÍÌÎÓÒÔÚÙÛß]{2,}"
+                 (muss ohne Umbruch in eine Zeile)
+
+Regulaere Ausdruecke fuer Erkennung der Stimmen
+(Gross-/Kleinschreibung spielt keine Rolle):
+
+ja_stimme        Standardwert: (J\s*A|J|(D\s*A\s*)?F\s*U\s*E\s*R)
+nein_stimme      Standardwert: (N\s*E\s*I\s*N|N|(D\s*A\s*)?G\s*E\s*G\s*E\s*N)
+enth_stimme      Standardwert: (E|E\s*N\s*T\s*H\s*A\s*L\s*T\s*U\s*N\s*G)
+ann_stimme       Standardwert: A\s*N\s*N\s*U\s*L\s*L\s*I\s*E\s*R\s*U\s*N\s*G
+
+Definition der verwendeten Templatedateien
+(werden im Unterverzeichnis "templates" mitgeliefert):
+
+tpl_mailheader      "mailheader"         # generally used mail header
+tpl_bouncelist      "bouncelist"         # used by uvbounce.pl
+tpl_result_multi    "result-multi"       # used by uvcount.pl -r -m
+tpl_result_single   "result-single"      # used by uvcount.pl -r -o
+tpl_votes_multi     "votes-multi"        # used by uvcount.pl -v (multiple groups)
+tpl_votes_single    "votes-single"       # used by uvcount.pl -v (single group only)
+tpl_voterlist       "voterlist"          # used by uvcount.pl -l (2nd CfV)
+tpl_ballot          "ballot"             # used by uvballot.pl (personal = 0)
+tpl_ballot_request  "ballot-request"     # used by uvballot.pl (personal = 1)
+tpl_ballot_personal "ballot-personal"    # used by uvcfv.pl    (personal = 1)
+tpl_addr_reg        "address-not-registered"  # used by uvvote.pl   (personal = 1)
+tpl_no_ballotid     "no-ballotid"        # used by uvvote.pl   (personal = 1)
+tpl_wrong_ballotid  "wrong-ballotid"     # used by uvvote.pl   (personal = 1)
+tpl_bdsg_error      "bdsg-error"         # used by uvvote.pl   (bdsg = 1)
+tpl_ack_mail        "ack-mail"           # used by uvvote.pl   (voteack = 1)
+tpl_cancelled       "cancelled"          # used by uvvote.pl
+tpl_invalid_account "invalid-account"    # used by uvvote.pl
+tpl_invalid_name    "invalid-name"       # used by uvvote.pl
+tpl_multiple_votes  "multiple-votes"     # used by uvvote.pl
+tpl_no_ballot       "no-ballot"          # used by uvvote.pl
+tpl_no_votes        "no-votes"           # used by uvvote.pl
+tpl_rule_violated   "rule-violated"      # used by uvvote.pl  (siehe usevote.rul)
+
+
+15. Templates
+=============
+
+Die mitgelieferten Templates befinden sich im Verzeichnis "templates".
+In usevote.cfg koennen weitere Verzeichnisse definiert werden, in
+denen auch nach Templates gesucht wird ("templatedir", kommaseparierte
+Liste mit Verzeichnissen). Außerdem koennen dort die Dateinamen der einzelnen
+Templates eingestellt werden, wodurch es moeglich ist, in einzelnen
+Abstimmungen andere, selbst definierte Templates zu verwenden, die in
+einem gesonderten Verzeichnis abgelegt sind.
+
+Eine Templatedatei besteht aus zwei Teilen. Am Anfang werden die
+Formatierungen bestimmter Schluessel definiert und nach einem Trenner
+folgt der eigentlich Template-Koerper, der dann von Programm bearbeitet
+und ausgegeben wird.
+
+   format-key := function1 param | function2 param
+
+   == TEMPLATE ====================================
+
+   Ich bin nun das eigentliche Template:
+
+   format-key: [$format-key]
+
+Der Trenner beginnt mit den Zeichen '== TEMPLATE' danach koennen beliebige
+Zeichen folgen um die beiden Sektionen optisch voneinander abzugrenzen.
+
+Wenn es keine Formatierungsanweisungen gibt, kann der Trenner auch
+weggelassen werden. D.h. wenn kein Trenner gefunden wird, wird der
+komplette Text als Template-Koerper betrachtet.
+
+
+Template-Koerper
+----------------
+
+Im Template-Koerper werden die zu ersetzenden Token durch eckige
+Klammern abgegrenzt. Sollen eckige Klammern im Text ausgegeben werden,
+muessen diese durch einen Backslash freigestellt werden.
+
+   [$termersetzung] [@schleife] nur eine \[ Klammer
+
+
+Termersetzung:
+
+    Ersetzt den Token durch den Wert des angegeben Schluessels:
+
+      [$formatierung] [$schluessel]
+
+    Es wird zuerst nach einer Formatierung mit den entsprechenden
+    Bezeichner gesucht. Ist diese vorhanden, werden die entsprechenden
+    Funktionen ausgefuehrt (siehe folgender Abschnitt).
+
+    Kann kein Format gefunden werden, wird direkt in der im Programmcode
+    gefuellten Datenstruktur nach einem Schluessel mit dem angegeben
+    Bezeichner gesucht und sein Wert eingesetzt. Alle Einstellungen
+    aus der usevote.cfg sind automatisch als Schluessel definiert, es
+    kann also z.B. [$nametext] jederzeit verwendet werden. Eingesetzt
+    wird an der Stelle der mit "nametext = " definierte Text aus
+    usevote.cfg.
+
+    Schlussendlich ist es noch moeglich einen default-Wert zu
+    definieren, der eingesetzt wird, wenn keiner der obigen Wege
+    erfolgreich war:
+
+      Hallo [$name|Unbekannter]!
+
+    Diese Zeile gibt, wenn kein Name definiert wurde, das Wort
+    "Unbekannter" aus.
+
+
+Bedingte Verzeigung:
+
+    Ueberprueft ob der Wert des angegebenen Formats/Schluessel boolsch
+    WAHR ist. Dementsprechend wird der then oder else Block eingefuegt:
+
+      [?if|then|else] oder auch nur [?if|then]
+
+    Die then/else Bloecke werden natuerlich auch auf Tokens geparst und
+    diese dementsprechend ersetzt.
+
+
+Schleifen/Listen:
+
+    Der nachfolgende Textblock wird fuer alle Elemente des durch den
+    Schluessel bezeichneten Arrays ausgefuehrt und eingefuegt.
+
+      [@schluessel|block] oder [@schluessel|block|sep]
+
+    Als zweiter Parameter kann ein Separtor definiert werden, mit dem
+    sich z.B. kommaseparierte Listen erzeugen lassen, da der Separator
+    eben nur zwischen den Elementen eingefuegt wird.
+
+    Auch fuer Schleifen koennen Formatierungen genutzt werden.Allerdings
+    darf kein String zurueckgegeben werden, sondern ein Array mit einer
+    Menge von UVtemplate-Objekten.
+
+
+Kommentare:
+
+    Token die nach der Bearbeitungen entfernt werden:
+
+       [# mich sieht man nicht]
+
+
+Sonstiges:
+
+    Um in Listen einen Zeilenumbruch zu erzwingen, muss lediglich ein
+    '\n' eingefuegt werden, falls eine kompakte Definition der Liste
+    erfolgen soll.
+
+       [@names|[name] [email]\n]
+
+
+Formatierungen
+--------------
+
+Eine Formatierung besteht eigentlich nur aus dem entsprechenden
+Namen und einer beliebigen Anzahl von Funktionsaufrufen:
+
+   format := funktion param1 "param 2" | funktion param
+
+Aehnlich der Unix-Shell-Funktionalitaet, wird dabei die Ausgabe
+einer Funktion an die folgende weitergeleitet. So ist es moeglich,
+verschiedenste simple Formatierungen zu kombinieren um nicht fuer
+jeden Spezialfall eine neue Funktion schreiben zu muessen.
+
+Die jeweilige Formatierungsfunktion erhaelt als Input die
+Datenstruktur, den Output der vorherigen Funktion und die definierten
+Parameter in der entsprechenden Reihenfolge.  
+
+Zahlen und einfache Bezeichner koennen direkt definiert werden.
+Sollen Sonderzeichen oder Leerzeichen uebergeben werden, muessen
+diese gequotet werden. Dazu kann ' oder " verwendet werden.
+
+Die Funktionen geben im Allgemeinen einen String zurueck. Im Rahmen
+von Listen können auch Arrays uebergeben werden.
+
+Die erste Funktion duerfte ueblicherweise 'value' sein. Sie gibt
+den Wert des angegeben Schluessel zurueck, der dann von den
+folgenden Funktionen definiert wird:
+
+   name-60 := value name | fill-right 60
+
+Das Format "name-60" definiert also den Wert des Schluessel "name",
+der um Leerzeichen aufgefuellt wird, bis eine Laenge von 60 Zeichen
+erreicht wird.
+
+   name-email := value name | justify-behind mail 72
+
+"name-email" resultiert in einem String, der zwischen den Werten von
+"name" und "email" genau so viele Leerzeichen enthaelt, damit der
+gesamte String 72 Zeichen lang ist.
+
+Wird dieses Format in einer Liste angewandt, erhaelt man eine Tabelle
+in der die linke Spalte linksbuendig und die rechte Spalte
+entsprechend rechtsbuendig ist.
+
+Soweit ein kleiner Ueberblick ueber die Formatierungen. Ausfuehrliche
+Funktionsbeschreibungen und weitere Beispiele finden sich in der
+POD-Dokumentation des Moduls UVformat ("perldoc UVformat.pm").
+
+Es ist moeglich, selbst eigene Module mit Formatierungsfunktionen
+zu schreiben und ueber die Option "formats" in usevote.cfg einzubinden.
+Am besten kann dazu UVformats.pm als Vorlage genommen werden.
+Wichtig: der Name einer Formatierungsfunktionen darf nur aus
+Gross- und Kleinbuchstaben, Zahlen und Minuszeichen bestehen.
+Unterstriche, Punkte etc. sind nicht erlaubt!
+
+Falls eine elementare Funktion fehlt, kannst Du Dich auch gerne
+bei mir melden und ich pruefe, wie sie sich einbauen laesst.
+
+Marc Langer, im Oktober 2005
diff --git a/UVconfig.pm b/UVconfig.pm
new file mode 100644 (file)
index 0000000..c93f135
--- /dev/null
@@ -0,0 +1,329 @@
+# UVconfig: Reads config files and tests configuration
+# Used by all components
+
+package UVconfig;
+
+use strict;
+use Net::Domain qw(hostname hostfqdn hostdomain);
+use UVmessage;
+use vars qw(@ISA @EXPORT $VERSION $usevote_version %config %messages
+            @rules @groups $bdsg_regexp $bdsg2_regexp %ids %functions);
+
+require Exporter;
+@ISA = qw(Exporter);
+@EXPORT = qw($usevote_version %config %messages @rules @groups
+             $bdsg_regexp $bdsg2_regexp %ids %functions);
+
+# Module version
+$VERSION = "0.18";
+
+# Usevote version
+$usevote_version = "UseVoteGer 4.09";
+
+sub read_config {
+
+  my ($cfgfile, $redir_errors) = @_;
+  
+  # Default configuration options (overwritten in usevote.cfg)
+  %config = (votefile             => "votes",
+             votename             => "unkonfiguriertes Usevote",
+             resultfile           => "ergebnis.alle",
+             rulefile             => "usevote.rul",
+             badaddrfile          => "mailpatterns.cfg",
+             messagefile          => "messages.cfg",
+             idfile               => "scheinkennungen",
+             requestfile          => "anforderung",
+             errorfile            => "errors.log",
+             lockfile             => "usevote.lock",
+             replyto              => 0,
+             personal             => 0,
+             proportional         => 0,
+             bdsg                 => 0,
+             onestep              => 0,
+             multigroup           => 0,
+             voteack              => 1,
+             voteaccount          => "<> (unkonfiguriertes Usevote)",
+             mailfrom             => "<> (unkonfiguriertes Usevote)",
+             envelopefrom         => "<>",
+             mailstart            => "^From ",
+             archivedir           => "fertig",
+             tmpdir               => "tmp",
+             templatedir          => "templates",
+             formats              => "UVformats.pm",
+             domailfile           => "tmp/domail",
+             controlfile          => "tmp/ack.control",
+             mailcmd              => "sendmail -oi -oem",
+             mailcc               => "",
+             sleepcmd             => "sleep 1",
+             clearcmd             => "clear",
+             pager                => "less",
+             pop3                 => 0,
+             pop3server           => "localhost",
+             pop3port             => 110,
+             pop3user             => "default",
+             pop3pass             => "default",
+             pop3delete           => 0,
+             pop3uidlcache        => "uidlcache",
+             pop3server_req       => "localhost",
+             pop3port_req         => 110,
+             pop3user_req         => "default",
+             pop3pass_req         => "default",
+             pop3delete_req       => 0,
+             pop3uidlcache_req    => "uidlcache_req",
+             pop3server_bounce    => "localhost",
+             pop3port_bounce      => 110,
+             pop3user_bounce      => "default",
+             pop3pass_bounce      => "default",
+             pop3delete_bounce    => 0,
+             pop3uidlcache_bounce => 'uidlcache_bounce',
+             smtp                 => 0,
+             smtpserver           => 'localhost',
+             smtpport             => 25,
+             smtphelo             => hostfqdn(),
+             fqdn                 => hostfqdn(),
+             smtpauth             => 0,
+             smtpuser             => '',
+             smtppass             => '',
+             name_re              => '[a-zA-ZäöüÄÖÜß-]{2,} +.*[a-zA-ZäöüÄÖÜß]{2,}',
+             ja_stimme            => '(J\s*A|J|(D\s*A\s*)?F\s*U\s*E\s*R)',
+             nein_stimme          => '(N\s*E\s*I\s*N|N|(D\s*A\s*)?G\s*E\s*G\s*E\s*N)',
+             enth_stimme          => '(E|E\s*N\s*T\s*H\s*A\s*L\s*T\s*U\s*N\s*G)',
+             ann_stimme           => 'A\s*N\s*N\s*U\s*L\s*L\s*I\s*E\s*R\s*U\s*N\s*G',
+             condition1           => '$yes>=2*$no', # twice as many yes as no
+             condition2           => '$yes>=60',    # min 60 yes votes
+             prop_formula         => '$yes/$no',
+             tpl_ack_mail         => 'ack-mail',
+             tpl_bouncelist       => 'bouncelist',
+             tpl_mailheader       => 'mailheader',
+             tpl_result_multi     => 'result-multi',
+             tpl_result_single    => 'result-single',
+             tpl_result_prop      => 'result-proportional',
+             tpl_votes_multi      => 'votes-multi',
+             tpl_votes_single     => 'votes-single',
+             tpl_voterlist        => 'voterlist',
+             tpl_ballot           => 'ballot',
+             tpl_ballot_request   => 'ballot-request',
+             tpl_ballot_personal  => 'ballot-personal',
+             tpl_addr_reg         => 'address-not-registered',
+             tpl_no_ballotid      => 'no-ballotid',
+             tpl_wrong_ballotid   => 'wrong-ballotid',
+             tpl_bdsg_error       => 'bdsg-error',
+             tpl_cancelled        => 'cancelled',
+             tpl_invalid_account  => 'invalid-account',
+             tpl_invalid_name     => 'invalid-name',
+             tpl_multiple_votes   => 'multiple-votes',
+             tpl_no_ballot        => 'no-ballot',
+             tpl_no_votes         => 'no-votes',
+             tpl_rule_violated    => 'rule-violated',
+             begin_divider        => 'Alles vor dieser Zeile bitte loeschen',
+             end_divider          => 'Alles nach dieser Zeile bitte loeschen',
+             nametext             => 'Dein Realname, falls nicht im FROM-Header:',
+             nametext2            => 'Waehlername:',
+             addresstext          => 'Waehleradresse:',
+             ballotidtext         => 'Wahlscheinkennung:',
+             bdsgtext             => 'Datenschutzklausel - Zustimmung',
+             bdsgfile             => 'bdsgtext.cfg',
+             rightmargin          => 72,
+             usevote_version      => $usevote_version); # needed for use in templates
+
+  # read config
+  read_file($cfgfile);
+
+  # read message file
+  open (RES, "<$config{messagefile}")
+     or die "Could not read message file $config{messagefile}!\n\n";
+  my @lines = <RES>;
+  close(RES);
+
+  foreach my $line (@lines) {
+    chomp($line);
+    $line =~ s/^#.*//;        # Delete comments
+    if ($line =~ m/^\s*([A-Za-z0-9_-]+)\s*=\s*(.+)\s*$/){
+      $messages{$1} = $2;
+    }
+  } 
+
+  # missing "groupX =" lines in config file?
+  die UVmessage::get("CONF_NOGROUPS", CONFIGFILE=>$cfgfile) . "\n\n" unless (@groups);
+
+  # redirect errors to a file if desired by calling script
+  open (STDERR, ">$config{errorfile}") if ($redir_errors);
+
+  # check for data protection law? read text for ballot
+  parse_bdsgtext() if ($config{bdsg});
+
+  # personalized ballots? read ballot IDs
+  read_ballot_ids() if ($config{personal});
+
+  load_formats() if ($config{formats});
+}
+
+
+##############################################################################
+# read config file                                                           #
+##############################################################################
+
+sub read_file {
+
+  my $cfgfile = shift;
+  my $CONFIG;
+  open ($CONFIG, "<$cfgfile") or die "Could not find config file $cfgfile!\n\n";
+
+  while (<$CONFIG>) {
+    next if (/^#/);     # line is a comment
+    chomp;              # delete \n
+    s/\r//;             # delete \r if present
+    s/([^\\])#.*$/$1/;  # Remove comments not starting at beginning of line.
+                        # (ignore escaped comment sign \#)
+
+
+    if (/^include (\S+)$/) {
+      # include other config file
+      read_file($1);
+
+    } elsif (my($key, $value) = split (/\s*=\s*/, $_, 2)) {
+      # delete trailing spaces
+      $value =~ s/\s*$//;
+
+      # evaluate quotation marks
+      $value =~ s/^\"([^\"]+[^\\\"])\".*$/$1/;
+      $value =~ s/\\"/"/g;
+
+      if ($key =~ /^group(\d+)$/) {
+        my $num = $1;
+        $groups[$num-1] = $value;    # internal index starts at 0
+      } else {
+        $config{$key} = $value;
+      }
+    }
+  }
+
+  close ($CONFIG);
+
+}
+
+
+##############################################################################
+# parse data protection law texts                                            #
+##############################################################################
+
+sub parse_bdsgtext {
+
+  open (BDSG, "<$config{bdsgfile}") or die UVmessage::get("CONF_NOBDSGFILE",
+                            ('BDSGFILE' => "$config{bdsgfile}")) . "\n\n";
+  my @bdsg = <BDSG>;
+  close BDSG;
+
+  $config{bdsginfo} = '';
+
+  foreach my $line (@bdsg) {
+    $config{bdsginfo} .= $line unless ($line =~ /^\s*#/);
+  }
+
+  my $bdsgtmp = $config{bdsginfo};
+  $bdsgtmp =~ s/\"/\\\"/g;
+  $bdsgtmp =~ s/\'/\\\'/g;
+  $bdsgtmp =~ s/\(/\\\(/g;
+  $bdsgtmp =~ s/\)/\\\)/g;
+  $bdsgtmp =~ s/\[/\\\[/g;
+  $bdsgtmp =~ s/\]/\\\]/g;
+  $bdsgtmp =~ s/\./\\\./g;
+  $bdsgtmp =~ s/\!/\\\!/g;
+  my @bdsgtext = split(' ', $bdsgtmp);
+
+  # Build Regular Expression from single words.
+  # There has to be at least a space between two words, additional characters
+  # are allowed, e.g. quotation marks (but no letters)
+  $bdsg_regexp = join('\s\W*?', @bdsgtext);
+
+  # Build Regular Expression from $config{bdsgtext}
+  $bdsg2_regexp = join('\s\W*?', split(' ', $config{bdsgtext}));
+}
+  
+
+##############################################################################
+# Read suspicious mail addresses (normally mailpatterns.cfg)                 #
+##############################################################################
+
+sub read_badaddr {
+  
+  my @bad_addr = ();
+
+  open (BADADDR, "<$config{badaddrfile}") or die 
+    UVmessage::get("CONF_NOBADADDR",(BADADDRFILE => $config{badaddrfile})) . "\n\n";
+
+  while (<BADADDR>) {
+    chomp;
+    # Comment line? Not only whitespaces?
+    if (/^[^#]/ && /[^\s]/) {
+      push(@bad_addr, $_);
+    }
+  }
+
+  close (BADADDR);
+  return @bad_addr;
+}
+
+
+##############################################################################
+# Read ballot IDs                                                            #
+##############################################################################
+
+sub read_ballot_ids {
+  # open file with ballot ids
+  open(FILE, "<$config{idfile}") or return 1;
+  while (<FILE>) {
+    chomp;
+    # Format: mailaddress (whitespace) ballot ID
+    if (/^(.+@.+)\s+([a-z0-9]+)/) {
+      # $1=mailadresse, $2=ballot ID
+      $ids{$1} = $2;
+    }
+  }
+  close(FILE);
+  return 0;
+}
+
+
+##############################################################################
+# Funktionen für Templates laden                                             #
+##############################################################################
+
+sub load_formats {
+  my $modules = $config{formats};
+
+  my @modules = split(/\s*,\s*/, $modules);
+
+  foreach my $module (@modules){
+    if (-r $module){
+      require $module;
+    }
+  }
+}
+
+
+##############################################################################
+# config test                                                                #
+##############################################################################
+
+sub test_config {
+  print UVmessage::get("CONF_CONFIG"), "\n\n";
+  foreach my $option (keys %config) {
+    print "$option = $config{$option}\n";
+  }
+
+  print "\n", UVmessage::get("CONF_TEST_RULES");
+  if (@rules) {
+    print "\n\n";
+    for (my $n=0; $n<@rules; $n++) {
+      my $text = UVrules::rule_print($n);
+      print $text;
+    }
+    print "\n";
+  } else {
+    print UVmessage::get("CONF_NO_RULES"), "\n\n";
+  }
+}
+
+1;
diff --git a/UVformats.pm b/UVformats.pm
new file mode 100644 (file)
index 0000000..09e79c5
--- /dev/null
@@ -0,0 +1,566 @@
+#----------------------------------------------------------------------
+  package UVformats;
+#----------------------------------------------------------------------
+
+=head1 NAME
+
+UVformats - Methoden zur Stringformatierung
+
+=head1 SYNOPSIS
+
+  value  <name-of-key>
+  append <name-of-key>
+
+  fill-left   <width> <character>
+  fill-right  <width> <character>
+  fill-center <width> <character>
+
+  justify       <name-of-key> <width>
+  justify-before <name-of-key> <width>
+  justify-behind <name-of-key> <width>
+  
+  first-words  <width>
+  drop-words   <width>
+  create-lines <width>
+
+  multi-graph <width> <position> <count>
+  multi-line  <width> <count>
+
+  quote <string>
+  replace <original-string> <replacement-string>
+  sprintf <format-string>
+
+  generate_date_header
+
+=head1 DESCRIPTION
+
+Dieses Modul stellt verschiedenste Methoden bereit, um die Strings in 
+den Templates auf die unterschiedlichste Art zu formatieren. 
+
+Dieses Modul beschraenkt sich auf die Beschreibung der Funktionen. Ihre
+Einbindung wird in UVtemplates beschrieben.
+
+=head1 FUNCTIONS
+
+=over 3
+
+=cut
+
+#----------------------------------------------------------------------
+
+use strict;
+use vars qw(@ISA @EXPORT $VERSION $functions);
+
+use Exporter;
+$VERSION = 0.01;
+
+@ISA = qw(Exporter);
+@EXPORT = qw( getFunctions );
+
+use Text::Wrap;
+#use POSIX qw(strftime);
+use Email::Date;
+
+#----------------------------------------------------------------------
+
+sub getFunctions{
+  return $functions;
+}
+
+#----------------------------------------------------------------------
+=item value
+
+Gibt den Wert eines Schluessel zurueck. 
+
+  new-key := value 'old-key' | <other-functions> ...
+
+Diese Funktion sollte dann eingesetzt werden, wenn man einen virtuellen
+Schluessel erzeugen will. D.h. der Bezeichner nicht im Template als
+Schluessel vorhanden ist. Durch den Einsatz von value wird der Wert eines
+anderen Schluessel kopiert und kann dann weiter formatiert werden.
+
+=cut
+
+sub value{
+  my ($data, $value, $key) = @_;
+  return $data->getKey($key);
+}
+
+#----------------------------------------------------------------------
+
+=item append
+
+Den Wert eines anderen Schluessels an den bisherigen String anhaengen.
+
+  ... | append 'other-key' | ...
+
+Per default wird als Trenner der beiden String ein Leerzeichen verwendet.
+Soll dieses entfallen oder ein anderes Zeichen benutzt werden, so kann
+ein dementsprechender drittere Parameter angegeben werden.
+
+  ... | append 'other-key' ''  | ...
+  ... | append 'other-key' '_' | ...
+
+Im ersten Beispiel wird der Wert von C<other-key> nahtlos hinzugefuegt.
+Im zweiten statt des Leerzeichens '_' benutzt.
+
+=cut
+
+sub append{
+  my ($data, $value, $key, $sep) = @_;
+
+  $sep = ' ' unless defined($sep);
+
+  return $value. $sep. $data->getConvKey($key);
+}
+
+#----------------------------------------------------------------------
+
+=item fill-left, fill-right, fill-center
+
+Fuellt den String entsprechend mit Zeichen auf bis die gewuenschte
+Laenge erreicht ist. Bei C<fill-left> werden die Zeichen vorranggestellt,
+bei C<fill-right> angehaengt. C<fill-center> verteilt die Zeichen 
+gleichmaessig vor und nach dem String.
+
+  ... | fill-left 72 '.' | ...
+
+Wird kein zweiter Parameter angegeben, wird automatisch das Leerzeichen
+benutzt.
+
+  ... | fill-right 60 | ...
+
+Ist der String bereits laenger als gewuenscht, wird er nicht weiter
+veraendert und auch nicht verkuerzt.
+
+=cut
+
+sub fill_left{ 
+  my ($data, $value, $width, $char) = @_;
+
+  $width ||= 72;
+  $char  = ' ' unless (defined($char) && length($char) == 1);
+
+  my $fill = $width - length($value);
+
+  $value = $char x $fill . $value if ($fill > 0);
+
+  return $value;
+}
+
+sub fill_right{ 
+  my ($data, $value, $width, $char) = @_;
+
+  $width ||= 72;
+  $char  ||= ' ';
+
+  my $fill = $width - length($value);
+
+  $value .= $char x $fill if ($fill > 0);
+
+  return $value;
+}
+
+sub fill_both{ 
+  my ($data, $value, $width, $char) = @_;
+
+  $width ||= 72;
+  $char  ||= ' ';
+
+  my $fill = $width - length($value);
+  
+  if ($fill > 0){
+    my $left  = int($fill / 2);
+    my $right = $fill - $left;
+
+    $value = $char x $left . $value . $char x $right; 
+  }
+
+  return $value;
+}
+
+#----------------------------------------------------------------------
+
+=item justify, justify-before, justify-behind
+
+Fuegt zwischen den existierenden String und dem Wert des angegebenen 
+Schluessel genau so viele Leerzeichen ein, damit die gewuenschte 
+Stringlaenge erreicht wird.
+
+  ... | justify-behind 'key' 72 | ...
+
+C<justify-behind> haengt den Wert des Schluessel an das Ende des Strings,
+C<justify-before> stellt es davor.
+
+  justify-behind: existing-string.........value-of-key
+  justify-before: value-of-key.........existing-string
+
+C<justify> ist lediglich ein Alias auf C<justify-behind>.
+
+Sind die beiden Strings zusammen länger als die gewuenschte
+Zeilenlaenge, wird automatisch einen Zeilenbruch eingefuegt
+und beide Zeilen entsprechend mit Leerzeichen gefuellt.
+
+  very-very-very-long-existing-string.........\n
+  ...................and-a-too-long-new-string
+
+=cut
+
+sub justify_behind{
+  my ($data, $value, $key, $width) = @_;
+  return _justify( $value, $data->getConvKey($key), $width);
+}
+
+sub justify_before{
+  my ($data, $value, $key, $width) = @_;
+  return _justify( $data->getConvKey($key), $value, $width);
+}
+
+sub _justify{
+  my ($lval, $rval, $width) = @_;
+
+  my $sep = ' ';
+
+  if (length($lval.$rval) >= $width ){
+    # wir basteln zwei zeilen
+    $lval .= $sep x ($width - length($lval));
+    $rval = $sep x ($width - length($rval)) . $rval;
+
+    return $lval."\n".$rval;
+
+  }else{
+    my $fill = $width - length($lval) - length($rval);
+    return $lval . $sep x $fill . $rval;
+  }
+}
+
+#----------------------------------------------------------------------
+
+=item first-words
+
+Gibt nur die ersten Worte eines Strings zurueck, die vollstaendig
+innerhalb der angegebenen Laenge liegen.
+
+=cut
+
+sub first_words{
+  my ($data, $value, $width) = @_;
+
+  my @words = split('\s+', $value);
+  my $string;
+
+  $string .= shift(@words);
+
+  while(@words && (length($string) + length($words[0]) + 1) < $width){
+    $string .= ' ' . shift(@words);
+  }
+
+  return $string;
+}
+
+=item drop-words
+
+Alle Woerter am Anfang des Strings entfernen, die komplett innerhalb
+der angegebenen Laenge liegen.
+
+=cut
+
+sub drop_words{
+  my ($data, $value, $width) = @_;
+
+  my @words = split('\s+', $value);
+
+  # das erste "Wort" immer verwerfen, egal wie lang es ist
+  my $first  = shift(@words);
+  my $length = length($first);
+
+  while (@words && ( $length + length($words[0]) + 1 ) < $width ){
+    $length += length($words[0]) + 1;
+    shift(@words);
+  }
+
+  return join(' ', @words);
+}
+
+=item create-lines
+
+Zerlegt einen String in einen Array, in dem die einzelnen Zeilen nicht
+laenger als die gewuenschte Anzahl Zeichen sind.
+
+  absatz := value 'key' | create-lines 72 
+
+Mit Hilfe dieser Funktion ist es moeglich, ueberlange Zeilen zu Absatzen
+umzuformatieren.
+
+Die Funktion erzeugt intern eine Liste, die jeweils den Schluessel C<line>
+mit dem entsprechenden String als Wert enthaelt. 
+
+Im Template wird der so Absatz dann mit Hilfe des Schleifen-Syntax
+eingebunden:
+
+  [@absatz|[line]\n]
+
+Achtung! Da die Funktion keinen String zurueckgibt, sollte sie am Ende
+der Kette stehen, da die normalen Formatierungsfunktionen einen String
+als Input erwartern!
+
+=cut
+
+sub create_lines{
+  my ($data, $value, $width) = @_;
+
+  my @words = split('\s+', $value);
+
+  my @lines;
+
+  while (@words){
+    my $string .= shift(@words);
+
+    while(@words && (length($string) + length($words[0]) + 1) < $width){
+      $string .= ' ' . shift(@words);
+    }
+
+    my $new = $data->new( line => $string );
+    push(@lines, $new);
+  }
+
+  return \@lines;
+}
+
+#----------------------------------------------------------------------
+
+=item multi-graph, multi-line
+
+Spezielle Funktionen, um eine bestimmte graphische Ausgabe fuer
+Votings mit mehreren Abstimmungspunkten zu erzeugen:
+
+  Punkt 1 --------------------------+
+  Punkt 2a ------------------------+|
+  Punkt 2b -----------------------+||
+  Punkt 3 -----------------------+|||
+                                 ||||
+  Name of Voter 1                jjnn
+  Name of Voter 2                nnjj
+
+C<multi-graph> ist hierbei für die Formatierung der einzelnen Abstimmungspunkte 
+zustaendig.
+
+  multi-graph 'key' 'width' 'pos-key' 'max-key'
+
+Der erste Parameter gibt den Schluessel an, dessen Wert als Abstimmungspunkt
+ausgegeben werden soll. C<width> die Laenge des zu erzeugenden Strings.
+C<pos-key> und C<max-key> sind die Namen der Schluessel, in denen stehen
+muss, um den wievielten Abstimmungspunkt es sich handelt (per default 'pos')
+und wieviele Abstimmungspunkte es insgesamt gibt ('anzpunkte').
+
+C<multi-line> erzeugt einfach nur einen String in der gewuenschten
+Laenge, der entsprechend der Anzahl der Abstimmungspunkte mit '|'
+abschliesst.
+
+=cut
+
+sub mgraph{
+  my ($data, $value, $width, $pkey, $okey) = @_;
+  return unless $data;
+
+  my $pos = $data->getKey($pkey || 'pos');
+  my $of  = $data->getKey($okey || 'anzpunkte');
+
+  my $gfx = '';
+  
+  $gfx = ' ---'.'-' x ($of-$pos) .'+'. '|' x ($pos - 1) if ($pos && $of);
+
+  if (length($value.$gfx) < $width){
+    $value = ' ' x ($width - length($value.$gfx)) . $value . $gfx;
+
+  }elsif (length($value.$gfx) > $width){
+    my @lines = _wrap($value, $width - length($gfx));
+   
+    $value = shift(@lines) . $gfx;
+    $value = ' ' x ($width - length($value)) . $value;
+
+    # Hilfzeile erzeugen
+    $gfx = '    '.' ' x ($of-$pos) . '|' x ($pos) if ($pos && $of);
+
+    foreach my $line (@lines){
+      $value .= "\n".' ' x ($width - length($line.$gfx)) . $line . $gfx;
+    }
+  }
+
+  return $value;
+}
+
+sub mgline{
+  my ($data, undef, $width, $okey) = @_;
+  return unless $data;
+
+  my $of = $data->getKey($okey || 'anzpunkte') || 0;
+
+  return ' ' x ($width - $of) . '|' x $of;
+}
+
+
+sub _wrap{
+  my ($string, $width) = @_;
+
+  my @words = split('\s+', $string);
+
+  my @lines;
+
+  while (@words){
+    my $line .= shift(@words);
+
+    while(@words && (length($line) + length($words[0]) + 1) < $width){
+      $line .= ' ' . shift(@words);
+    }
+
+    push(@lines, $line);
+  }
+
+  return @lines;
+}
+
+
+#----------------------------------------------------------------------
+
+=item quote
+
+Stellt in einem (mehrzeiligem) String jeder Zeile den gewuenschten
+Quotestring voran.
+
+  body := value 'body' | quote '> '
+
+=cut
+
+sub quote{
+  my ($data, $value, $quotechar) = @_;
+
+  $quotechar = '> ' unless defined($quotechar);
+
+  $value =~ s/^/$quotechar/mg;
+  return $value;
+}
+
+
+#----------------------------------------------------------------------
+
+=item replace
+
+Ersetzt in einem String ein oder mehrere Zeichen durch eine beliebige
+Anzahl anderer Zeichen. Diese Funktion kann z.B. genutzt werden, um
+beim Result die Mailadressen zu verfremden (Schutz vor Adress-Spidern).
+
+  mail := value 'mail' | replace '@' '-at-'
+
+=cut
+
+sub replace{
+  my ($data, $value, $original, $replacement) = @_;
+
+  $original = ' ' unless defined($original);
+  $replacement = ' ' unless defined($replacement);
+
+  $value =~ s/\Q$original\E/$replacement/g;
+  return $value;
+}
+
+
+#----------------------------------------------------------------------
+
+=item sprintf
+
+Gibt Text oder Zahlen mittels der Funktion sprintf formatiert aus
+(siehe "man 3 sprintf" oder "perldoc -f sprintf").
+
+  proportion := value 'proportion' | sprintf '%6.3f'
+
+=cut
+
+sub sprintf{
+  my ($data, $value, $format) = @_;
+
+  $format = '%s' unless defined($format);
+
+  return sprintf($format, $value);
+}
+
+
+#----------------------------------------------------------------------
+
+=item generate_date_header
+
+Gibt ein Datum im RFC822-Format zur Verwendung im Date:-Header einer
+Mail aus.
+
+  date := generate_date_header
+
+=cut
+
+sub generate_date_header{
+  my ($data, $value, $format) = @_;
+  #return strftime('%a, %d %b %Y %H:%M:%S %z', localtime);
+  return format_date;
+}
+
+#----------------------------------------------------------------------
+
+=item generate_msgid
+
+Gibt eine Message-ID im RFC822-Format zur Verwendung im Message-ID:-Header
+einer Mail aus.
+
+  msgid := generate_msgid
+
+=cut
+
+sub generate_msgid{
+  return ("<".$$.time().rand(999)."\@".$UVconfig::config{fqdn}.">");
+}
+
+
+#----------------------------------------------------------------------
+
+BEGIN{
+  %UVconfig::functions = ( %UVconfig::functions,
+    value              => \&value,
+    append             => \&append,
+
+    'fill-left'        => \&fill_left,
+    'fill-right'       => \&fill_right,
+    'fill-both'        => \&fill_both,
+
+    justify            => \&justify_behind,
+    'justify-behind'   => \&justify_behind,
+    'justify-before'   => \&justify_before,
+
+    'first-words'      => \&first_words,
+    'drop-words'       => \&drop_words,
+
+    'create-lines'     => \&create_lines,
+
+    'multi-graph'      => \&mgraph,
+    'multi-line'       => \&mgline,
+
+    'quote'             => \&quote,
+    'replace'           => \&replace,
+    'sprintf'           => \&sprintf,
+
+    'generate-date-header' => \&generate_date_header,
+    'generate-msgid'    => \&generate_msgid
+  );
+}
+
+1;
+
+#----------------------------------------------------------------------
+
+=back
+
+=head1 SEE ALSO
+
+L<UVtemplate>
+
+=head1 AUTHOR
+
+Cornell Binder <cobi@dex.de>
+Marc Langer <usevote@marclanger.de>
diff --git a/UVmenu.pm b/UVmenu.pm
new file mode 100644 (file)
index 0000000..d90da9c
--- /dev/null
+++ b/UVmenu.pm
@@ -0,0 +1,380 @@
+# UVmenu: menu for interaction with the votetaker
+# Used by uvvote.pl, uvcfv.pl, uvcount.pl
+package UVmenu;
+use strict;
+use UVconfig;
+use UVmessage;
+use UVrules;
+use vars qw($VERSION);
+
+use Text::Wrap qw(wrap $columns);
+# Module version
+$VERSION = "0.4";
+
+##############################################################################
+# Menu for interaction with the votetaker                                    #
+# Parameters: votes list and header (references to arrays)                   #
+#             Body, Mailadress, Name, Ballot ID (references to strings)      #
+#             List of newly set fields (reference to array)                  #
+#             List of errors to correct (Array-Ref)                          #
+# Return Values: 'w': proceed                                                #
+#                'i': ignore (don't save vote)                               #
+##############################################################################
+
+sub menu {
+  my ($votes, $header, $body, $addr, $name, $ballot_id, $set, $errors) = @_;
+  my $input = "";
+  my $voter_addr = $$addr || '';
+  my $voter_name = $$name || '';
+  my @newvotes = @$votes;
+  my $mailonly = 0;
+  my %errors;
+  $$ballot_id ||= '';
+
+  foreach my $error (@$errors) {
+
+    # unrecognized vote: extract group number und display warning
+    if ($error =~ /^UnrecognizedVote #(\d+)#(.+)$/) {
+      $errors{UnrecognizedVote} ||= UVmessage::get("MENU_UNRECOGNIZEDVOTE");
+      $errors{UnrecognizedVote} .= "\n  " . UVmessage::get("MENU_UNRECOGNIZED_LIST")
+                                          . " #$1: $2";
+
+    # violated rule: extract rule number and display warning
+    } elsif ($error =~ /^ViolatedRule #(\d+)$/) {
+      $errors{ViolatedRule} ||= UVmessage::get("MENU_VIOLATEDRULE", (RULE => "#$1"));
+
+    } else {
+      # special handling if called from uvballot.pl
+      $mailonly = 1 if ($error =~ s/Ballot$//);
+
+      # get error message for this error from messages.cfg
+      $errors{$error} = UVmessage::get("MENU_" . uc($error));
+    }
+  }
+
+  # This loop is only left by 'return'
+  while (1) {
+
+    system($config{clearcmd});
+    print UVmessage::get("MENU_PROBLEMS") . "\n";
+
+    foreach my $error (keys %errors) {
+      print "* $errors{$error}\n";
+    }
+
+    my $menucaption = UVmessage::get("MENU_CAPTION");
+    print "\n\n$menucaption\n";
+    print "=" x length($menucaption), "\n\n";
+    print "(1) ", UVmessage::get("MENU_SHOW_MAIL"), "\n\n",
+          UVmessage::get("MENU_CHANGE_PROPERTIES"), "\n",
+          "(2) ", UVmessage::get("MENU_ADDRESS"), " [$voter_addr]\n";
+
+    # don't print these options if called from uvcfv.pl
+    unless ($mailonly) {
+      print "(3) ", UVmessage::get("MENU_NAME"), " [$voter_name]\n";
+      print "(4) ", UVmessage::get("MENU_VOTES"), " [", @$votes, "]\n";
+      print "(5) ", UVmessage::get("MENU_BALLOT_ID"), " [$$ballot_id]\n"
+        if ($config{personal});
+      print "(6) ", UVmessage::get("MENU_BDSG"), "\n" if ($config{bdsg});
+    }
+
+    print "\n",
+          "(i) ", UVmessage::get("MENU_IGNORE"), "\n",
+          "(w) ", UVmessage::get("MENU_PROCEED"), "\n\n",
+          UVmessage::get("MENU_PROMPT");
+
+    do { $input = <STDIN>; } until ($input);
+    chomp $input;
+    print "\n";
+
+    # only accept 1, 2, i and w if called from uvcfv.pl
+    next if ($mailonly && $input !~ /^[12iw]$/i);
+
+    if ($input eq '1') {
+      system($config{clearcmd});
+      # ignore SIGPIPE (Bug in more and less)
+      $SIG{PIPE} = 'IGNORE';
+      open (MORE, "|$config{pager}");
+      print MORE join("\n", @$header), "\n\n", $$body, "\n";
+      close (MORE);
+      
+      print "\n", UVmessage::get("MENU_GETKEY");
+      $input = <STDIN>;
+
+    } elsif ($input eq '2') {
+      my $sel;
+      do {
+        print "[a] ", UVmessage::get("MENU_ADDRESS_OK"), "\n",
+              "[b] ", UVmessage::get("MENU_ADDRESS_CHANGE"), "\n",
+              "[c] ", UVmessage::get("MENU_ADDRESS_INVALID"), "\n\n",
+              UVmessage::get("MENU_PROMPT");
+        $sel = <STDIN>;
+      } until ($sel =~ /^[abc]$/i);
+      if ($sel =~ /^a$/i) {
+        delete $errors{SuspiciousAccount};
+        delete $errors{InvalidAddress};
+        next;
+      } elsif ($sel =~ /^c$/i) {
+        delete $errors{SuspiciousAccount};
+        $errors{InvalidAddress} = UVmessage::get("MENU_INVALIDADDRESS") . " " .
+                                  UVmessage::get("MENU_INVALIDADDRESS2");
+        next;
+      }
+        
+      do {
+        print "\n", UVmessage::get("MENU_ADDRESS_PROMPT"), " ";
+        $voter_addr = <STDIN>;
+        chomp ($voter_addr);
+      } until ($voter_addr);
+      $$addr = $voter_addr;
+      push (@$set, 'Adresse');
+      delete $errors{SuspiciousAccount};
+      delete $errors{InvalidAddress};
+      check_ballotid(\%errors, \$voter_addr, $ballot_id, \%ids);
+
+    } elsif ($input eq '3') {
+      my $sel;
+      do {
+        print "[a] ", UVmessage::get("MENU_NAME_OK"), "\n",
+              "[b] ", UVmessage::get("MENU_NAME_CHANGE"), "\n",
+              "[c] ", UVmessage::get("MENU_NAME_INVALID"), "\n\n",
+              UVmessage::get("MENU_PROMPT");
+        $sel = <STDIN>;
+      } until ($sel =~ /^[abc]$/i);
+      if ($sel =~ /^a$/i) {
+        delete $errors{InvalidName};
+        next;
+      } elsif ($sel =~ /^c$/i) {
+        $errors{InvalidName} = UVmessage::get("MENU_INVALIDNAME");
+        next;
+      }
+      print UVmessage::get("MENU_NAME"), ": ";
+      $voter_name = <STDIN>;
+      chomp ($voter_name);
+      $$name = $voter_name;
+      push (@$set, 'Name');
+      delete $errors{NoName};
+      delete $errors{InvalidName};
+
+      $errors{InvalidName} = UVmessage::get("MENU_INVALIDNAME")
+        unless ($voter_name =~ /$config{name_re}/);
+    } elsif ($input eq '4') {
+      # set votes
+
+      my $sel;
+      do {
+        print "[a] ", UVmessage::get("MENU_VOTES_OK"), "\n",
+              "[b] ", UVmessage::get("MENU_VOTES_RESET"), "\n",
+              "[c] ", UVmessage::get("MENU_VOTES_INVALID"), "\n",
+              "[d] ", UVmessage::get("MENU_VOTES_CANCELLED"), "\n\n",
+              UVmessage::get("MENU_PROMPT");
+        $sel = <STDIN>;
+      } until ($sel =~ /^[abcd]$/i);
+      if ($sel =~ /^[ad]$/i) {
+        delete $errors{NoVote};
+        delete $errors{UnrecognizedVote};
+        delete $errors{ViolatedRule};
+        delete $errors{DuplicateVote};
+        if ($sel =~ /^d$/i) {
+          # cancelled vote: replace all votes with an A
+          @$votes = split(//, 'A' x scalar @groups);
+          push @$set, 'Stimmen';
+          # some errors are irrelevant when cancelling a vote:
+          delete $errors{InvalidName};
+          delete $errors{NoName};
+          delete $errors{InvalidBDSG};
+          delete $errors{InvalidAddress};
+          delete $errors{SuspiciousAccount};
+        }
+        next;
+      } elsif ($sel =~ /^c$/i) {
+        $errors{NoVote} = UVmessage::get("MENU_INVALIDVOTE");
+        next;
+      }
+
+      # Set columns for Text::Wrap
+      $columns = $config{rightmargin};
+      print "\n", wrap('', '', UVmessage::get("MENU_VOTES_REENTER_ASK")), "\n\n";
+      print UVmessage::get("MENU_VOTES_REENTER_LEGEND"), "\n";
+
+      for (my $n=0; $n<@groups; $n++) {
+        my $voteinput = "";
+        $votes->[$n] ||= 'E';
+
+        # repeat while invalid character entered
+        while (!($voteinput =~ /^[JNE]$/)) {
+          my $invalid = $#groups ? 0 : 1;
+          print UVmessage::get("MENU_VOTES_REENTER", (GROUP => $groups[$n]));
+          $voteinput = <STDIN>;
+          chomp $voteinput;
+          $voteinput ||= $votes->[$n];
+          $voteinput =~ tr/jne/JNE/;
+        }
+        
+        # valid input, save new votes
+        $newvotes[$n] = $voteinput;
+      } 
+
+      print "\n\n";
+      my $oldvotes = UVmessage::get("MENU_VOTES_REENTER_OLD");
+      my $newvotes = UVmessage::get("MENU_VOTES_REENTER_NEW");
+      my $oldlen = length($oldvotes);
+      my $newlen = length($newvotes);
+      my $maxlen = 1 + (($newlen>$oldlen) ? $newlen : $oldlen);
+      print $oldvotes, ' ' x ($maxlen - length($oldvotes)), @$votes, "\n",
+            $newvotes, ' ' x ($maxlen - length($newvotes)), @newvotes, "\n\n";
+
+      do {
+        print "[a] ", UVmessage::get("MENU_VOTES_REENTER_ACK"), "    ",
+              "[b] ", UVmessage::get("MENU_VOTES_REENTER_NACK"), "\n\n", 
+               UVmessage::get("MENU_PROMPT");
+        $sel = <STDIN>;
+      } until ($sel =~ /^[ab]$/i);
+
+      next if ($sel =~ /^b$/i);
+      @$votes = @newvotes;
+      push @$set, 'Stimmen';
+      delete $errors{UnrecognizedVote};
+      delete $errors{DuplicateVote};
+      delete $errors{NoVote};
+      delete $errors{ViolatedRule};
+
+      if (my $rule = UVrules::rule_check($votes)) {
+        $errors{ViolatedRule} = UVmessage::get("MENU_VIOLATEDRULE", (RULE => "#$rule"));
+      }
+
+    } elsif ($input eq '5' && $config{personal}) {
+      print "\n", UVmessage::get("MENU_BALLOT_ID"), ": ";
+      $$ballot_id = <STDIN>;
+      chomp ($$ballot_id);
+      push (@$set, 'Kennung');
+      check_ballotid(\%errors, \$voter_addr, $ballot_id, \%ids);
+
+    } elsif ($input eq '6' && $config{bdsg}) {
+      my $sel;
+      do {
+        print "[a] ", UVmessage::get("MENU_BDSG_ACCEPTED"), "\n",
+              "[b] ", UVmessage::get("MENU_BDSG_DECLINED"), "\n\n",
+              UVmessage::get("MENU_PROMPT");
+        $sel = <STDIN>;
+      } until ($sel =~ /^[ab]$/i);
+
+      if ($sel =~ /^a$/i) {
+        delete $errors{InvalidBDSG};
+      } else {
+        $errors{InvalidBDSG} = UVmessage::get("MENU_INVALIDBDSG");
+      }
+
+    } elsif ($input =~ /^i$/i) {
+      my $ignore = UVmessage::get("MENU_IGNORE_STRING");
+      # Set columns for Text::Wrap
+      $columns = $config{rightmargin};
+      print wrap('', '', UVmessage::get("MENU_IGNORE_WARNING",
+                                        (MENU_IGNORE_STRING => $ignore)
+                                       ));
+      if (<STDIN> eq "$ignore\n") {
+        print "\n";
+        return "i";
+      }
+
+    } elsif ($input =~ /^w$/i) {
+
+      if (keys %errors) {
+        if ((keys %errors)==1 && $errors{UnrecognizedVote}) {
+          # unrecognized vote lines aren't errors if votetaker
+          # did not change them
+          @$errors = ();
+        } else {
+          # Set columns for Text::Wrap
+          $columns = $config{rightmargin};
+          @$errors = keys %errors;
+          my $warning = ' ' . UVmessage::get("MENU_ERROR_WARNING") . ' ';
+          my $length = length($warning);
+          print "\n", '*' x (($config{rightmargin}-$length)/2), $warning,
+                '*' x (($config{rightmargin}-$length)/2), "\n\n",
+                wrap('', '', UVmessage::get("MENU_ERROR_TEXT")), "\n\n",
+                '*' x $config{rightmargin}, "\n\n",
+                UVmessage::get("MENU_ERROR_GETKEY");
+          my $input = <STDIN>;
+          next if ($input !~ /^y$/i);
+          print "\n";
+        }
+      } else {
+        @$errors = ();
+      }
+      system($config{clearcmd});
+      print "\n", UVmessage::get("MENU_PROCESSING"), "\n";
+      return "w";
+    }
+  }
+
+  sub check_ballotid {
+    my ($errors, $voter_addr, $ballot_id, $ids) = @_;
+
+    return 0 unless ($config{personal});
+
+    delete $errors->{NoBallotID};
+    delete $errors->{WrongBallotID};
+    delete $errors->{AddressNotRegistered};
+
+    if ($$ballot_id) {
+      if ($ids->{$$voter_addr}) {
+        if ($ids->{$$voter_addr} ne $$ballot_id) {
+          # ballot id incorrect
+          $errors->{WrongBallotID} = UVmessage::get("MENU_WRONGBALLOTID");
+        }
+      } else {
+        $errors->{AddressNotRegistered} = UVmessage::get("MENU_ADDRESSNOTREGISTERED");
+      } 
+    } else {
+      $errors->{NoBallotID} = UVmessage::get("MENU_NOBALLOTID");
+    }
+  }
+
+}
+
+
+##############################################################################
+# Menu for sorting out duplicate votings manually                            #
+# Parameters: References to hashes with the paragraphs from the result file  #
+#             and the default value                                          #
+# Return value: selected menu item (1, 2 or 0)                               #
+##############################################################################
+
+sub dup_choice {
+  my ($vote1, $vote2, $default) = @_;
+
+  print STDERR "\n", UVmessage::get("MENU_DUP_VOTE"), "\n\n";
+  print STDERR UVmessage::get("MENU_DUP_FIRST"), "\n";
+  print STDERR "A: $vote1->{A}\n";
+  print STDERR "N: $vote1->{N}\n";
+  print STDERR "D: $vote1->{D}\n";
+  print STDERR "K: $vote1->{K}\n";
+  print STDERR "S: $vote1->{S}\n\n";
+  print STDERR UVmessage::get("MENU_DUP_SECOND"), "\n";
+  print STDERR "A: $vote2->{A}\n";
+  print STDERR "N: $vote2->{N}\n";
+  print STDERR "D: $vote2->{D}\n";
+  print STDERR "K: $vote2->{K}\n";
+  print STDERR "S: $vote2->{S}\n\n";
+  print STDERR "1: ", UVmessage::get("MENU_DUP_DELFIRST"), "\n",
+               "2: ", UVmessage::get("MENU_DUP_DELSECOND"), "\n",
+               "0: ", UVmessage::get("MENU_DUP_DELNONE"), "\n\n";
+
+  my $input;
+
+  do {
+    print STDERR UVmessage::get("MENU_PROMPT"), "[$default] ";
+    $input = <STDIN>;
+    chomp $input;
+  } until ($input eq '' || ($input >= 0 && $input<3));
+
+  return $input || $default;
+}
+
+1;
diff --git a/UVmessage.pm b/UVmessage.pm
new file mode 100644 (file)
index 0000000..a52d8d5
--- /dev/null
@@ -0,0 +1,32 @@
+# UVmessages: parses resource strings and substitutes variables
+# Used by all components
+
+package UVmessage;
+
+use strict;
+use vars qw(@ISA @EXPORT_OK $VERSION);
+
+require Exporter;
+@ISA = qw(Exporter);
+@EXPORT_OK = qw(get);
+
+# Module version
+$VERSION = "0.1";
+
+sub get {
+  my ($key, %param) = @_;
+  my $string = $UVconfig::messages{$key} || return '';
+  while ($string =~ m/\$\{([A-Za-z0-9_-]+)\}/) {
+    my $skey = $1;
+    my $sval = $param{$skey};
+    $sval = '' unless defined($sval);
+    $string =~ s/\$\{$skey\}/$sval/g;
+  }
+  return $string;
+}
+1;
diff --git a/UVreadmail.pm b/UVreadmail.pm
new file mode 100644 (file)
index 0000000..e1e599c
--- /dev/null
@@ -0,0 +1,225 @@
+# UVreadmail: functions for reading and processing mailfiles
+# Used by uvvote.pl, uvcfv.pl, uvbounce.pl
+
+package UVreadmail;
+
+use strict;
+use UVconfig;
+use UVmessage;
+use MIME::QuotedPrint;
+use MIME::Base64;
+use MIME::Parser;
+use POSIX qw(strftime);
+
+use vars qw($VERSION);
+
+# Module version
+$VERSION = "0.11";
+
+sub process {
+
+  # $filename: file containing bounces or (if POP3 is enabled) where
+  #            mails should be saved
+  # $callsub:  reference to a sub which should be called for each mail
+  # $caller:   0 = uvvote.pl, 1 = uvcfv.pl, 2 = uvbounce.pl
+  #            3 = uvbounce.pl but POP3 disabled (overrides $config{pop3}
+  #
+
+  my ($filename, $callsub, $caller) = @_;
+  my ($voter_addr, $voter_name, $body);
+  my $count = 0;
+  my ($pop3server, $pop3user, $pop3pass, $pop3delete, $pop3uidlcache);
+  my @mails = ();
+  $caller ||= 0;
+
+  if ($config{pop3} && $caller<3) {
+
+    if ($caller == 1) {
+      # Ballot request (personal = 1 set in usevote.cfg) from uvcfv.pl
+      $pop3server = $config{pop3server_req} . ':' . $config{pop3port_req};
+      $pop3user = $config{pop3user_req};
+      $pop3pass = $config{pop3pass_req};
+      $pop3delete = $config{pop3delete_req};
+      $pop3uidlcache = $config{pop3uidlcache_req};
+    } elsif ($caller == 2) {
+      # called from uvbounce.pl
+      $pop3server = $config{pop3server_bounce} . ':' . $config{pop3port_bounce};
+      $pop3user = $config{pop3user_bounce};
+      $pop3pass = $config{pop3pass_bounce};
+      $pop3delete = $config{pop3delete_bounce};
+      $pop3uidlcache = $config{pop3uidlcache_bounce};
+    } else {
+      $pop3server = $config{pop3server} . ':' . $config{pop3port};
+      $pop3user = $config{pop3user};
+      $pop3pass = $config{pop3pass};
+      $pop3delete = $config{pop3delete};
+      $pop3uidlcache = $config{pop3uidlcache};
+    }
+
+    # read list of seen mails (UIDLs)
+    my %uidls = ();  # hash for quick searching
+    my @uidls = ();  # array to preserve order
+    my $cacheexist = 1;
+    open (UIDLCACHE, "<$pop3uidlcache") or $cacheexist = 0;
+    if ($cacheexist) {
+      while (my $uidl = <UIDLCACHE>) {
+        chomp ($uidl);
+        $uidls{$uidl} = 1;
+        push (@uidls, $uidl);
+      }     
+      close (UIDLCACHE);
+    }
+
+    print UVmessage::get("READMAIL_STATUS"), "\n" unless ($caller == 2);
+
+    # open POP3 connection and get new mails
+    use Net::POP3;
+    my $pop = Net::POP3->new($pop3server)
+      or die UVmessage::get("READMAIL_NOCONNECTION") . "\n\n";
+
+    my $mailcount = $pop->login($pop3user, $pop3pass);
+
+    die UVmessage::get("READMAIL_NOLOGIN") . "\n\n" unless ($mailcount);
+
+    for (my $n=1; $n<=$mailcount; $n++) {
+      my $uidl = $pop->uidl($n);
+      if ($uidl) {
+        next if ($uidls{$uidl});
+        $uidls{$uidl} = 1;
+        push (@uidls, $uidl);
+      }
+      my $mailref = $pop->get($n)
+         or print STDERR UVmessage::get("READMAIL_GET_PROBLEM", (NR => $n)) . "\n";
+      my $mail = join ('', @$mailref);
+      my $fromline = 'From ';
+      if ($mail =~ /From: .*?<(.+?)>/) {
+        $fromline .= $1;
+      } elsif ($mail =~ /From:\s+?(\S+?\@\S+?)\s/) {
+        $fromline .= $1;
+      } else {
+        $fromline .= 'foo@bar.invalid';
+      }
+      $fromline .= ' ' . strftime ('%a %b %d %H:%M:%S %Y', localtime) . "\n";
+      push (@mails, $fromline . $mail);
+      if ($pop3delete) {
+        $pop->delete($n)
+          or print STDERR UVmessage::get("READMAIL_DEL_PROBLEM", (NR => $n)) . "\n";
+      }
+    }
+
+    # save UIDLs
+    my $uidlerr = 0;
+    open (UIDLCACHE, ">$pop3uidlcache") or $uidlerr = 1;
+    if ($uidlerr) {
+      print STDERR UVmessage::get("READMAIL_UIDL_PROBLEM") . "\n";
+      print STDERR UVmessage::get("READMAIL_UIDL_PROBLEM2") . "\n";
+    } else {
+      print UIDLCACHE join("\n", @uidls);
+      close (UIDLCACHE) or print STDERR UVmessage::get("READMAIL_UIDL_CLOSE") . "\n";
+    }
+
+    # make archive of all mails
+    my $fileproblem = 0;
+    open (VOTES, ">$filename") or $fileproblem = 1;
+    if ($fileproblem) {
+      print STDERR UVmessage::get("READMAIL_ARCHIVE_PROBLEM",
+                   (FILE => $filename)) . "\n";
+    } else {
+      print VOTES join ("\n", @mails);
+      close (VOTES)
+        or print STDERR UVmessage::get("READMAIL_ARCHIVE_CLOSE",
+                        (FILE => $filename)) . "\n";
+    }
+
+    $pop->quit();
+
+  } else {
+    # open mail file
+    open(VOTES, "<$filename")
+        or die UVmessage::get("READMAIL_NOMAILFILE", (FILE => $filename)) . "\n\n";
+
+    # read all mails
+    my $i = 0;
+    while (<VOTES>) {
+      if (/$config{mailstart}/) {
+        $i++;
+      }
+      $mails[$i] = ($mails[$i] || "") . $_;
+    }
+
+    # close mail file
+    close(VOTES);
+  }
+
+  foreach my $mail (@mails) {
+    next unless $mail;
+
+    # split mail into array and remove first line (from line)
+    my @mail = split(/\n/, $mail);
+    shift (@mail) if ($mail[0] =~ /^From /);
+
+    # generate MIME-Parser object for the mail
+    my $parser = new MIME::Parser;
+    # headers are to be decoded
+    $parser->decode_headers(1);
+    # don't write into file
+    $parser->output_to_core(1);
+
+    # read mail
+    my $entity = $parser->parse_data(join("\n", @mail));
+    my $head = $entity->head;
+
+    # extract address and name
+    my $from = $head->get('From') || '';
+
+    if ($from =~ /\s*([^<]\S+\@\S+[^>]) \((.+)\)/) {
+      ($voter_addr, $voter_name) = ($1, $2);
+    } elsif ($from =~ /\s*\"?([^\"]+)\"?\s*<(\S+\@\S+)>/) {
+      ($voter_name, $voter_addr) = ($1, $2);
+      $voter_name =~ s/\s+$//;  # kill spaces at the end
+    } elsif ($from =~ /\s*<?(\S+\@[^\s>]+)>?[^\(\)]*/) {
+      ($voter_addr, $voter_name) = ($1, '');
+    } else {
+      # initialize with empty value
+      $voter_addr = '';
+      $voter_name = '';
+    }
+
+    # look at reply-to?
+    if ($config{replyto}) {
+
+      my $replyto = Mail::Field->new('Reply-To', $head->get('Reply-To'));
+
+      # Address in Reply-To?
+      ($voter_addr) = $replyto->addresses() if ($replyto->addresses());
+
+      # Name in reply-to?
+      if ($replyto->names()) {
+         my ($nametmp) = $replyto->names();
+         $voter_name = $nametmp unless ($nametmp =~ /^\s*$/);
+      }
+
+    }
+
+    # decode body
+    my $encoding = $head->get('Content-Transfer-Encoding') || '';
+    if ($encoding =~ /quoted-printable/i) {
+      $body = decode_qp($entity->stringify_body);
+    } elsif ($encoding =~ /base64/i) {
+      $body = decode_base64($entity->stringify_body);
+    } else {
+      $body = $entity->stringify_body;
+    }
+
+    my $h_date = $head->get('Date') || '';
+    chomp $h_date;
+
+    # call referred sub and increase counter
+    &$callsub($voter_addr, $voter_name, $h_date, $entity, \$body);
+    $count++;
+  } 
+
+  return $count;
+}
+
+1;
diff --git a/UVrules.pm b/UVrules.pm
new file mode 100644 (file)
index 0000000..76e260f
--- /dev/null
@@ -0,0 +1,409 @@
+# UVrules: Module with rule functions for usevote
+# Used by uvvote.pl, UVconfig.pm
+
+package UVrules;
+use strict;
+use vars qw (@ISA @EXPORT $VERSION @rules);
+use UVconfig;
+use UVmessage;
+require Exporter;
+@ISA = qw(Exporter);
+@EXPORT = qw(@rules);
+# Module version
+$VERSION = "0.3"; 
+
+# ---------------------------------------------------------------------
+# Erlaeuterung zur Regelpruefung (to be translated)
+# ---------------------------------------------------------------------
+# Um Stimmen mit multiplen Abstimmungspunkten auf ihre Sinnfaelligkeit 
+# pruefen zu koennen, koennen in Usevote verschiedenste Regeln
+# fuer solche Pruefungen definiert werden. 
+#
+# Die Regeln bestehen aus zwei Teilen. Einer IF-Klausel und einer THEN-
+# Klausel. Die IF-Klausel bestimmt, ob die Stimme mit der THEN-Klausel
+# verglichen werden soll. Passt sie auf diese, ist die Stimme in Ordnung,
+# wenn nicht liegt ein Fehler vor.
+#
+# Ein kleines Beispiel: "IF S.. THEN .SS"
+# Wenn beim ersten Punkt mit Ja oder Nein gestimmt wurde, dann muss
+# bei den anderen beiden Punkten auch ein Ja oder Nein vorliegen.
+#
+# Die Stimmabgabe JNE wuerde also gegen die obige Regel verstossen,
+# JJN nicht. EEJ wuerde ebenfalls gueltig sein, da die Regel nicht unter
+# die IF-Klausel faellt und somit keine Ueberpruefung der THEN-Klausel
+# erfolgt.
+#
+#
+# ---------------------------------------------------------------------
+# Implementierung
+# ---------------------------------------------------------------------
+# Um eine moeglichst einfache Ueberpruefung der Stimmen vorzunehmen,
+# bietet es sich an, aus den beiden Klauseln regulaere Ausdruecke zu
+# generieren. Diese wuerden dann auf die Stimme angewandt werden.
+# 
+# Bei der Umwandlung in regulaere Audruecke kommt uns die Notation
+# der Regeln bereits entgegen. So kann der Punkt als beliebige Stimme
+# beibehalten werden. Die grossen Buchstaben bleiben ebenfalls bis
+# auf S erhalten, da die zu pruefenden Stimmen aus den Buchstaben
+# 'JNE' bestehen.
+#
+# So muessen wir zur Ueberpruefung von direkten Stimmen nur 'S' in
+# eine Klasse mit [JN] und I in eine Klasse mit [EN] umwandeln.
+#
+# 'J..' => 'J..', 'NNE' => 'NNE', 'S..' => '[JN]..'
+#
+# Bei den indirekten Stimmabgaben wird es schon schwieriger. Hier 
+# muessten alle Moeglichkeiten eines Strings gebaut werden, um zu
+# testen ob mindestens eine Version matcht.
+#
+# '.jjj' => '.(j..|.j.|..j)
+#
+# Je komplexer die Regeln, um so mehr Moeglichkeiten muessten
+# konstruiert werden, um einen geschlossenen regulaeren Ausdruck
+# zu erhalten.
+#
+# Wir koennen den Regex aber auch einfach aufbauen, in dem wir
+# nicht alle Faelle betrachten die moeglich sind, sondern nur die
+# Faelle die nicht erlaubt sind. 
+# 
+# D.h. soll an einer Stelle ein Ja stehen, erlauben wir dort
+# nur Nein und Enthaltungen. Passt eine Stimme auf diesen Regex,
+# kann sie unmoeglich die Vorgabe enthalten.
+# 
+# 'nnnn' => '[JE][JE][JE][JE]'
+#
+# Besteht eine Stimme also nur aus Ja und Enthaltung, wissen wir
+# das kein einziges Nein enthalten seien kann. Die Stimme passt
+# also nicht auf unser Muster.
+#
+# Tritt hingegen nur ein einziges J auf, passt der regulaere Ausdruck
+# nicht mehr, und wir wissen, dass die Stimme die Regel erfuellt.
+#
+# Wie wir sehen koennen, ist der negative Ausdruck leichter zu
+# bilden als der positive. 
+#
+#
+# Da eine Stimme nun sowohl aus direkten, als auch indirekten
+# Stimmen bestehen kann (z.B. 'Jnnn..') muessen wir die Stimme
+# zerlegen. Wir bilden einen positiven Regex fuer die Grossbuch-
+# staben und einen negativen Regex fuer die kleinen.
+#
+# Passt eine Stimme dann auf den positiven Regex und nicht auf
+# den negativen Regex, so entspricht sie der urspruenglichen
+# Regel.
+#
+# Ein Beispiel: 'Sss..' (Der erste Punkt und der zweite oder dritte
+# Punkt muessen ein Ja oder Nein sein.)
+#
+# positiver Regex: '[JN]...'   muss erfuellt werden
+# negativer Regex: '.EE.'      darf nicht erfuellt werden
+#
+# JJNN => positiv matcht => negativ matcht nicht => Regel erfuellt
+# ENJE => positiv matcht nicht => Regel nicht erfuellt
+# NEEJ => positiv matcht => negativ matcht => Regel nicht erfuellt
+#
+#
+# Mit Hilfe dieser Technik, lassen sich einfach Regex bilden, die
+# ebenso einfach ueberprueft werden koennen.
+
+
+##############################################################################
+# Read usevote.rul and check rules for correct syntax                        #
+##############################################################################
+  
+sub read_rulefile {
+  @rules = ();
+
+  open (RULES, "<$config{rulefile}")
+    or die UVmessage::get("RULES_ERROPENFILE", (FILE => $config{rulefile})) . "\n\n";
+  while (<RULES>) {
+    chomp;
+    s/#.*$//;  # delete comments
+    # does line match correct if-then syntax?
+    if (/^\s*if\s+(\S+)\s+then\s+(\S+)\s*$/) {
+      my $if   = $1;
+      my $then = $2;
+
+      # $num contains the rule's array index
+      my $num = @rules;
+      # check for correct length of condition
+      my $errortext;
+      if (length($if) < @groups) {
+        $errortext = UVmessage::get("RULES_TOOSHORT", (NUM=>$num+1, TYPE=>"if"));
+      } elsif (length($if) > @groups) {
+        $errortext = UVmessage::get("RULES_TOOLONG", (NUM=>$num+1, TYPE=>"if"));
+      } elsif (length($then) < @groups) {
+        $errortext = UVmessage::get("RULES_TOOSHORT", (NUM=>$num+1, TYPE=>"then"));
+      } elsif (length($then) > @groups) {
+        $errortext = UVmessage::get("RULES_TOOLONG", (NUM=>$num+1, TYPE=>"then"));
+      }
+      die $errortext . ": $_\n\n" if ($errortext);
+      # check for correct characters in conditions
+      if ($if !~ /^[JjNnEeSsHhIi\.]+$/) {
+        die UVmessage::get ("RULES_INVCHARS", (NUM=>$num+1, TYPE=>"if")) . ": $if\n\n";
+
+      } elsif ($then !~ /^[JjNnEeSsHhIi\.]+$/) {
+        die UVmessage::get ("RULES_INVCHARS",
+                            (NUM=>$num+1, TYPE=>"if")) . ": $then\n\n";
+      }
+      # Zur Speicherung der Regeln (to be translated):
+      # - if_compl und then_compl sind die kompletten Bedingungen als Strings,
+      #   werden fuer die Sprachausgabe der Regeln benoetigt
+      # - zusaetzlich werden der if- und then-Teil fuer die einfachere
+      #   Verarbeitung in zwei Teile gesplittet: Eine Positiv-Regex, die auf
+      #   die Grossbuchstaben (explizite Forderungen, UND-Verknuepfungen)
+      #   matched, und eine Negativ-Regex, die bei den Kleinbuchstaben
+      #   (optionale Felder, ODER-Verknuepfungen) verwendet wird.
+
+      my %rule = ( if_compl   => $if,
+                   if_pos     => make_regex_pos($if),
+                   if_neg     => make_regex_neg($if),
+                   then_compl => $then,
+                   then_pos   => make_regex_pos($then),
+                   then_neg   => make_regex_neg($then) );
+      push (@rules, \%rule);
+
+    }
+  }
+}
+
+##############################################################################
+# Generates a RegEx for positive matching of the rules                       #
+#                                                                            #
+# All lower case characters are replaced with dots, as they are to be        #
+# matched by the negativ RegEx. Furthermore the symbol S is replaced by [JN] #
+# and I is replaced by [EN] (for use in combined votings when only one       #
+# option may be accepted and the others must be rejected or abstained.       #
+# As a result we have a regular expression that can be matched against the   #
+# received votes.                                                            #
+##############################################################################
+  
+sub make_regex_pos {
+  my $pat = $_[0];
+  $pat =~ s/[hijens]/./g;
+  $pat =~ s/S/[JN]/g;
+  $pat =~ s/H/[EJ]/g;
+  $pat =~ s/I/[EN]/g;
+  return $pat;
+}
+
+##############################################################################
+# Generates a RegEx for negative matching of the rules                       #
+#                                                                            #
+# All upper case characters are replaced with dots, as they are to be        #
+# matched by the positiv RegEx. If lower case characters are found the       #
+# condition is reversed, so that we are able to match votes *not*            #
+# corresponding to this rule                                                 #
+##############################################################################
+  
+sub make_regex_neg {
+  my $pat = $_[0];
+  # upper case characters are replaced with dots
+  # (are covered by make_regex_pos)
+  $pat =~ s/[HIJENS]/./g;
+  # reverse lower case characters
+  $pat =~ s/j/[NE]/g;
+  $pat =~ s/n/[JE]/g;
+  $pat =~ s/e/[JN]/g;
+  $pat =~ s/s/E/g;
+  $pat =~ s/h/N/g;
+  $pat =~ s/i/J/g;
+  # If the string contained only upper case characters they are now all
+  # replaced with dots and the RegEx would match everything, i.e. declare
+  # every vote as invalid. In this case an empty pattern is returned.
+  $pat =~ s/^\.+$//;
+  return $pat;
+}
+
+##############################################################################
+# Check a voting for rule compliance                                         #
+# Parameters: Votes (Reference to Array)                                     #
+# Return value: Number of violated rule or 0 (everything OK)                 #
+# (Internally rules are saved with indexes starting at 0)                    #
+##############################################################################
+
+sub rule_check {
+  my ($voteref) = @_;
+
+  # Turn array reference into a string
+  my $vote = join ('', @$voteref);
+
+  # For compliance with the rules every rule has to be matched against the
+  # the vote. If the IF clause matches but not the THEN clause the vote is
+  # invalid and the rule number is returned.
+
+  for (my $n = 0; $n < @rules; $n++) {
+    return $n+1 if ($vote =~ m/^$rules[$n]->{if_pos}$/ &&
+                    $vote !~ m/^$rules[$n]->{if_neg}$/ &&
+                not($vote =~ m/^$rules[$n]->{then_pos}$/ &&
+                    $vote !~ m/^$rules[$n]->{then_neg}$/ ));
+  }
+  return 0;
+} 
+
+
+##############################################################################
+# Print rules in human readable format                                       #
+# Parameter: rule number                                                     #
+# Return value: rule text                                                    #
+##############################################################################
+sub rule_print {
+  my ($n) = @_;
+
+  my $and = UVmessage::get ("RULES_AND");
+  my $or = UVmessage::get ("RULES_OR");
+  my $yes = UVmessage::get ("RULES_YES");
+  my $no = UVmessage::get ("RULES_NO");
+  my $abst = UVmessage::get ("RULES_ABSTAIN");
+
+  $n++;
+  my $text = UVmessage::get ("RULES_RULE") . " #$n:\n";
+  $text .= "  " . UVmessage::get ("RULES_IF") . "\n";
+  my @rule = split (//, $rules[$n-1]->{if_compl});
+  my $firstrun = 1;
+  my $fill = "";
+  for (my $i=0; $i<@rule; $i++) {
+    my $text1 = "";
+
+    if ($rule[$i] eq 'J') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'N') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$no, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'E') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'S') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE",
+                               (VOTE=>"$yes $or $no", GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'H') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE",
+                               (VOTE=>"$abst $or $yes", GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'I') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE",
+                               (VOTE=>"$abst $or $no", GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'j') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'n') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$no, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'e') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 's') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE",
+                               (VOTE=>"$yes $or $no", GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'h') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE",
+                               (VOTE=>"$abst $or $yes", GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'i') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_IFCLAUSE",
+                               (VOTE=>"$abst $or $no", GROUP=>$groups[$i]));
+    }
+    if ($text1) {
+      if ($firstrun) {
+        $text .= "    " . $text1 . "\n";
+        $firstrun = 0;
+      } else  {
+        $text .= $fill . $text1 . "\n";
+      }
+    }
+  }
+  @rule = split (//, $rules[$n-1]->{then_compl});
+  $text .= "  ..." . UVmessage::get ("RULES_THEN") . "\n";
+  $firstrun = 1;
+  for (my $i=0; $i<@rule; $i++) {
+    my $text1 = "";
+    if ($rule[$i] eq 'J') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'N') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$no, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'E') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'S') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE",
+                               (VOTE=>"$yes $or $no", GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'H') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE",
+                               (VOTE=>"$abst $or $yes", GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'I') {
+      $fill = "    $and ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE",
+                               (VOTE=>"$abst $or $no", GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'j') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'n') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$no, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'e') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 's') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE",
+                               (VOTE=>"$yes $or $no", GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'h') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE",
+                               (VOTE=>"$abst $or $yes", GROUP=>$groups[$i]));
+    } elsif ($rule[$i] eq 'i') {
+      $fill = "    $or ";
+      $text1 = UVmessage::get ("RULES_THENCLAUSE",
+                               (VOTE=>"$abst $or $no", GROUP=>$groups[$i]));
+    }
+    if ($text1) {
+      if ($firstrun) {
+        $text .= "    " . $text1 . "\n";
+        $firstrun = 0;
+      } else  {
+        $text .= $fill . $text1 . "\n";
+      }
+    }
+  }
+  return $text . "\n";
+}
+
+1;
diff --git a/UVsendmail.pm b/UVsendmail.pm
new file mode 100644 (file)
index 0000000..a773733
--- /dev/null
@@ -0,0 +1,336 @@
+# UVsendmail: functions for sending mails
+# Used by uvvote.pl, uvcfv.pl
+
+package UVsendmail;
+
+use strict;
+use UVconfig;
+use UVtemplate;
+use MIME::Words;
+use Text::Wrap qw(wrap $columns);
+
+# Set columns for Text::Wrap
+$columns = $config{rightmargin};
+
+use vars qw($VERSION);
+
+# Module version
+$VERSION = "0.9";
+
+my $num = 0;
+
+##############################################################################
+# generation of acknowledge and error mails (don't sends them out yet)       #
+# each mail is saved in a different file and a control file containing       #
+# filename and envelope-to address is generated.                             #
+# Parameters: mail address, fixed part of subject and body of mail (strings) #
+##############################################################################
+sub mail {
+  my ($addr, $subject, $text, $reference, $replyto) = @_;
+  # address set?
+  if ($addr) {
+    # generate mail to sender
+  
+    my $template = UVtemplate->new();
+    $template->setKey('from' => mimeencode($config{mailfrom}));
+    $template->setKey('subject' => mimeencode("$config{votename} - $subject"));
+    $template->setKey('address' => $addr);
+    $template->setKey('reference' => $reference) if ($reference);
+    $template->setKey('reply-to' => $replyto) if ($replyto);
+    $template->setKey('usevote-version' => $usevote_version);
+
+    my $message = $template->processTemplate($config{'tpl_mailheader'});
+    $message .= "\n" . $text;
+
+    # get envelope-to addresses
+    my $envaddr = $addr;
+    $envaddr .= " $config{mailcc}" if ($config{mailcc});
+
+    my $mailfile = '';
+
+    # search for file names
+    do {
+      $num++;
+      $mailfile = "$config{tmpdir}/ack.$num";
+    } while (-e $mailfile);
+    
+    # write mail in a file and append a line at the control file
+
+    open (CONTROL, ">>$config{controlfile}") or print STDERR "\n\n",
+      UVmessage::get("SENDMAIL_ERROPENCONTROL", (FILE => $config{controlfile})), "\n"; 
+    print CONTROL "$mailfile\t$envaddr\n";
+    close (CONTROL) or print STDERR "\n\n",
+      UVmessage::get("SENDMAIL_ERRCLOSECONTROL", (FILE => $config{controlfile})), "\n";
+
+    open (MAIL, ">$mailfile") or print STDERR "\n\n",
+      UVmessage::get("SENDMAIL_ERROPENMAIL", (FILE => $config{controlfile})), "\n";
+    print MAIL $message;
+    close (MAIL) or print STDERR "\n\n",
+      UVmessage::get("SENDMAIL_ERRCLOSEMAIL", (FILE => $config{controlfile})), "\n";
+
+  }
+}                                                                                      
+
+
+##############################################################################
+# Send previously generated acknowledge or error mails.                      #
+# Depending on configuration mails are piped to your MTA or send via SMTP.   #
+##############################################################################
+
+sub send {
+  unless (-e $config{controlfile}) {
+    print "\n", UVmessage::get("SENDMAIL_NOMAILS", (FILE => $config{controlfile})),
+          "\n\n";
+    return 0;
+  }
+
+  open (CONTROL, "<$config{controlfile}") or die "\n\n",
+    UVmessage::get("SENDMAIL_ERROPENCONTROL", (FILE => $config{controlfile})), "\n";
+  my @mailinfo = <CONTROL>;
+  close (CONTROL);
+
+  print UVmessage::get("SENDMAIL_SENDING"), "\n";
+
+  if ($config{smtp}) {
+    # send mails via SMTP
+    use Net::SMTP;
+    my $smtp = Net::SMTP->new("$config{smtpserver}:$config{smtpport}",
+                              Hello => $config{smtphelo});
+    die UVmessage::get("SENDMAIL_SMTP_CONNREFUSED") . "\n\n" unless ($smtp);
+    if ($config{smtpauth}) {
+      $smtp->auth($config{smtpuser}, $config{smtppass})
+        or die UVmessage::get("SENDMAIL_SMTP_CONNREFUSED") . "\n" .
+               $smtp->code() . ' ' . $smtp->message() . "\n";
+    }
+
+    my $errors = 0;
+    my $missingfiles = 0;
+
+    open (CONTROL, ">$config{controlfile}") or die  "\n\n",
+      UVmessage::get("SENDMAIL_ERROPENCONTROL", (FILE => $config{controlfile})), "\n";
+
+    foreach my $mail (@mailinfo) {
+
+      chomp ($mail);
+      next unless $mail;
+
+      my ($file, $envelope) = split(/\t/, $mail);
+      my $notfound = 0;
+      open (MAIL, "<$file") or $notfound = 1;
+      if ($notfound) {
+        print STDERR UVmessage::get("SENDMAIL_ERRNOTFOUND") . "\n";
+        $missingfiles++;
+        next;
+      }
+      my $message = join('', <MAIL>);
+      close (MAIL);
+
+      next unless $message;
+
+      $smtp->reset();
+      $smtp->mail($config{envelopefrom});
+      unless ($smtp->ok()) {
+        print STDERR UVmessage::get("SENDMAIL_SMTP_INVRCPT", (RCPT => $envelope)),
+                     "\n", $smtp->code(), ' ', $smtp->message(), "\n";
+        $errors++;
+        next;
+      }
+        
+      my $onesent = 0;
+      my $onefail = 0;
+      foreach my $addr (split(/ +/, $envelope)) {
+        $smtp->to($addr);
+        if ($smtp->ok()) {
+          $onesent = 1;
+        } else {
+          print CONTROL ($onefail ? " " : "$file\t");
+          print CONTROL $addr;
+          print STDERR UVmessage::get("SENDMAIL_SMTP_INVRCPT", (RCPT => $envelope)),
+                       "\n", $smtp->code(), ' ', $smtp->message(), "\n";
+          $errors++;
+          $onefail = 1;
+          next;
+        }
+      }
+
+      print CONTROL "\n" if ($onefail);
+      next unless $onesent;
+
+      $smtp->data();
+      if ($smtp->ok()) {
+        $smtp->datasend($message);
+        $smtp->dataend();
+      }
+      unless ($smtp->ok()) {
+        print STDERR UVmessage::get("SENDMAIL_SMTP_INVRCPT", (RCPT => $envelope)),
+                     "\n", $smtp->code(), ' ', $smtp->message(), "\n";
+        $errors++;
+        next; 
+      }
+      unlink ($file) unless ($onefail);
+    }
+
+    $smtp->quit();
+    close (CONTROL) or die "\n\n",
+      UVmessage::get("SENDMAIL_ERRCLOSECONTROL", (FILE => $config{controlfile})), "\n";
+
+    if ($errors) {
+      print STDERR "\n".wrap('', '', "$errors ".UVmessage::get("SENDMAIL_ERROCCURED"))."\n\n";
+    }
+
+    if ($missingfiles) {
+      print STDERR wrap('', '', "$missingfiles " .
+                   UVmessage::get("SENDMAIL_MISSINGFILES")), "\n\n";
+    }
+
+  } else {
+
+    foreach my $mail (@mailinfo) {
+      next unless $mail;
+      chomp($mail);
+      my ($file, @rcpt) = split(/\s+/, $mail);
+      open (DOMAIL, ">>$config{domailfile}");
+      print DOMAIL "$config{mailcmd} ";
+      foreach my $rcpt (@rcpt) {
+        print DOMAIL "'$rcpt' ";
+      }
+      print DOMAIL "<$file && rm $file ; $config{sleepcmd}\n";
+      close (DOMAIL)
+        or print STDERR "\n\n", UVmessage::get("SENDMAIL_ERRCLOSEDOMAIL"), "\n";
+    }
+    chmod(0700, $config{domailfile});
+    system($config{domailfile});
+
+  }
+
+  opendir (DIR, $config{tmpdir});
+  my @files = grep (/^ack\.\d+/, readdir (DIR));
+  closedir (DIR);
+  return 0 if (@files);
+
+  unlink $config{controlfile} or print STDERR "\n\n",
+    UVmessage::get("SENDMAIL_ERRDELCONTROL", (FILE => $config{controlfile})), "\n";
+
+  unless ($config{smtp}) {
+    unlink $config{domailfile} or print STDERR "\n\n",
+      UVmessage::get("SENDMAIL_ERRDELCONTROL", (FILE => $config{domailfile})), "\n";
+  }
+
+}
+
+
+##############################################################################
+# Encodes a string for use in mail headers                                   #
+#                                                                            #
+# Parameters: $text = string to encode.                                      #
+# Returns:  $newtext = encoded string.                                       #
+##############################################################################
+sub mimeencode {
+  my ($text) = @_;
+  my @words = split(/ /, $text);
+  my $line = '';
+  my @lines;
+  foreach my $word (@words) {
+    my $sameword = 0;
+    $word =~ s/\n//g;
+    my $encword;
+    if ($word =~ /[\x7F-\xFF]/) {
+      $encword = MIME::Words::encode_mimeword($word, 'Q', 'ISO-8859-1');
+    } elsif (length($word) > 75) {
+      $encword = MIME::Words::encode_mimeword($word, 'Q', 'us-ascii');
+    } else {
+      $encword = $word;
+    }
+    # no more than 75 chars per line allowed
+    if (length($encword) > 75) {
+      while ($encword) {
+        if ($encword =~ /(^=\?[-\w]+\?\w\?)(.{55}.*?)((=.{2}|[^=]{3}).*\?=)$/) {
+          addword($1 . $2 . '?=', \$line, \@lines, $sameword);
+          $encword = $1 . $3;
+        } else {
+          addword($encword, \$line, \@lines, $sameword);
+          $encword = '';
+        }
+        $sameword = 1;
+      }
+    } else {
+      addword($encword, \$line, \@lines, $sameword);
+    }
+  }
+  my $delim = (@lines) ? ' ' : '';
+  push(@lines, $delim . $line) if ($line);
+  return join('', @lines);
+}
+
+##############################################################################
+# Adds a word to a MIME encoded string, inserts linefeed if necessary        #
+#                                                                            #
+# Parameters:                                                                #
+#   $word = word to add                                                      #
+#   $line = current line                                                     #
+#   $lines = complete text (without current line)                            #
+#   $sameword = boolean switch, indicates that this is another part of       #
+#               the last word (for encoded words > 75 chars)                 #
+##############################################################################
+sub addword {
+  my ($word, $line, $lines, $sameword) = @_;
+  # If the passed fragment is a new word (and not another part of the
+  # previous): Check if it is MIME encoded
+  if (!$sameword && $word =~ /^(=\?[^\?]+?\?[QqBb]\?)(.+\?=[^\?]*)$/) {
+    # Word is encoded, save without the MIME header
+    # (e.g. "t=E4st?=" instead of "?iso-8859-1?q?t=E4st?=")
+    my $charset = $1;
+    my $newword = $2;
+
+    if ($$line =~ /^(=\?[^\?]+\?[QqBb]\?)(.+)\?=$/) {
+      # Previous word was encoded, too:
+      # Delete the trailing "?=" and insert an underline character (=space)
+      # (space between to encoded words is ignored)
+      if ($1 eq $charset) {
+        if (length($1.$2)+length($newword)>75) {
+          my $delim = (@$lines) ? ' ' : '';
+          push(@$lines, "$delim$1$2_?=\n");
+          $$line = $word;
+        } else {
+          $$line = $1 . $2 . '_' . $newword;
+        }
+      } else {
+        if (length("$$line $word")>75) {
+          my $delim = (@$lines) ? ' ' : '';
+         push(@$lines, "$delim$1$2_?=\n");
+          $$line = $word;
+       } else {
+          $$line = "$1$2_?= $word";
+       }
+      }
+      return 0;
+    }
+  }
+
+  # New word is not encoded: simply append it, but check for line length
+  # and add a newline if necessary
+  if (length($$line) > 0) {
+    if (length($$line) + length($word) >= 75) {
+      my $delim = (@$lines) ? ' ' : '';
+      push(@$lines, "$delim$$line\n");
+      $$line = $word;
+    } else {
+      $$line .= " $word";
+    }
+  } else {
+    # line is empty
+    $$line = $word;
+  }
+}
+
+1;
diff --git a/UVtemplate.pm b/UVtemplate.pm
new file mode 100644 (file)
index 0000000..35f28db
--- /dev/null
@@ -0,0 +1,810 @@
+#----------------------------------------------------------------------
+package UVtemplate;
+#----------------------------------------------------------------------
+
+=head1 NAME
+
+UVtemplate - Templateverarbeitung und String-Formatierungen
+
+=head1 SYNOPSIS
+
+  use UVtemplate;
+
+  $plate  = UVtemplate->new([%keys]);
+
+            $plate->setKey(%keys);
+  $item   = $plate->addListItem($name, %keys);
+
+  $string = $plate->processTemplate($file);
+
+=head1 DESCRIPTION
+
+Mit Hilfe von UVtemplate, wird die komplette Aufbereitung und 
+Formatierung der Programmausgaben nicht nur ausgelagert sondern
+auch so flexibiliert, dass sie jederzeit angepasst werden kann,
+ohne das im eigentlichen Programmcode veraendert werden muss.
+
+Auf Programmseite wird eine Datenstruktur mit Schluessel-Wert
+Paaren erzeugt. In den Template-Dateien werden dann spaeter die
+jeweiligen Schluessel, durch ihre im Programm festgelegten 
+Werte ersetzt. Zusaetzlich ist es moeglich Schluessel zu Listen
+zusammenzufassen.
+
+Da es sich bei den Templates um Ascii-Texte handelt, gibt es 
+zusaetzlich die Moeglichkeit die Werte der Schluessel zuformatieren
+um eine einheitliche Ausgabe zu ermoeglichen. D.h. es kann z.B. durch 
+das Anhaengen von Leerzeichen dafuer gesorgt werden, das ein Schluessel
+einer Liste immer 60 Zeichen lang ist um ansehnliche Tabellen auszugeben.
+
+=head1 FUNCTIONS
+
+=over 3
+
+=cut
+
+#----------------------------------------------------------------------
+
+use strict;
+use vars qw( $VERSION $functions @dirs);
+use UVconfig;
+
+$VERSION = 0.1;
+
+#----------------------------------------------------------------------
+
+=item new
+
+Eine neues Objekt vom Typ UVtemplate anlegen. 
+
+  my $plate = UVtemplate->new();
+
+Als Parameter koennen gleich beliebig viele Schluessel-Wert-Paare
+uebergeben werden.
+
+=cut
+
+sub new{
+  my $class = shift;
+  my $self  = {};
+
+  if (ref($class)){
+    $self->{FATHER} = $class;
+    bless($self, ref($class));
+
+  }else{
+    bless($self, $class);
+  }
+
+  $self->setKey(@_);
+
+  return $self;
+}
+
+=item setKey
+
+Schluessel und zugehoerige Werte im Objekt speichern.
+
+  $plate->setKey( vote-addr => 'to-vote@dom.ain' );
+  $plate->setKey( datenschutz => 1);
+
+Ist der zu speichernde Schluessel bereits vorhanden, wird er
+durch den neuen Wert ueberschrieben.
+
+=cut
+
+sub setKey{
+  my $self = shift;
+  my %param = @_;
+
+  foreach my $key (keys(%param)){
+    $self->{KEYS}->{$key} = $param{$key};
+  }
+}
+
+=item addListItem
+
+Erzeugt ein neues Objekt vom Typ UVtemplate und fuegt es der 
+angebenen Liste hinzu.
+
+  $plate->addListItem(name => 'Musterman', email => 'em@il');
+
+Da sich Listen wie normale Schluessel-Wert Paare verhalten, 
+wird die Liste als Array ueber UVtemplate-Objekte unter dem
+definiertem Schluessel abgelegt. Ist dieser Schluessel bereits
+gesetzt und enthaehlt keinen Array, so bricht die Funktion ab.
+
+=cut
+
+sub addListItem{
+  my $self = shift;
+  my $list = shift;
+
+  # pruefen ob key angegeben ist und falls key vorhanden
+  # eine liste vorliegt
+  return unless ($list && (not($self->{KEYS}->{$list}) ||
+    UNIVERSAL::isa($self->{KEYS}->{$list}, 'ARRAY')));
+
+  # neues Element erzeugen
+  my $new = $self->new( @_ );
+
+  # an listen anhaengen
+  push(@{$self->{KEYS}->{$list}}, $new);
+
+  # referenz zurueckgeben
+  return $new;
+}
+
+=item getKey($key)
+
+Den Wert eines Schluessel ermitteln.
+
+  my $value = $plate->getKey('email');
+
+Ist der Wert im Objekt nicht gesetzt, wird - falls es sich um ein
+Element einer Liste handelt - rekursiv beim Vater weiter gesucht.
+
+So stehen allen Kindern auch die Schluessel-Wert Paare ihrer Eltern
+zur Verfuegung.
+
+Zum Schluss wird noch geprueft, ob der Schluessel in usevote.cfg
+gesetzt wurde. Dadurch sind alle Konfigurationsoptionen direkt
+in Templates nutzbar.
+
+=cut
+
+sub getKey{
+  my $self = shift;
+  my $key  = $_[0];
+
+  my $value;
+
+  do{
+    $value = $self->{KEYS}->{$key};
+    $self  = $self->{FATHER};
+
+  }while(!defined($value) && $self);
+
+  if (!defined($value) && defined($config{$key})) {
+    $value = $config{$key};
+  }
+
+  return $value;
+}
+
+#----------------------------------------------------------------------
+
+sub getRules{
+  my $self = shift;
+
+  do{
+    return $self->{RULES} if ($self->{RULES});
+    $self = $self->{FATHER};
+  }while($self);
+
+  return;
+}
+
+=item getConvKey{
+
+Einen Format ermitteln.
+
+  my $value = $plate->getConvKey('email-adresse');
+
+Diese Funktion ueberprueft ob eine Formatierung mit den entsprechenden
+Schluessel definiert ist und ruft dementsprechend die dort definierten
+Funktionen ueber der Datenstruktur auf.
+
+Ist kein solches Format definiert, wird der Wert des Schluessel mit
+einem solchen Namen zurueckgegeben. (Es wird intern getKey aufgerufen).
+
+=cut
+
+sub getConvKey{
+  my $self = shift;
+  my $key  = $_[0] || return;
+
+  my $rules = $self->getRules();
+  my $value = $self->getKey($key);
+  
+  $value = '' unless (defined($value));
+
+  if ($rules && ($rules->{$key})){
+    my @funcs = @{$rules->{$key}};
+
+    foreach my $func (@funcs){
+      my ($name, @params) = @$func;
+  
+      if ($functions->{$name}){
+        $value = $functions->{$name}->($self, $value, @params);
+    
+      }else{
+        print STDERR "format function '$name' not found!\n";
+      }
+    }
+  }
+
+  return $value;
+}
+
+#----------------------------------------------------------------------
+
+=item processTemplate
+
+Daten des Objekts in ein Template einfuegen.
+
+  my $string = $plate->processTemplate('template/info.txt');
+
+Die angebene Datei wird eingelesen, zerlegt und danach
+die enstprechenden Platzhalter durch die (formatierten) 
+Werte aus der Datenstruktur ersetzt.
+
+=cut
+
+sub processTemplate{
+  my $self = shift;
+  my $file = $_[0] || return;
+
+  my ($rules, $body) = _split_file($file);
+
+  # konvertierungsregeln parsen
+  $self->{RULES} = _parse_rules($rules);
+
+  # template zerlegen (zuerst fuehrende leerzeilen entfernen!)
+  $body =~ s/^\n+//s;  
+  my $token = UVtemplate::scan->new(string => $body);
+
+  # daten einsetzen
+  return $token->processData($self);
+}
+
+sub _split_file{
+  my $file = $_[0] || return;
+
+  my $fname = _complete_filename($file);
+
+  unless ($fname){
+    print STDERR "couldnt find '$file'\n";
+    return;
+  }
+
+  my (@rules, @body);
+
+  open(PLATE, $fname);
+  my @lines = <PLATE>;
+  close(PLATE);
+
+  my $body = 0;
+
+  foreach my $line (@lines){
+    if ($line =~ m/^== TEMPLATE/){
+      $body = 1;
+
+    }else{
+      if ($body){
+        push(@body, $line);
+
+      }else{
+        push(@rules, $line);
+      }
+    }
+  }
+
+  # falls kein Separator definiert war, wird der komplette Text
+  # als Body interpretiert. Es gibt keine Regeln!
+  
+  unless ($body){
+    @body  = @rules;
+    @rules = ();
+  }
+
+  # und nun wieder zu Strings zusammenpappen
+  return (join('', @rules), join('', @body));
+}
+
+sub _complete_filename{
+  my $file = $_[0] || return;
+
+  my $dirs = $UVconfig::config{templatedir};
+     @dirs = split(/\s*,\s*/, $dirs) if $dirs;
+
+  my $fname;
+
+  foreach my $dir (@dirs, '.'){
+    $fname = "$dir/$file";
+    return $fname if (-r $fname);
+  }
+}
+
+#----------------------------------------------------------------------
+# Konvertierungs-Regeln
+#----------------------------------------------------------------------
+
+sub _parse_rules{
+  my $string = $_[0] || return;
+
+  my @stack;
+  my $rules = {};
+
+  my $this = [];
+    
+  while (length($string) > 0){
+    _strip_chars(\$string);
+
+    my $token = _parse_token(\$string);
+
+    if ($token){
+      push(@stack, $token);
+      
+      _strip_chars(\$string);
+
+      if ($string =~ s/^:=//){
+        # neuen Schluessel vom Stack holen
+        my $key = pop(@stack);
+       
+       # restlichen Stack auf alten Schluessel packen
+       push(@$this, [ @stack ]);
+       @stack = ();
+
+       # neuen Schluessel anlegen
+       $rules->{$key} = $this = [];
+
+      }elsif($string =~ s/^\|//){
+        # stack auf schluessel packen
+       push(@$this, [ @stack ]);
+       @stack = ();
+      }
+
+    }else{
+      # fehlermeldung ausgeben (nacharbeiten!)
+      print STDERR "Syntaxerror in Definition\n";
+      return;
+    }
+  }
+
+  # den Rest vom Stack abarbeiten
+  push(@$this, [ @stack ]) if @stack;
+
+  return $rules;
+}
+
+sub _strip_chars{
+  my $line = $_[0] || return;
+
+  # führenden whitespace entfernen
+  $$line =~ s/^\s+//;
+
+  # kommentare bis zum nächsten Zeilenumbruch entfernen
+  $$line =~ s/^#.*$//m;
+}
+
+
+sub _parse_token{
+  my $string = shift;
+
+  if ($$string =~ s/^(["'])//){
+    return _parse_string($string, $1);
+
+  }else{
+    return _parse_ident($string);
+  }
+}
+
+
+sub _parse_string{
+  my ($string, $limit) = @_;
+
+  my $value;
+
+  while ($$string){
+    if ($$string =~ s/^$limit//){
+      $$string =~ s/^\s*//;
+      return $value;
+
+    }elsif($$string =~ s/^\\(.)//){
+      $value .= $1;
+    
+    }else{
+      $$string =~ s/^[^$limit\\]*//;
+      $value .= $&;
+    }
+  }
+
+  # end of line 
+  return $value;
+}
+
+
+sub _parse_ident{
+  my $string = shift;
+
+  if ($$string =~ s/^([A-Za-z0-9-]+)\s*//){
+    return $1;
+  }
+
+  return;
+}
+
+#----------------------------------------------------------------------
+
+BEGIN{
+  $functions = \%UVconfig::functions;
+}
+
+#----------------------------------------------------------------------
+#----------------------------------------------------------------------
+package UVtemplate::scan;
+#----------------------------------------------------------------------
+#----------------------------------------------------------------------
+
+sub new{
+  my $class = shift;
+  my %param = @_;
+
+  my $self  = {};
+  bless($self, $class);
+
+  $self->parseFile($param{file})     if defined($param{file});
+  $self->parseString($param{string}) if defined($param{string});
+
+  return $self;
+}
+
+#----------------------------------------------------------------------
+
+sub processData{
+  my $self = shift;
+  my $data = $_[0];
+
+  return _process_data($self->{toks}, $data);
+}
+
+sub _process_data{
+  my ($toref, $data) = @_;
+  
+  my $string = '';
+  my $length = 0;
+  my $empty  = 0;
+
+  foreach my $token (@$toref){
+    if (ref($token)){
+      my $before = length($string);
+         $empty  = 0;
+    
+      if ($token->[0] eq 'VAR'){
+        my $value = $data->getConvKey(_process_data($token->[1], $data));
+      
+        if (defined($value) && length($value)){
+          $string .= $value;
+
+       }else{
+         $string .= _process_data($token->[2], $data);
+       }
+
+      }elsif($token->[0] eq 'IF'){
+        if ($data->getConvKey(_process_data($token->[1], $data))){
+          $string .= _process_data($token->[2], $data);
+
+       }else{
+          $string .= _process_data($token->[3], $data);
+       }
+     
+      }elsif($token->[0] eq 'LOOP'){
+        my $nodes = $data->getConvKey(_process_data($token->[1], $data));
+       my @block;
+
+        if ($nodes && (UNIVERSAL::isa($nodes, 'ARRAY'))){
+         foreach my $node (@$nodes){
+            push(@block, _process_data($token->[2], $node));
+         }
+
+         $string .= join(_process_data($token->[3], $data), @block);
+       }
+      }
+
+      $length = length($string);
+      $empty  = 1 if ($before == $length);
+
+    }else{
+      if ($empty && ($string =~ m/(\n|^)$/s)){
+        $empty = 0;            # Falls die letzte Zeile nur aus einem Token
+        $token =~ s/^\n//s;    # ohne Inhalt bestand, wird die Zeile entfernt
+      }
+    
+      $string .= $token;
+    }
+  }
+
+  return $string;
+}
+
+#----------------------------------------------------------------------
+# Den String in einen Syntaxbaum abbilden
+
+sub _parse_token_string{
+  my $self   = shift;
+  my ($string, $intern) = @_;
+
+  my (@token, $toref); 
+  my $data = '';
+
+  while ($string){
+    if ($intern && $string =~ m/^(\]|\|)/){
+      last;
+  
+    }elsif ($string =~ s/^\[//){
+      my $orig = $string;
+    
+      ($toref, $string) = $self->_parse_token($string);
+
+      if (@$toref){
+        push (@token, $data) if $data;
+       $data = '';
+
+        push(@token, $toref) 
+      }
+
+      if ($string !~ s/^\]//){
+        my $pos = $self->{lines} - _count_lines($orig) + 1;
+
+        print STDERR "Scanner: [$pos] missing right bracket\n";
+       return (\@token, $string);
+      }
+      
+    }elsif($string =~ s/^\\n//s){
+      $data .= "\n";
+
+    }elsif($string =~ s/^\\(.)//s){
+      $data .= $1;
+
+    }elsif($intern){
+      $string =~ s/^([^\]\[\|\\]+)//s;
+      $data  .= $1;
+    
+    }else{
+      $string =~ s/^([^\[\\]+)//s;
+      $data  .= $1;
+    } 
+  }
+
+  push (@token, $data) if length($data);
+  return (\@token, $string)
+}
+
+
+sub _parse_token{
+  my $self   = shift; 
+  my $string = $_[0];
+
+  my @token = ();
+  
+  if ($string =~ s/^\$//s){ 
+    # Variablen - Syntax: [$key[|<else>]] 
+    push (@token, 'VAR');
+
+  }elsif ($string =~ s/^\?//s){
+    # Bedingung - Syntax: [?if|<then>[|<else>]]
+    push (@token, 'IF');
+
+  }elsif ($string =~ s/^\@//s){
+    # Schleifen - Syntax: [@key|<block>[|<sep>]]
+    push (@token, 'LOOP');
+
+  }elsif ($string =~ s/^#//s){
+    # Kommentare - Syntax: [# ... ]
+    $string = _parse_comment($string);
+
+    return (\@token, $string);
+    
+  }else{
+    print STDERR "unknown token in template\n";
+  }
+
+  my $toref;
+
+  ($toref, $string) = $self->_parse_token_string($string, 1);
+  push(@token, $toref);
+
+  while ($string =~ s/^\|//){
+    ($toref, $string) = $self->_parse_token_string($string, 1);
+    push(@token, $toref);
+  }
+
+  return (\@token, $string);
+}
+
+
+sub _parse_comment{
+  my $string = $_[0];
+  my $count  = 1;
+
+  while($string && $count) {
+    $string =~ s/^[^\[\]\\]+//s; # alles außer Klammern und Backslash wegwerfen
+    $string =~ s/^\\.//;       # alles gesperrte löschen
+
+    $count++ if $string =~ s/^\[//;
+    $count-- if $string =~ s/^\]//;
+  }
+
+  $string = ']'.$string if !$count;
+  return $string;
+}
+
+#----------------------------------------------------------------------
+
+sub parseString{
+  my $self = shift;
+  my $text = $_[0];  
+
+  $self->{lines} = _count_lines($text);
+  my ($toref, $rest) = $self->_parse_token_string($text);
+
+  $self->{toks} = $toref;
+}
+
+
+sub _count_lines{
+  return 0 unless defined($_[0]);
+
+  my ($string, $count) = ($_[0], 1);
+  $count++ while($string =~ m/\n/sg);
+
+  return $count;
+}
+
+#----------------------------------------------------------------------
+#----------------------------------------------------------------------
+#----------------------------------------------------------------------
+
+1;
+
+=back
+
+=head1 SYNTAX
+
+Eine Templatedatei besteht aus zwei Teilen. Am Anfang werden die 
+Formatierungen bestimmter Schluessel definiert und nach einem
+Trenner folgt der eigentlich Template-Koerper, der dann von Programm
+bearbeitet und ausgegeben wird.
+
+  format-key := function1 param | function2 param
+
+  == TEMPLATE ====================================
+
+  Ich bin nun das eigentliche Template:
+
+  format-key: [$format-key]
+
+Der Trenner beginnt mit den Zeichen '== TEMPLATE' danach koennen
+beliebige Zeichen folgen um die beiden Sektionen optisch voneinander 
+abzugrenzen.
+
+Wenn es keine Formatierungsanweisungen gibt, kann der Trenner auch
+weggelassen werden. D.h. wenn kein Trenner gefunden wird, wird der
+komplette Text als Template-Koerper betrachtet.
+
+=head2 Template-Koerper
+
+Im Template-Koerper werden die zu ersetzenden Token durch eckige
+Klammern abgegrenzt. Sollen eckige Klammern im Text ausgegeben werden
+muessen diese durch einen Backslash freigestellt werden.
+
+  [$termersetzung] [@schleife] nur eine \[ Klammer
+
+=over 3
+
+=item $ - Termersetzung 
+
+Ersetzt den Token durch den Wert des angegeben Schluessels.
+
+  [$formatierung] [$schluessel]
+
+Es wird zuerst nach einer Formatierung mit den entsprechenden
+Bezeichner gesucht. Ist dies der Fall werden die entsprechenden
+Funktionen ausgefuehrt.
+
+Kann kein Format gefunden, wird direkt in der Datenstruktur
+nach einem Schhluessel mit dem angegeben Bezeichner gesucht
+und sein Wert eingesetzt.
+
+Schlussendlich ist es noch moeglich einen default-Wert zu
+definieren, der eingesetzt wird, wenn keiner der obigen Wege
+erfolgreich war.
+
+  Hallo [$name|Unbekannter]!
+
+=item ? - bedingte Verzeigung
+
+Ueberprueft ob der Wert des angegebenen Formats/Schluessel
+boolsch WAHR ist. Dementsprechend wird der then oder else
+Block eingefuegt.
+
+  [?if|then|else] oder auch nur [?if|then]
+
+Die then/else Bloecke werden natuerlich auch auf Tokens
+geparst und diese dementsprechend ersetzt.
+
+=item @ - Schleifen/Listen
+
+Der nachfolgende Textblock wird fuer alle Elemente des durch
+den Schluessel bezeichneten Arrays ausgefuehrt und eingefuegt.
+
+  [@schluessel|block] oder [@schluessel|block|sep]
+
+Als zweiter Parameter kann ein Separtor definiert werden, mit
+dem sich z.B. kommaseparierte Listen erzeugen lassen, da der
+Separator eben nur zwischen den Element eingefuegt wird.
+
+Auch fuer Schleifen koennen Formatierungen genutzt werden.
+Allerdings darf kein String zurueckgegeben werden, sondern
+ein Array mit einer Menge von UVtemplate-Objekten.
+
+=item # - Kommentare
+
+Token die nach der Bearbeitungen entfernt werden.
+
+  [# mich sieht man nicht]
+
+=item Sonstiges
+
+Um in Listen einen Zeilenumbruch zu erzwingen, muss 
+lediglich ein '\n' eingefuegt werden, falls eine kompakte
+Definition der Liste erfolgen soll.
+
+  [@names|[name] [email]\n]
+
+=back
+
+=head2 Formatierungen
+
+Eine Formatierung besteht eigentlich nur aus dem entsprechenden
+Namen und einer beliebigen Anzahl von Funktionsaufrufen:
+
+  format := funktion param1 "param 2" | funktion param
+
+Aehnlich der Unix-Shell-Funktionalitaet, wird dabei die Ausgabe
+einer Funktion an die folgende weitergeleitet. So ist es moeglich
+verschiedenste simple Formatierungen zu kombinieren um nicht fuer
+jeden Spezialfall eine neue Funktion schreiben zu muessen.
+
+Die jeweilige Formatierungsfunktion erhaelt als Input die Datenstruktur,
+den Output der vorherigen Funktion und die definierten Parameter in der
+entsprechenden Reihenfolge.
+
+Zahlen und einfache Bezeichner koennen direkt definiert werden. Sollen
+Sonderzeichen oder Leerzeichen uebergeben werden muessen diese gequotet
+werden. Dazu kann ' also auch " verwendet werden.
+
+Die Funktionen geben im Allgemeinen einen String zurueck. Im Rahmen
+von Listen können auch Arrays uebergeben werden.
+
+Die erste Funktion duerfte ueblicherweise 'value' sein. Sie gibt den
+des angegeben Schluessel zurueck, der dann von den folgenden Funktionen
+definiert wird.
+
+  name-60 := value name | fill-right 60
+
+Das Format "name-60" definiert also den Wert des Schluessel "name" der
+um Leerzeichen aufgefuellt wird, bis eine Laenge von 60 Zeichen 
+erreicht wird.
+
+  name-email := value name | justify-behind mail 72
+
+"name-email" resultiert in einem String, der zwischen den Werten
+von "name" und "email" genau so viele Leerzeichen enthaelt, damit
+der gesamte String 72 Zeichen lang ist.
+
+Wird dieses Format in einer Liste angewandt, erhaelt man eine Tabelle
+in der die linke Spalte linksbuendig und die rechte Spalte entsprechend
+rechtsbuendig ist.
+
+Soweit ein kleiner Ueberblick ueber die Formatierungen. 
+Ausfuehrliche Funktionsbeschreibungen und weitere Beispiele finden
+sich in der Dokumentation des Moduls UVformat.
+
+=head1 SEE ALSO
+
+L<UVformats>
+
+=head1 AUTHOR
+
+Cornell Binder <cobi@dex.de>
diff --git a/bdsgtext.cfg b/bdsgtext.cfg
new file mode 100644 (file)
index 0000000..28bc3b5
--- /dev/null
@@ -0,0 +1,13 @@
+# Diese Datei enthaelt den Hinweistext auf die Datenschutzklausel,
+# der im Wahlschein erscheint und auch bei eingegangenen Wahlscheinen
+# wieder abgeprueft wird. Alle Zeilen, die mit dem Kommentarzeichen #
+# anfangen, werden ignoriert.
+#
+Zur Verarbeitung des Wahlscheines und inbesondere der Veroeffentlichung
+des Ergebnisses ist deine Zustimmung zur Speicherung, Auswertung und
+Veroeffentlichung deiner Stimmdaten (Name und E-Mail-Adresse in
+Verbindung mit dem Stimmverhalten) im Rahmen dieses Verfahrens
+erforderlich. Wenn du im Feld unterhalb dieses Absatzes "JA"
+eintraegst, erklaerst du dich damit einverstanden. In allen anderen
+Faellen wird der Wahlschein mit Ruecksicht auf das deutsche
+Bundesdatenschutzgesetz verworfen und nicht gewertet.
diff --git a/mailpatterns.cfg b/mailpatterns.cfg
new file mode 100644 (file)
index 0000000..0fbbd31
--- /dev/null
@@ -0,0 +1,42 @@
+# Mailadressen, die auf diese Kriterien zutreffen, werden als verdaechtig
+# angesehen und zurueckgewiesen. Im interaktiven Modus kann der Wahlleiter 
+# im Einzelfall entscheiden.
+#
+# Bitte ein Pattern (Regular Expression) pro Zeile eingeben.
+# Achtung, es wird so benutzt, wie es hier steht, also auf fuehrende oder
+# abschliessende Leerzeichen aufpassen!
+#
+# Alle Zeilen, die mit dem Kommentarzeichen # anfangen, werden ignoriert.
+
+@anon\.penet\.fi
+@anon\.sub\.net
+anonymous@mixmaster\.nullify\.org
+me@privacy.net
+admin@
+daemon@
+anon@
+audit@
+bin@
+decnet@
+default@
+field@
+guest@
+hostmaster@
+install@
+mailer.*@
+maint.*@
+news@
+newsmaster@
+nobody@
+operator@
+postmaster@
+root@
+shutdown@
+sync@
+sys@
+sysop@
+system@
+test@
+tutor@
+usenet@
+uucp@ 
diff --git a/messages.cfg b/messages.cfg
new file mode 100644 (file)
index 0000000..6b1dd5d
--- /dev/null
@@ -0,0 +1,194 @@
+# Messages used in Usevote. Variables are enclosed in ${... }.
+# Mostly these is interactive output for the votetaker, but texts from
+# uvbounce.pl and uvrules.pl are also partly mailed back to the voters.
+#
+# Comments are allowed, but the # sign must be at the beginning of the line
+# (no leading spaces or others characters allowed).
+#
+# General format:
+# Identifier = message
+# (Identifier has to start at the beginning of the line, without leading space)
+#
+###################################################################################
+#
+# UVconfig.pm
+#
+CONF_NOGROUPS = Kein Abstimmungsgegenstand definiert (siehe ${CONFIGFILE})!
+CONF_NOBDSGFILE = Datei mit Datenschutzhinweis ("${BDSGFILE}") nicht lesbar!
+CONF_NOSIG = Abschnitt [Signatur] nicht in Datei ${FILE} gefunden!
+CONF_NOBADADDR = Datei mit verdaechtigen Adressen "${BADADDRFILE}" nicht lesbar!
+CONF_TEST_RULES = Regeln aus usevote.rul: 
+CONF_NO_RULES = (keine Regeln definiert)
+CONF_CONFIG = Konfiguration:
+#
+# UVmenu.pm
+#
+MENU_INVALIDNAME = Ungueltiger Name.
+MENU_NONAME = Kein Name angegeben.
+MENU_INVALIDBDSG = Datenschutzhinweis fehlerhaft oder nicht bestaetigt.
+MENU_DUPLICATEVOTE = Doppelte Stimmabgabe gefunden.
+MENU_NOVOTE = Keine Stimmabgabe gefunden.
+MENU_INVALIDVOTE = Ungueltige Stimmabgabe.
+MENU_NOBALLOTID = Keine Scheinkennung gefunden.
+MENU_WRONGBALLOTID = Scheinkennung falsch.
+MENU_ADDRESSNOTREGISTERED = Adresse nicht registriert.
+MENU_INVALIDADDRESS = Ungueltige Mail-Adresse.
+MENU_INVALIDADDRESS2 = Es wird keine Mail verschickt!
+MENU_SUSPICIOUSACCOUNT = Verdaechtige Adresse gefunden.
+MENU_UNRECOGNIZEDVOTE = Stimmen nicht vollstaendig erkannt. Im Zweifelsfall "Enthaltung" angenommen.
+MENU_UNRECOGNIZED_LIST = Stimme
+MENU_VIOLATEDRULE = Regel ${RULE} verletzt.
+MENU_PROBLEMS = Die folgenden Probleme muessen beseitigt werden:
+MENU_CAPTION = Auswahlmenue:
+MENU_SHOW_MAIL = Anzeigen der Wahlmail
+MENU_CHANGE_PROPERTIES = Bestaetigen oder Aendern von Wahlschein-Eigenschaften:
+MENU_ADDRESS = Mailadresse
+MENU_ADDRESS_CHANGE = Adresse aendern
+MENU_ADDRESS_OK = Adresse OK
+MENU_ADDRESS_INVALID = Adresse ungueltig
+MENU_ADDRESS_PROMPT = Waehleradresse:
+MENU_NAME = Waehlername
+MENU_NAME_CHANGE = Namen aendern
+MENU_NAME_OK = Name OK
+MENU_NAME_INVALID = Name ungueltig
+MENU_VOTES = Stimmen
+MENU_VOTES_RESET = Stimmen neu setzen
+MENU_VOTES_OK = Stimmen OK
+MENU_VOTES_INVALID = Stimmen ungueltig
+MENU_VOTES_CANCELLED = Stimmen vom Waehler annulliert
+MENU_VOTES_REENTER = Stimme fuer ${GROUP} (J, N oder E): 
+MENU_VOTES_REENTER_ASK = Bitte die Stimmen neu eingeben. Die aus dem Wahlschein erkannten Stimmen sind jeweils der Standardwert und werden in [ ] aufgefuehrt.
+MENU_VOTES_REENTER_LEGEND = J: Ja | N: Nein | E: Enthaltung
+MENU_VOTES_REENTER_OLD = Bisherige Stimmen:
+MENU_VOTES_REENTER_NEW = Neue Stimmen:
+MENU_VOTES_REENTER_ACK = Stimmen uebernehmen
+MENU_VOTES_REENTER_NACK = bisherige Stimmen belassen
+MENU_BALLOT_ID = Scheinkennung
+MENU_BDSG = Datenschutzklausel
+MENU_BDSG_ACCEPTED = Datenschutzklausel wurde akzeptiert
+MENU_BDSG_DECLINED = Datenschutzklausel nicht akzeptiert bzw. Text veraendert
+MENU_IGNORE = Diese Stimme ignorieren (ohne Benachrichtigung verwerfen)
+MENU_IGNORE_WARNING = Die Stimme wird nicht aufgezeichnet, und es wird keine Bestaetigung verschickt. ${MENU_IGNORE_STRING} eingeben, wenn Du sicher bist: 
+MENU_IGNORE_STRING = JA
+MENU_PROCEED = Weiter
+MENU_PROMPT = Eingabe: 
+MENU_GETKEY = *** Return druecken, um fortzufahren ***
+MENU_PROCESSING = Verarbeite Mails...
+MENU_ERROR_WARNING = WARNUNG
+MENU_ERROR_TEXT = Es wurden nicht alle Fehler behoben. Der Waehler wird eine Fehlermail erhalten und die Stimme wird ungueltig gewertet.
+MENU_ERROR_GETKEY = Bitte mit 'y' bestätigen oder mit jeder anderen Eingabe zurück: 
+MENU_DUP_VOTE = Moeglicherweise doppelte Stimmabgabe!
+MENU_DUP_FIRST = Erste Stimme:
+MENU_DUP_SECOND = Zweite Stimme:
+MENU_DUP_DELFIRST = Erste loeschen
+MENU_DUP_DELSECOND = Zweite loeschen
+MENU_DUP_DELNONE = Keine loeschen
+#
+# UVreadmail.pm
+#
+READMAIL_STATUS = Abruf neuer Mails vom POP3-Server...
+READMAIL_NOCONNECTION = Verbindung zum POP3-Server fehlgeschlagen!
+READMAIL_NOLOGIN = Anmeldung am POP3-Server nicht moeglich!
+READMAIL_NOMAILFILE = Maildatei ${FILE} nicht lesbar!
+READMAIL_GET_PROBLEM = Warnung! Konnte Mail Nr. ${NR} nicht abrufen.
+READMAIL_DEL_PROBLEM = Warnung! Konnte Mail Nr. ${NR} nicht loeschen.
+READMAIL_UIDL_PROBLEM = Warnung! Konnte Liste mit UIDLs nicht speichern.
+READMAIL_UIDL_PROBLEM2 = Beim naechsten Lauf keine Erkennung bereits abgerufener Mails moeglich!
+READMAIL_UIDL_CLOSE = Warnung! Konnte UIDL-Cachedatei nicht ordnungsgemaess schliessen.
+READMAIL_ARCHIVE_PROBLEM = Warnung! Wahlmails konnten nicht in Datei ${FILE} gesichert werden.
+READMAIL_ARCHIVE_CLOSE = Warnung! Konnte Wahlmail-Archivdatei ${FILE} nicht schliessen.
+#
+# UVrules.pm
+#
+RULES_ERROPENFILE = Kann Regeldatei ${FILE} nicht oeffnen
+RULES_TOOSHORT = Regel ${NUM}: '${TYPE}'-Bedingung zu kurz
+RULES_TOOLONG = Regel ${NUM}: '${TYPE}'-Bedingung zu lang
+RULES_INVCHARS = Regel ${NUM}: '${TYPE}'-Bedingung enthaelt nicht erlaubte Zeichen
+RULES_RULE = Wahlregel
+RULES_IF = Wenn Du
+RULES_THEN = musst Du
+RULES_AND = und
+RULES_OR = oder
+RULES_YES = JA
+RULES_NO = NEIN
+RULES_ABSTAIN = ENTHALTUNG
+RULES_IFCLAUSE = ${VOTE} stimmst fuer ${GROUP}
+RULES_THENCLAUSE = ${VOTE} stimmen fuer ${GROUP}
+#
+# UVsendmail.pm
+#
+SENDMAIL_ERROPENCONTROL = FEHLER! Steuerungsdatei ${FILE} konnte nicht geoeffnet werden.
+SENDMAIL_ERRCLOSECONTROL = FEHLER! Steuerungsdatei ${FILE} konnte nicht geschlossen werden.
+SENDMAIL_ERRDELCONTROL = FEHLER! Steuerungsdatei ${FILE} konnte nicht geloescht werden.
+SENDMAIL_ERROPENMAIL = FEHLER! Mail-Datei ${FILE} konnte nicht geoeffnet werden.
+SENDMAIL_ERRCLOSEMAIL = FEHLER! Mail-Datei ${FILE} konnte nicht geschlossen werden.
+SENDMAIL_NOMAILS = Keine Mails zu verschicken (Datei ${FILE} nicht gefunden).
+SENDMAIL_SMTP_CONNREFUSED = Keine Verbindung zum SMTP-Server moeglich!
+SENDMAIL_SMTP_AUTHERR = Anmeldung am SMTP-Server fehlgeschlagen!
+SENDMAIL_ERRNOTFOUND = Datei ${FILE} nicht gefunden.
+SENDMAIL_SMTP_INVRCPT = Mail an ${RCPT} konnte nicht verschickt werden:
+SENDMAIL_SENDING = Mails werden verschickt...
+SENDMAIL_ERROCCURED = Fehler aufgetreten. Bitte kontrollieren und "uvvote.pl clean" ggf. noch einmal starten!
+SENDMAIL_MISSINGFILES = Dateien konnten nicht gefunden werden. Dieser Fehler kann auftreten, falls bei einem vorherigen Lauf nur einzelne Mails verschickt werden konnten (diese Dateien wurden dann bereits geloescht).
+SENDMAIL_ERRCLOSEDOMAIL = FEHLER! domail-Datei konnte nicht geschlossen werden.
+SENDMAIL_ACKTXT_MISSING = KONFIGURATIONSFEHLER: Abschnitt [${SECTION}] in ${FILE} existiert nicht! Mails an die Waehler sind moeglicherweise unvollstaendig.
+#
+# uvballot.pl
+#
+BALLOT_NO_PERSONAL = Die Option -t kann nur in Zusammenhang mit persoenlichen Wahlscheinen verwendet werden (Option "personal" in ${CFGFILE}).
+#
+# uvbounce.pl
+#
+BOUNCE_BALLOT = ! Wahlschein nicht zustellbar (Adresse ungueltig)
+BOUNCE_ACKMAIL = ! Bestaetigung nicht zustellbar (Adresse ungueltig)
+#
+# uvcfv.pl
+#
+CFV_NUMBER = Es werden ${COUNT} CfVs verschickt.
+CFV_ERRWRITE = Kann nicht in Scheinkennungsdatei ${FILE} schreiben!
+CFV_ERRCLOSE = Kann Scheinkennungsdatei nicht schliessen!
+CFV_ERROPENCFV = Kann CfV-Datei ${FILE} nicht lesen!
+CFV_SUBJECT = Wahlschein
+#
+# uvcount.pl
+#
+COUNT_ERR_OPEN = Kann Ergebnisdatei ${FILE} nicht oeffnen!
+COUNT_ERR_RESULT = Fehler in ${FILE} Zeile ${LINE}
+COUNT_ERR_GROUPCOUNT = Bei Stimme von <${ADDR}>: ${NUM1} statt ${NUM2} Stimmen gefunden (${RESULTFILE} kontrollieren!)
+COUNT_DELETED = ${NUM} Stimme(n) geloescht.
+#
+# uvvote.pl
+#
+VOTE_RENAMING_MAILBOX = Benenne Stimmdatei um...
+VOTE_WRITE_RESULTS = Ergebnisdatei ${FILE} nicht schreibbar!
+VOTE_CLOSE_RESULTS = Ergebnisdatei ${FILE} konnte nicht erfolgreich geschlossen werden!
+VOTE_NO_VOTES = Keine Stimmen zu verarbeiten.
+VOTE_NUM_VOTES = ${COUNT} Stimmen bearbeitet.
+VOTE_NOT_SAVED = ${COUNT} Stimmen bearbeitet, aber nicht gespeichert.
+VOTE_FIRSTRUN = 'uvvote clean' aufrufen, um Ergebnisse zu speichern und Bestaetigungen zu verschicken.
+VOTE_ERRORS = Folgende Fehler sind aufgetreten (siehe auch ${FILE}):
+VOTE_INVALID_BALLOTID = Scheinkennung ungueltig
+VOTE_MISSING_BALLOTID = Scheinkennung fehlt
+VOTE_UNREGISTERED_ADDRESS = Adresse nicht registriert
+VOTE_INVALID_VOTE = Ungueltige Stimmabgabe
+VOTE_VIOLATED_RULE = Regel ${RULE} verletzt
+VOTE_NO_VOTES = Keine Stimmen abgegeben
+VOTE_INVALID_ACCOUNT = Ungueltiger Account
+VOTE_INVALID_ADDRESS = Ungueltige Adresse
+VOTE_INVALID_REALNAME = Ungueltiger Realname
+VOTE_MISSING_NAME = Kein Name angegeben
+VOTE_DUPLICATES = Verschiedene Stimmen fuer gleichen Abstimmungspunkt
+VOTE_FILE_COMMENT = Wahlleiter setzte ${FIELDS}
+VOTE_NO_NEW_RESULTS = Keine noch nicht verarbeiteten Ergebnisdateien gefunden! Bitte uvvote.pl zunaechst ohne die 'clean'-Option starten.
+VOTE_MOVE_RESULTFILE = Fehler! Konnte Ergebnisdatei ${FILE} nicht verschieben: 
+VOTE_MOVE_VOTEFILE = Fehler! Konnte Stimmdatei ${FILE} nicht verschieben: 
+VOTE_CREATING_RESULTS = Erstelle neue Gesamtergebnisdatei ${FILENAME}...
+#
+# Allgemeine Meldungen
+#
+ERR_MKDIR = Kann Verzeichnis '${DIR}' nicht anlegen: 
+ERR_NOTPERSONAL = Usevote ist nicht fuer die Verwendung personalisierter Wahlscheine konfiguriert (Option "personal" in ${CFGFILE})
+ERR_FILE_CREATION = Fataler Fehler: Konnte keine Datei anlegen.
+ERR_RENAME_MAILFILE = Konnte Maildatei nicht umbenennen: 
+ERR_LOCK = Lockfile ${FILE} bereits vorhanden! Wurde UseVoteGer mehrfach gestartet?
+INFO_TIDY_UP = Aufraeumen...
diff --git a/templates/ack-mail b/templates/ack-mail
new file mode 100644 (file)
index 0000000..150df0b
--- /dev/null
@@ -0,0 +1,46 @@
+ballotid-line   := value ballotidtext | fill-right 20 | append ballotid
+address-line    := value addresstext | fill-right 20 | append address
+name-line       := value nametext2 | fill-right 20 | append name
+pos             := value pos | fill-right 2
+group-first    := value group | first-words 50 
+group-more     := value group | drop-words 50 | create-lines 50
+vote            := value vote  | fill-both 10
+votetaker       := value mailfrom | fill-left 65
+
+== TEMPLATE =================================================================
+Diese automatische Nachricht wurde dir nach Zaehlung deiner Stimme
+zugesandt. Wenn alles stimmt, gibt es keinen Anlass fuer eine Reaktion.
+Wenn deine Stimme falsch registriert wurde, stimme bitte erneut ab,
+indem du diese Mail komplett zitierst und die falschen Wertungen
+korrigierst (zwischen die eckigen Klammern schreiben). Dabei darf
+keinesfalls die laufende Nummer am Zeilenanfang entfernt werden.
+
+Diese Wahl ist oeffentlich, und die Adressen aller Waehlerinnen und
+Waehler werden am Ende bekanntgegeben. Wenn du deine Adresse & Stimme
+loeschen willst, kannst du erneut abstimmen und dabei 'ANNULLIERUNG'
+anstelle von 'JA' oder 'NEIN' angeben. \[Doppel-N, Doppel-L :-)\]
+
+[?personal|Da diese Abstimmung mit personalisierten Wahlscheinen durchgefuehrt
+wird, sind auch saemtliche Aenderungen nur mit Angabe der folgenden
+Wahlscheinkennung gueltig!
+
+[$ballotid-line]]
+[$address-line]
+[$name-line]
+  (Real-Namen sind fuer diese Abstimmung vorgeschrieben. Wenn hier
+   nicht Dein wirklicher Name steht, dann korrigiere die Zeile
+   bitte und sende die Nachricht zurueck; sonst kann die Stimme
+   spaeter als ungueltig gewertet werden.)
+
+
+Deine Stimmabgabe wurde wie folgt erkannt:
+
+[@groups|#[$pos]  \[[$vote]\]  fuer [$group-first]
+[@group-more|                        [$line]\n]|\n]
+
+
+Danke fuer deine Stimmabgabe.  Eine Kopie des CfV kannst du von mir er-
+halten (bitte Namen der Abstimmung angeben, falls mehrere laufen).
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/address-not-registered b/templates/address-not-registered
new file mode 100644 (file)
index 0000000..f12ae79
--- /dev/null
@@ -0,0 +1,25 @@
+votetaker       := value mailfrom | fill-left 65
+head            := value head     | quote "> "
+body            := value body     | quote "> "
+
+== TEMPLATE =================================================================
+Deine Adresse ist beim Wahlleiter bisher nicht mit einer Wahlschein-
+kennung registriert, mit einer EMail an die im CfV genannten Adresse
+kannst du dir eine Kennung zuteilen lassen.
+
+Solltest du bereits eine Kennung erhalten haben, so achte bitte darauf,
+diese nur mit genau der Mailadresse zu verwenden, fuer die sie
+registriert ist.
+
+Achtung, die Mailadresse wird unter Beachtung von Gross- und Klein-
+schreibung ausgewertet, Meier und meier sind daher _nicht_ identisch!
+
+Hier die Mail, die ich erhalten habe:
+
+[$head]
+>
+[$body]
+
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/ballot b/templates/ballot
new file mode 100644 (file)
index 0000000..5e3242a
--- /dev/null
@@ -0,0 +1,34 @@
+votename-first  := value votename | first-words 55
+votename-more   := value votename | drop-words 55 | create-lines 55
+
+pos             := value pos | fill-right 2
+
+group-first    := value group | first-words 50 
+group-more     := value group | drop-words 50 | create-lines 50
+
+bdsginfo        := value bdsginfo | create-lines 72
+bdsgtext-first  := value bdsgtext | first-words 50
+bdsgtext-more   := value bdsgtext | drop-words 50 | create-lines 50
+
+== TEMPLATE =================================================================
+
+=-=-=-=-=-=-=-=- Alles vor dieser Zeile bitte loeschen =-=-=-=-=-=-=-=-
+
+WAHLSCHEIN fuer [$votename-first]
+[@votename-more|                [$line]\n]
+
+
+[$nametext]
+
+Wenn du keinen Real-Namen angibst, wird deine Stimme fuer
+ungueltig erklaert werden.
+
+
+Nr   \[Deine Stimme\]  Gruppe/Abstimmungsgegenstand
+========================================================================
+[@groups|#[$pos]  \[            \]  [$group-first]
+[@group-more|                     [$line]\n]|\n]
+[?bdsg|[@bdsginfo|[$line]|\n]\n\n#a   \[            \]  [$bdsgtext-first]
+[@bdsgtext-more|                     [$line]|\n]]
+
+=-=-=-=-=-=-=-=- Alles nach dieser Zeile bitte loeschen =-=-=-=-=-=-=-=-
diff --git a/templates/ballot-personal b/templates/ballot-personal
new file mode 100644 (file)
index 0000000..13aef2a
--- /dev/null
@@ -0,0 +1,63 @@
+votename-first  := value votename | first-words 55
+votename-more   := value votename | drop-words 55 | create-lines 55
+
+pos             := value pos | fill-right 2
+
+group-first    := value group | first-words 50 
+group-more     := value group | drop-words 50 | create-lines 50
+
+bdsginfo        := value bdsginfo | create-lines 72
+bdsgtext-first  := value bdsgtext | first-words 50
+bdsgtext-more   := value bdsgtext | drop-words 50 | create-lines 50
+
+votetaker       := value mailfrom | fill-left 65
+
+== TEMPLATE =================================================================
+
+[?alreadysent|
+!! Dieser CfV wurde dir bereits einmal zugeschickt, sollte der erste
+!! Wahlschein nicht angekommen oder die Anforderung nicht auf deinen
+!! Wunsch geschehen sein, so wende dich bitte an den Wahlleiter.
+]
+
+
+
+************************************
+*** hier den CfV-Text einfuegen! ***
+************************************
+
+
+=-=-=-=-=-=-=-=- Alles vor dieser Zeile bitte loeschen =-=-=-=-=-=-=-=-
+
+WAHLSCHEIN fuer [$votename-first]
+[@votename-more|                [$line]\n]
+
+
+[$ballotidtext] [$ballotid]
+[$addresstext] [$address]
+
+Dies ist deine persoenliche und _geheime_ Wahlschein-Kennung!
+Halte deine Wahlscheinkennung zwecks Missbrauchs- und Manipulations-
+vermeidung bitte moeglichst geheim, da jeder, der sie kennt, in
+deinem Namen abstimmen kann!
+
+[$nametext]
+
+Wenn du keinen Real-Namen angibst, wird deine Stimme fuer
+ungueltig erklaert werden.
+
+
+Nr   \[Deine Stimme\]  Gruppe/Abstimmungsgegenstand
+========================================================================
+[@groups|#[$pos]  \[            \]  [$group-first]
+[@group-more|                     [$line]\n]|\n]
+[?bdsg|[@bdsginfo|[$line]|\n]\n\n#a   \[            \]  [$bdsgtext-first]
+[@bdsgtext-more|                     [$line]|\n]]
+
+=-=-=-=-=-=-=-=- Alles nach dieser Zeile bitte loeschen =-=-=-=-=-=-=-=-
+
+
+Danke fuer Deine Stimmabgabe.
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/ballot-request b/templates/ballot-request
new file mode 100644 (file)
index 0000000..b1b0c69
--- /dev/null
@@ -0,0 +1,10 @@
+=-=-=-=-=-=-=-=- Alles vor dieser Zeile bitte loeschen =-=-=-=-=-=-=-=-
+
+Zur Wahlscheinanforderung einfach formlos *per Mail* auf dieses
+Posting antworten. Falls nach einigen Tagen noch kein Wahlschein
+eingetroffen ist, kannst du unter folgender Adresse mit dem
+Wahlleiter Kontakt aufnehmen:
+
+[$mailaddress]
+
+=-=-=-=-=-=-=-=- Alles nach dieser Zeile bitte loeschen =-=-=-=-=-=-=-=-
diff --git a/templates/bdsg-error b/templates/bdsg-error
new file mode 100644 (file)
index 0000000..bd44741
--- /dev/null
@@ -0,0 +1,29 @@
+votetaker       := value mailfrom | fill-left 65
+
+== TEMPLATE =================================================================
+Zur Wertung deiner Stimme ist die Zustimmung zur Speicherung,
+Verarbeitung und Veroeffentlichung der Stimmabgabe erforderlich.
+Diese Zustimmung konnte nicht automatisch erkannt werden.
+deine Stimme wurde daher geloescht und wird nicht gewertet.
+
+Dieses kann folgende Gruende haben:
+
+* In deinem Wahlschein fehlt der Abschnitt, der ueber die Art
+  der Verarbeitung deiner Stimmdaten informiert, oder er wurde
+  als nicht vollstaendig erkannt. Um bezueglich des deutschen
+  Bundesdatenschutzgesetzes keine Risiken einzugehen, muss ich
+  darauf bestehen, dass dieser Absatz unveraendert mitgeschickt
+  und akzeptiert wird.
+
+* Du hast nicht der Speicherung/Verarbeitung/Veroeffentlichung
+  zugestimmt. Im dazugehoerigen Feld muss "JA" eingetragen
+  werden.
+
+Bitte stimme erneut ab, lasse dabei den Hinweistext im
+Wahlschein intakt und akzeptiere die Klausel, indem du im
+vorgesehen Feld "JA" eintraegst. Sollte das Problem aufgrund eines
+Uebermittlungsfehlers bestehen bleiben, melde Dich bitte unter
+der unten angegebenen Adresse bei mir.
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/bouncelist b/templates/bouncelist
new file mode 100644 (file)
index 0000000..6f570bd
--- /dev/null
@@ -0,0 +1,10 @@
+multi-line     := value name | justify-before mail 72
+bouncetext      := value bouncetext | create-lines 72
+
+== TEMPLATE =================================================================
+
+Als ungueltig erkannte Stimmen:
+------------------------------------------------------------------------
+[@bounces|[$multi-line]
+[@bouncetext|[$line]]|\n]
+
diff --git a/templates/cancelled b/templates/cancelled
new file mode 100644 (file)
index 0000000..07cb9b2
--- /dev/null
@@ -0,0 +1,12 @@
+votetaker       := value mailfrom | fill-left 65
+
+== TEMPLATE =================================================================
+Diese automatische Nachricht wurde dir nach Erhalt Deiner ANNULLIERUNG
+zugesandt. Jede vorherige Stimmabgabe in dieser Abstimmung ist dadurch
+annulliert und wird weder gezaehlt noch veroeffentlicht.
+
+Wenn dies ein Fehler war und du doch waehlen moechtest, musst du erneut
+abstimmen.
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/invalid-account b/templates/invalid-account
new file mode 100644 (file)
index 0000000..909dc01
--- /dev/null
@@ -0,0 +1,27 @@
+votetaker       := value mailfrom | fill-left 65
+head            := value head     | quote "> "
+body            := value body     | quote "> "
+
+== TEMPLATE =================================================================
+Deine Stimme kann nicht akzeptiert werden, da du eine kritische
+Absenderadresse verwendest. Nichtpersoenliche Accounts wie root@,
+operator@, postmaster@, guest@ usw. sind im Allgemeinen nicht zu
+Abstimmungen zugelassen, insbesondere, wenn kein Realname angegeben
+wird. Stimmen von Anon-Servern werden ebenfalls nicht akzeptiert.
+Bitte stimme erneut von einem persoenlichen Account aus ab.
+
+Wenn das ein Fehler ist und dein wahrer Name/Account faelschlicherweise
+fuer einen dieser unpersoenlichen oder Anon-Accounts gehalten wurde, so
+schicke bitte eine eMail an meine persoenliche Adresse. Sorry fuer das
+umstaendliche Verfahren, aber nur so ist die zuverlaessige automatische
+Stimmenzaehlung moeglich.
+
+Hier die Mail, die ich erhielt:
+
+[$head]
+>
+[$body]
+
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/invalid-name b/templates/invalid-name
new file mode 100644 (file)
index 0000000..ce7a28d
--- /dev/null
@@ -0,0 +1,19 @@
+votetaker       := value mailfrom | fill-left 65
+head            := value head     | quote "> "
+body            := value body     | quote "> "
+
+== TEMPLATE =================================================================
+Dieser Call for Votes (CfV) erfordert die Angabe eines Real-Namens ent-
+weder im "From:"-Feld des Headers oder im dafuer vorgesehehen Feld des
+Wahlscheins. Bitte wiederhole Deine Stimmabgabe mit Angabe Deines wirk-
+lichen Namens.
+
+Hier die Mail, die ich erhalten habe:
+
+[$head]
+>
+[$body]
+
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/mailheader b/templates/mailheader
new file mode 100644 (file)
index 0000000..93746ba
--- /dev/null
@@ -0,0 +1,16 @@
+date := generate-date-header
+msgid := generate-msgid
+
+== TEMPLATE =================================================================
+From: [$from]
+Subject: [$subject]
+To: [$address]
+[?reply-to|Reply-To: [$reply-to]]
+[?reference|In-Reply-To: [$reference]]
+[?reference|References: [$reference]]
+Message-ID: [$msgid]
+Date: [$date]
+X-Automated-Message: generated by [$usevote-version]
+MIME-Version: 1.0
+Content-Type: text/plain; charset\=iso-8859-1
+Content-Transfer-Encoding: 8bit
diff --git a/templates/multiple-votes b/templates/multiple-votes
new file mode 100644 (file)
index 0000000..7da6433
--- /dev/null
@@ -0,0 +1,19 @@
+votetaker       := value mailfrom | fill-left 65
+body            := value body     | quote "> "
+
+== TEMPLATE =================================================================
+Dein Wahlschein enthielt widerspruechliche Stimmen (z.B. sowohl JA- als
+auch NEIN-Stimme fuer dieselbe Gruppe).
+
+Das passiert meistens, wenn du mehrere Zeilen schickst,  die als Stimme
+interpretiert werden, z. B. wenn du vergisst, eine Beispielzeile im CfV
+zu loeschen. Bitte lies den CfV noch einmal und wiederhole deine Stimm-
+abgabe.
+
+Hier die Mail, die ich erhalten habe:
+
+[$body]
+
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/no-ballot b/templates/no-ballot
new file mode 100644 (file)
index 0000000..4a2b71e
--- /dev/null
@@ -0,0 +1,16 @@
+votetaker       := value mailfrom | fill-left 65
+body            := value body     | quote "> "
+
+== TEMPLATE =================================================================
+In deiner Mail wurde kein gueltiger Wahlschein gefunden. Bitte lies die
+Hinweise im Call for Votes (CfV) und stimme nochmal ab.
+Dieser Fehler tritt meistens dann auf, wenn du Zeilen loeschst, die du
+laut CfV nicht haettest loeschen sollen.
+
+Hier die Mail, die ich erhalten habe:
+
+[$body]
+
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/no-ballotid b/templates/no-ballotid
new file mode 100644 (file)
index 0000000..2628944
--- /dev/null
@@ -0,0 +1,19 @@
+votetaker       := value mailfrom | fill-left 65
+head            := value head     | quote "> "
+body            := value body     | quote "> "
+
+== TEMPLATE =================================================================
+In deinem Wahlschein konnte keine Wahlscheinkennung gefunden werden,
+diese wurde dir zusammen mit dem Wahlschein zugeschickt. Solltest
+du noch keine eigenen Wahlscheinkennung haben, so kannst du mit
+einer Mail an die im CfV genannte Adresse eine anfordern.
+
+Hier die Mail, die ich erhalten habe:
+
+[$head]
+>
+[$body]
+
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/no-votes b/templates/no-votes
new file mode 100644 (file)
index 0000000..1a6de00
--- /dev/null
@@ -0,0 +1,20 @@
+votetaker       := value mailfrom | fill-left 65
+body            := value body     | quote "> "
+
+== TEMPLATE =================================================================
+Offenbar hast du keinerlei Stimmen abgegeben. Wenn das ein Irrtum war,
+lies bitte nochmal die Hinweise im CfV und wiederhole Deine Stimmabgabe.
+
+Dieser Fehler tritt oft auf, wenn du dich vertippst oder dich nicht an
+das im Wahlschein vorgegebene Format haeltst.
+
+Wenn du dich enthalten willst, dann setze bitte ENTHALTUNG an die
+Stelle von JA oder NEIN.
+
+Hier die Mail, die ich erhalten habe:
+
+[$body]
+
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/result-multi b/templates/result-multi
new file mode 100644 (file)
index 0000000..cc3659b
--- /dev/null
@@ -0,0 +1,31 @@
+votename-first        := value votename | first-words 60
+votename-more         := value votename | drop-words 60 | create-lines 60
+numinvalid            := value numinvalid | fill-left 4
+numabstain-formatted  := value numabstain | fill-left 4
+yes                   := value yes | fill-left 4
+no                    := value no | fill-left 4
+group-first           := value group | first-words 40
+group-more            := value group | drop-words 40 | create-lines 40
+
+== TEMPLATE ============================================================
+
+Ergebnisse [$votename-first]
+[@votename-more|           [$line]\n]
+([$numvalid] gueltige Stimmen)
+
+ Ja  Nein : 2/3? >=60? : ang.? : Gruppe
+---- ---- : ---- ----- : ----- : ---------------------------------------
+[@count|[$yes] [$no] : [?cond1| Ja |Nein] [?cond2| Ja |Nein]  : [?result| Ja |Nein]  : [$group-first]
+[@group-more|          :            :       : [$line]\n]\n]
+[?numabstain|[$numabstain-formatted] Enthaltungen]
+[?numinvalid|[$numinvalid] ungueltige Stimme(n)]
+
+Gegen dieses Ergebnis kann innerhalb einer Woche nach seiner
+Veroeffentlichung Einspruch erhoben werden. Der Einspruch ist per
+E-Mail bei der Moderation von de.admin.news.announce (Adressen
+siehe Signatur) einzulegen.
+Wenn es keine ernsthaften Einsprueche gibt oder diese abgelehnt
+werden, wird die Moderation von de.admin.news.announce das
+Ergebnis danach umsetzen.
+
+
diff --git a/templates/result-proportional b/templates/result-proportional
new file mode 100644 (file)
index 0000000..53685d1
--- /dev/null
@@ -0,0 +1,31 @@
+votename-first        := value votename | first-words 60
+votename-more         := value votename | drop-words 60 | create-lines 60
+proportion            := value proportion | sprintf '%6.3f'
+numinvalid            := value numinvalid | fill-left 4
+numabstain-formatted  := value numabstain | fill-left 4
+yes                   := value yes | fill-left 4
+no                    := value no | fill-left 4
+group-first           := value group | first-words 40
+group-more            := value group | drop-words 40 | create-lines 40
+
+== TEMPLATE ============================================================
+
+Ergebnisse [$votename-first]
+[@votename-more|           [$line]\n]
+([$numvalid] gueltige Stimmen)
+
+ Ja  Nein : J>=N? Ja/Nein : ang.? : Gruppe
+---- ---- : ----- ------- : ----- : ---------------------------------------
+[@count|[$yes] [$no] : [?cond1| Ja |Nein]  [$proportion]  :       : [$group-first]
+[@group-more|          :               :       : [$line]\n]\n]
+[?numinvalid|[$numinvalid] ungueltige Stimme(n)]
+
+Gegen dieses Ergebnis kann innerhalb einer Woche nach seiner
+Veroeffentlichung Einspruch erhoben werden. Der Einspruch ist per
+E-Mail bei der Moderation von de.admin.news.announce (Adressen
+siehe Signatur) einzulegen.
+Wenn es keine ernsthaften Einsprueche gibt oder diese abgelehnt
+werden, wird die Moderation von de.admin.news.announce das
+Ergebnis danach umsetzen.
+
+
diff --git a/templates/result-single b/templates/result-single
new file mode 100644 (file)
index 0000000..1be10d2
--- /dev/null
@@ -0,0 +1,28 @@
+votename-first        := value votename | first-words 60
+votename-more         := value votename | drop-words 60 | create-lines 60
+
+votename-text-first   := value votename | first-words 30
+votename-text-more    := value votename | drop-words 30 | create-lines 72
+
+== TEMPLATE ============================================================
+
+Ergebnisse [$votename-first]
+[@votename-more|           [$line]\n]
+([$numvalid] gueltige Stimmen)
+
+Es gab [$yes] Ja-Stimmen und [$no] Nein-Stimmen[?numabstain| bei [$numabstain] Enthaltungen].
+[?numinvalid|[$numinvalid] Stimme(n) wurden als ungueltig gewertet.]
+
+Es wurde [?cond1|die|keine] 2/3-Mehrheit erreicht und es gingen [?cond2|mehr|weniger] als
+60 Ja-Stimmen ein. Damit ist die [$votename-text-first]
+[@votename-text-more|[$line] |\n][?cond1|[?cond2|angenommen|abgelehnt]|abgelehnt].
+
+Gegen dieses Ergebnis kann innerhalb einer Woche nach seiner
+Veroeffentlichung Einspruch erhoben werden. Der Einspruch ist per
+E-Mail bei der Moderation von de.admin.news.announce (Adressen
+siehe Signatur) einzulegen.
+Wenn es keine ernsthaften Einsprueche gibt oder diese abgelehnt
+werden, wird die Moderation von de.admin.news.announce das
+Ergebnis danach umsetzen.
+
+
diff --git a/templates/rule-violated b/templates/rule-violated
new file mode 100644 (file)
index 0000000..ef95b33
--- /dev/null
@@ -0,0 +1,20 @@
+votetaker       := value mailfrom | fill-left 65
+body            := value body     | quote "> "
+
+== TEMPLATE =================================================================
+Diese Abstimmung unterliegt bestimmten Bedingungen, fuer welche Gruppen
+wie abgestimmt werden kann. Diese Bedingungen wurden im Call for Votes
+erklaert. Deine Stimme verletzt eine oder mehrere der Regeln. Bitte lies
+den CfV noch einmal durch und wiederhole Deine Stimmabgabe.
+
+Die folgenden Regeln wurden nicht eingehalten:
+
+[$rules]
+
+Hier die Mail, die ich erhalten habe:
+
+[$body]
+
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/templates/voterlist b/templates/voterlist
new file mode 100644 (file)
index 0000000..4187ce6
--- /dev/null
@@ -0,0 +1,12 @@
+multi-line     := value name | justify-before mail 72
+
+== TEMPLATE ============================================================
+
+Folgende Personen haben sich bislang an der Abstimmung beteiligt:
+========================================================================
+[@multi|[$multi-line]|\n]
+
+
+Als ungueltig erkannte Stimmen:
+========================================================================
+[@invalid|[$multi-line]\n[$reason]|\n]
diff --git a/templates/votes-multi b/templates/votes-multi
new file mode 100644 (file)
index 0000000..6029098
--- /dev/null
@@ -0,0 +1,36 @@
+grafix                 := value group | multi-graph 72 pos groupcount
+grafix-line    := multi-line 72 groupcount
+multi-line     := value name | append vote | justify-before mail 72
+invalid-line   := value name | justify-before mail 72
+numinvalid      := value numinvalid | fill-left 4
+yes            := value yes | fill-left 4
+no             := value no | fill-left 4
+condition1     := value cond1 | fill-both 4
+condition2     := value cond2 | fill-both 5
+result         := value result | fill-both 5
+
+== TEMPLATE ============================================================
+
+========================================================================
+
+Wichtiger Hinweis:
+
+Die unten aufgefuehrten Personen haben der Speicherung, Verarbeitung
+und Veroeffentlichung ihrer Adressen und Stimmdaten nur im Rahmen dieses
+Verfahrens zugestimmt. Eine Verwendung darueber hinaus wurde nicht
+erlaubt. Damit ist insbesondere die Nutzung oder Uebermittlung der Daten
+fuer Werbezwecke oder fuer die Markt- oder Meinungsforschung verboten.
+
+========================================================================
+
+
+Liste der gezaehlten Stimmen:
+
+[@groups|[$grafix]|\n]
+[$grafix-line]
+[@multi|[$multi-line]|\n]
+
+
+Ungueltige Stimmen:
+------------------------------------------------------------------------
+[@invalid|[$invalid-line]\n[$reason]|\n]
diff --git a/templates/votes-single b/templates/votes-single
new file mode 100644 (file)
index 0000000..64e1d37
--- /dev/null
@@ -0,0 +1,33 @@
+mail-name      := value mail | justify name 72
+invalid-line   := value name | justify-before mail 72
+
+== TEMPLATE ============================================================
+
+========================================================================
+
+Wichtiger Hinweis:
+
+Die unten aufgefuehrten Personen haben der Speicherung, Verarbeitung
+und Veroeffentlichung ihrer Adressen und Stimmdaten nur im Rahmen dieses
+Verfahrens zugestimmt. Eine Verwendung darueber hinaus wurde nicht
+erlaubt. Damit ist insbesondere die Nutzung oder Uebermittlung der Daten
+fuer Werbezwecke oder fuer die Markt- oder Meinungsforschung verboten.
+
+========================================================================
+
+
+Ja gestimmt:
+------------------------------------------------------------------------
+[@yes|[$mail-name]\n]
+
+Nein gestimmt:
+------------------------------------------------------------------------
+[@no|[$mail-name]\n]
+
+[?abstain|Enthaltung:
+------------------------------------------------------------------------
+[@abstain|[$mail-name]\n]]
+
+Ungueltige Stimmen:
+------------------------------------------------------------------------
+[@invalid|[$invalid-line]\n[$reason]|\n]
diff --git a/templates/wrong-ballotid b/templates/wrong-ballotid
new file mode 100644 (file)
index 0000000..0eb49f7
--- /dev/null
@@ -0,0 +1,23 @@
+votetaker       := value mailfrom | fill-left 65
+head            := value head     | quote "> "
+body            := value body     | quote "> "
+
+== TEMPLATE =================================================================
+Die Wahlscheinkennung, die du in deinem Wahlschein benutzt hast,
+entspricht nicht der Kennung, die beim Wahlleiter fuer deine EMail-
+Adresse registriert ist, bitte benutzte die Wahlscheinkennung
+aus deinem persoenlichen Wahlschein.
+
+Solltest du wirklich eine andere Wahlscheinkennung zugeschickt
+bekommen haben (oder du hast gar keinen Wahlschein angefordert),
+so setzte dich bitte mit mir in Verbindung.
+
+Hier die Mail, die ich erhalten habe:
+
+[$head]
+>
+[$body]
+
+
+[$votetaker]
+\[mit [$usevote_version]\]
diff --git a/tmp/ergebnis-1191790177 b/tmp/ergebnis-1191790177
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tmp/stimmen-1191790177 b/tmp/stimmen-1191790177
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/uidlcache b/uidlcache
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/usevote.cfg b/usevote.cfg
new file mode 100644 (file)
index 0000000..cf801f5
--- /dev/null
@@ -0,0 +1,293 @@
+########################################################################
+# Diese Einstellungen muessen fuer jede Abstimmung angepasst werden
+########################################################################
+
+# Name der Abstimmung
+votename = Einrichtung von xyz
+
+# Abstimmungsgegenstaende (beliebig viele groupX moeglich,
+# von 1 an durchzunummerieren)
+group1 = Einrichtung von abc
+
+# Bei den folgenden Fragen bedeutet jeweils:
+# 0: nein
+# 1: ja
+
+# Persoenliche Wahlscheine generieren und Scheinkennung erzwingen?
+personal = 0
+
+# Verhaeltniswahl durchfuehren? (z.B. fuer Moderationsnachwahlen)
+proportional = 0
+
+# Formel fuer die Berechnung des Verhaeltnisses. Kann z.B.
+# $yes/$no oder $yes-$no sein, letzteres wird bei Moderationsnachwahlen
+# verwendet. Als Variablen sind $yes und $no zulaessig, es kann
+# beliebiger Perlcode angegeben werden, dessen Rueckgabewert im 
+# Result erscheinen soll. Ausserdem wird bei der Auswertung die
+# Bedingung aus "condition1" weiter unten in dieser Datei ausgewertet.
+prop_formula = $yes/$no
+
+# Vote-Account (diese Adresse muss unbedingt korrekt sein, wird
+# in das Reply-To uebernommen)
+voteaccount = vote-xyz@foo.bar
+
+# Absender fuer den From-Header der Bestaetigungsmails
+mailfrom = Vorname Nachname <gvv@foo.bar>
+
+# Absender fuer den Envelope (Return-Path) der Bestaetigungsmails
+# bei Verwendung von SMTP (bitte einfach nur die Adresse eintragen,
+# ohne Klammern und Zusaetze). Bei smtp=0 muss das in "mailcmd"
+# eingestellt werden, z.B. "-fadresse" für Sendmail
+envelopefrom = gvv@foo.bar
+
+# Nur bei persoenlichen Wahlscheinen: Datei mit Anforderungsmails
+requestfile = anforderung
+
+# Datenschutzklausel generieren und auf deren Existenz pruefen?
+# Konfiguration siehe unten (bdsgtext)
+bdsg = 1
+
+# Reply-To beachten?
+# Nicht empfohlen, da jemand fuer andere Personen abstimmen und
+# die Bestaetigungen zu sich umlenken koennte.
+replyto = 0
+
+# Stimmen einzeln bestaetigen? Empfohlen!
+voteack = 1
+
+# Bcc-Adresse fuer alle Mails (Backup fuer alle Faelle)
+#
+#mailcc = 
+
+# Alles in einem Schritt durchfuehren (gesonderter Aufruf von
+# "uvvote.pl clean" entfaellt)? Fuehrt zu geringeren
+# Eingreifmoeglichkeiten! (Mails werden automatisch verschickt)
+onestep = 0
+
+# Fuer das Ergebnis (xx Ja-Stimmen, xx Nein-Stimmen, xx Enthaltungen)
+# auch bei Eingruppenabstimmung des Mehrgruppenformat waehlen?
+multigroup = 1
+
+# Bedingungen fuer einen Erfolg der Abstimmung (genau zwei erforderlich).
+# Es muss sich jeweils um gueltigen Perl-Code handeln, Rueckgabewert
+# wird boolesch ausgewertet (true/false). Als Variablen sind $yes
+# und $no zugelassen. Falls oben "proportional = 1" gesetzt wurde,
+# wird nur Bedingung 1 ausgewertet und kann z.B. auf "$yes>$no" gesetzt
+# werden.
+condition1 = $yes>=2*$no
+condition2 = $yes>=60
+
+# Ergebnisdatei, in der alle Einzelergebniss zusammengeschrieben werden
+# (wird bei jedem Programmlauf neu erstellt!)
+resultfile = ergebnis.alle
+
+# Datei fuer Scheinkennungen
+idfile = scheinkennungen
+
+# POP3 benutzen? (falls nicht, wird eine lokale Mailbox eingelesen)
+pop3 = 1
+
+# Mailbox, in der die zu verarbeitenden Mails liegen (falls pop3=0)
+votefile = votes
+
+# POP3-Einstellungen fuer Abruf der eingehenden Wahlscheine:
+# Server, Port, Benutzername, Passwort
+pop3server = 127.0.0.1
+pop3port = 110
+pop3user = test
+pop3pass = test
+# Mail nach dem Abrufen vom Server loeschen?
+pop3delete = 0
+# Dateiname zum Speichern der bereits abgerufenen Mail-IDs (UIDL)
+pop3uidlcache = uidlcache
+
+# POP3-Einstellungen fuer Abruf von Wahlschein-Anforderungen bei Abstimmungen
+# mit personalisierten Wahlscheinen (Punkt 6a der Wahlregeln in de.*)
+# Diese zweite Mailbox ist notwendig, um Wahlschein-Anforderungen und die
+# eigentliche Abstimmung voneinander zu trennen (nicht noetig, wenn
+# personal = 0 gesetzt ist)
+pop3server_req = 127.0.0.1
+pop3port_req = 110
+pop3user_req = test
+pop3pass_req = test
+# Mail nach dem Abrufen vom Server loeschen?
+pop3delete_req = 0
+# Dateiname zum Speichern der bereits abgerufenen Mail-IDs (UIDL)
+pop3uidlcache_req = uidlcache_req
+
+# POP3-Einstellungen fuer uvbounce.pl (Verarbeitung von Bounces
+# und Generierung einer Liste mit ungueltigen Stimmen). Alle
+# zurueckgekommenen Mails an Waehler sollten in dieser Mailbox landen
+pop3server_bounce = 127.0.0.1
+pop3port_bounce = 110
+pop3user_bounce = test2
+pop3pass_bounce = test2
+# Mail nach dem Abrufen vom Server loeschen?
+pop3delete_bounce = 0
+# Dateiname zum Speichern der bereits abgerufenen Mail-IDs (UIDL)
+pop3uidlcache_bounce = uidlcache_bounce
+
+########################################################################
+# Alles ab hier braucht i.d.R. nur einmal festgelegt werden.
+# Es ist moeglich, diese Einstellungen in eine globale Konfigurations-
+# datei auszulagern. Das Einbinden erfolgt mit der Zeile
+#
+# include /pfad/zur/globalen_datei
+#
+# Falls Einstellungen aus der globalen Konfigurationsdatei hier
+# fuer einzelne Abstimmungen ueberschrieben werden sollen, muessen
+# diese *hinter* dem Include-Befehl stehen!
+# (der letzte Wert ueberschreibt vorhergehende Definitionen)
+########################################################################
+
+# SMTP benutzen? (falls nicht, wird der weiter unten einstellbare
+# MTA direkt aufgerufen; unter Windows kann nur SMTP benutzt werden!)
+smtp = 1
+
+# SMTP-Server (falls smtp = 1)
+smtpserver = localhost
+smtpport = 25
+
+# SMTP-Authentifizierung benutzen? (RFC 2554)
+# Das entsprechende Perlmodul (Net::SMTP) kann derzeit nur AUTH PLAIN,
+# funktioniert also moeglicherweise nicht mit jedem Server
+#smtpauth = 0
+#smtpuser = 
+#smtppass = 
+
+# Falls als HELO etwas anderes als der Hostname verwendet werden soll:
+#smtphelo = 
+
+# Falls ein anderer Fully Qualified Domain Name als der Hostname fuer
+# die Message-ID verwendet werden soll:
+#fqdn =
+
+# Verzeichnis fuer fertig verarbeitete Mails und Ergebnisse
+archivedir = fertig
+
+# Temporaeres Verzeichnis
+tmpdir = tmp
+
+# Pfad zu den Templates (kommaseparierte Liste mit Verzeichnissen)
+templatedir = templates
+
+# Konvertierungsfunktionen für die Templates
+# (kommaseparierte Liste mit Funktions-Modulen)
+formats   = UVformats.pm
+
+# Dateiname der Steuerungsdatei fuer den Mailversandt
+controlfile = tmp/ack.control
+
+# Dateiname des Shellscripts zum Versenden der Bestaetigungsmails (falls smtp=0)
+domailfile = tmp/domail
+
+# MTA-Aufruf zum Verschicken der Bestaetigungsmails
+# nuetzlich ist die Sendmail-Option -f zum Setzen des Absenders
+#mailcmd = sendmail -oi -oem -femail@adresse
+
+# Weiteres Kommando, welches nach jeder Mail aufgerufen werden soll (falls smtp=0).
+# Sinnvoll ist ein "sleep x", wobei x bei langsamen Systemen hoeher
+# gewaehlt werden sollte, um die Belastung gering zu halten.
+sleepcmd = sleep 1
+
+# Shellbefehl zum Loeschen des Bildschirms
+# Unix: i.d.R. "clear"
+# Windows: "cls"
+# Falls das Betriebssystem bzw. die Shell keinen solchen Befehl zur Verfuegung
+# stellt, sollte ein Kommando verwendet werden, welches eine Trennlinie
+# oder aehnliches auf dem Bildschirm ausgibt, z.B. mit "echo"
+clearcmd = clear
+
+# Shellbefehl zum seitenweisen Darstellen von Mails auf dem Bildschirm
+# Empfohlene Einstellung: "less", da more Probleme mit der Umleitung von
+# STDERR in eine Datei Probleme hat. Unter Windows ist "less" nicht
+# vorinstallirt, kann man sich aber herunterladen und einfach in das
+# Windows-Verzeichnis kopieren (URL siehe README-Datei)
+pager = less
+
+# Datei mit diversen Meldungen und Textfragmenten (Resourcendatei)
+messagefile = messages.cfg
+
+# Datei mit Wahlregeln
+rulefile = usevote.rul
+
+# Datei mit verdaechtigen Mailadressen
+badaddrfile = mailpatterns.cfg
+
+# Datei fuer Fehlermeldungen beim Programmlauf
+errorfile = errors.log
+
+# Lockdatei (Verhinderung von mehrfachen Programmstarts)
+lockfile = usevote.lock
+
+# Einleitungszeile fuer naechste Mail (RegExp)
+mailstart = "^From "
+
+# Trennlinien vor und nach dem Wahlschein
+begin_divider = Alles vor dieser Zeile bitte loeschen
+end_divider = Alles nach dieser Zeile bitte loeschen
+
+# Text fuer die Namens-Angabe im Wahlschein. Achtung, muss im
+# Wahlschein genauso stehen!
+nametext = Dein Realname, falls nicht im FROM-Header:
+
+# Text fuer Namens-Angabe in Bestaetigungsmails
+nametext2 = Waehlername:
+
+# Text fuer die Adress-Angabe im Wahlschein
+addresstext = Waehleradresse:
+
+# Text für die Angabe der Wahlscheinkennung (siehe Option "personal")
+ballotidtext = Wahlscheinkennung:
+
+# Text fuer Datenschutzklausel (siehe Option "bdsg"), erscheint als Abstimmungspunkt
+bdsgtext = Datenschutzklausel - Zustimmung: Ich bin mit der Verarbeitung meiner Daten wie oben beschrieben einverstanden
+
+# Datei mit Erklaerungstext fuer BDSG-Klausel
+bdsgfile = bdsgtext.cfg
+
+# Rechter Rand fuer einige Bildschirmausgaben (Terminalbreite)
+rightmargin = 72
+
+# Regular Expression fuer Erkennung eines gueltigen Realnamens
+name_re = [-a-zA-ZäöüáàâéèêíìîóòôúùûÄÖÜÁÁÂÉÈÊÍÌÎÓÒÔÚÙÛß]{2,} +.*[a-zA-ZäöüáàâéèêíìîóòôúùûÄÖÜÁÁÂÉÈÊÍÌÎÓÒÔÚÙÛß]{2,}
+
+# RegExp fuer JA-Stimmen (case-insensitive)
+# Standardmaessig wird J, JA, FUER und DAFUER erkannt
+ja_stimme = (J\s*A|J|(D\s*A\s*)?F\s*U\s*E\s*R)
+
+# RegExp fuer NEIN-Stimmen (case-insensitive)
+# Standardmaessig wird N, NEIN, GEGEN und DAGEGEN erkannt
+nein_stimme = (N\s*E\s*I\s*N|N|(D\s*A\s*)?G\s*E\s*G\s*E\s*N)
+
+# RegExp fuer ENTHALTUNG (case-insensitive)
+enth_stimme = (E|E\s*N\s*T\s*H\s*A\s*L\s*T\s*U\s*N\s*G)
+
+# RegExp fuer ANNULLIERUNG (case-insensitive)
+# Achtung, sollte auch in den Templates im Bestaetigungstext angepasst werden
+ann_stimme = A\s*N\s*N\s*U\s*L\s*L\s*I\s*E\s*R\s*U\s*N\s*G
+
+# Template files (these files are in the template directory defined above)
+tpl_mailheader = "mailheader"            # generally used mail header
+tpl_bouncelist = "bouncelist"            # used by uvbounce.pl
+tpl_result_multi = "result-multi"        # used by uvcount.pl -r -m
+tpl_result_single = "result-single"      # used by uvcount.pl -r -o
+tpl_result_prop = "result-proportional"  # used by uvcount.pl -r (proportional = 1)
+tpl_votes_multi = "votes-multi"          # used by uvcount.pl -v (multiple groups)
+tpl_votes_single = "votes-single"        # used by uvcount.pl -v (single group only)
+tpl_voterlist = "voterlist"              # used by uvcount.pl -l (2nd CfV) 
+tpl_ballot = "ballot"                    # used by uvballot.pl (personal = 0)
+tpl_ballot_request = "ballot-request"    # used by uvballot.pl (personal = 1)
+tpl_ballot_personal = "ballot-personal"  # used by uvcfv.pl    (personal = 1)
+tpl_addr_reg = "address-not-registered"  # used by uvvote.pl   (personal = 1)
+tpl_no_ballotid = "no-ballotid"          # used by uvvote.pl   (personal = 1)
+tpl_wrong_ballotid = "wrong-ballotid"    # used by uvvote.pl   (personal = 1)
+tpl_bdsg_error = "bdsg-error"            # used by uvvote.pl   (bdsg = 1)
+tpl_ack_mail = "ack-mail"                # used by uvvote.pl   (voteack = 1)
+tpl_cancelled = "cancelled"              # used by uvvote.pl
+tpl_invalid_account = "invalid-account"  # used by uvvote.pl
+tpl_invalid_name = "invalid-name"        # used by uvvote.pl
+tpl_multiple_votes = "multiple-votes"    # used by uvvote.pl
+tpl_no_ballot = "no-ballot"              # used by uvvote.pl
+tpl_no_votes = "no-votes"                # used by uvvote.pl
+tpl_rule_violated = "rule-violated"      # used by uvvote.pl   (c.f. usevote.rul)
diff --git a/usevote.rul b/usevote.rul
new file mode 100644 (file)
index 0000000..4d9a6c6
--- /dev/null
@@ -0,0 +1,68 @@
+# UseVote  (c) 1993,94 Ron Dippold, alle Rechte vorbehalten
+#         uebersetzt von Frederik Ramm
+#
+# Mit dieser Datei koennen spezielle Regeln fuer eine gueltige Stimmabgabe
+# bei Mehrgruppenabstimmungen erstellt werden.
+# Beispiel: Eine Gruppe soll aufgeteilt werden, und Du willst erzwingen,
+# dass jemand, der fuer eine der neuen Untergruppen stimmt, auch fuer
+# die .misc-Gruppe stimmen muss. Man kann eine Menge komplexe Sachen hier-
+# mit machen, wenn man Programmierer ist :-)
+#
+# Das allgemeine Format sieht so aus:
+# (a) alles, was mit # anfaengt, ist ein Kommentar.
+# (b) "echte" Regeln sehen etwa so aus (natuerlich ohne #):
+#   if .jjjjj then J.....
+#
+# Eine Regel beginnt immer mit "if", und danach folgen eine Anzahl Symbole;
+# diese Anzahl muss gleich der Anzahl der Gruppen sein, ueber die abgestimmt
+# wird.
+# Oben geht es also um eine Abstimmung ueber sechs Gruppen.
+# Die Symbole zwischen 'if' und 'then' geben an, welche Bedingungen erfuellt
+# sein muessen, damit das System auch die Bedingungen hinter then prueft.
+#
+# Nach 'then' folgen nochmal so viele Symbole, die angeben, welche Bedin-
+# gungen erfuellt sein muessen, falls die Bedingungen zwischen 'if' und
+# 'then' erfuellt waren. Jeder Wahlschein, auf den das nicht zutrifft, ist
+# ungueltig.
+#
+# Folgende Symbole sind erlaubt:
+#   J  eine JA-Stimme
+#   N  eine NEIN-Stimme
+#   E  eine Enthaltung
+#   S  eine JA- oder NEIN-Stimme
+#   H   eine Enthaltung oder JA-Stimme
+#   I   eine Enthaltung oder NEIN-Stimme
+#   .  egal (Ja, nein oder Enthaltung)
+#   j  eine oder mehrere der markierten Gruppen hat JA-Stimme
+#   n   ""   ""    ""     ""     ""       ""    ""  NEIN-Stimme
+#   e   ""   ""    ""     ""     ""       ""    ""  Enthaltung
+#   s   ""   ""    ""     ""     ""       ""    ""  Ja- oder Nein-Stimme
+#   h   ""   ""    ""     ""     ""       ""    ""  Enthaltung oder Ja-Stimme
+#   i   ""   ""    ""     ""     ""       ""    ""  Enthaltung oder Nein-Stimme
+#
+# Alles klar?  Jede Stimme wird mit den Symbolen verglichen, und wenn alle
+# Kriterien passen, ist der Ausdruck wahr. Wenn der erste Ausdruck (if x)
+# wahr ist, muss auch der zeite (then y) wahr sein, sonst ist der Wahlschein
+# ungueltig. Ein Beispiel:
+#   if .jjjjj then J.....
+# Das heisst: Wenn der Waehler fuer *irgendeine* ausser der ersten Gruppe mit
+# JA stimmt, dann *muss* er JA fuer die erste Gruppe stimmen, oder das ganze
+# ist ungueltig.
+# Das koennte z.B. eine Gruppenaufteilung sein, wo fuer die erste (die .misc)-
+# Gruppe gestimmt werden *muss*, wenn fuer eine der anderen gestimmt wird.
+#
+# Hier noch ein Beispiel:
+#    if S... then .ss.
+#    if .S.. then ..E.
+#    if ..S. then .E..
+# Diese Regeln sagen: Wer fuer die erste Gruppe abstimmt, der muss auch fuer
+# die zweite und dritte Gruppe abstimmen - egal wie. Ausserdem muss er (Re-
+# geln 2 und 3) sich bei 3 enthalten, wenn er bei 2 eine Stimme abgibt und
+# umgekehrt. Die vierte Gruppe wird hier gar nicht betroffen.
+#
+# Also: es gibt einfache Regeln, aber es sind auch sehr komplizierte Kom-
+# binationen denkar. Das Programm macht alles mit... die Frage ist, ob
+# es die Waehler tun :-)
+#
+# Fuege Deine Regeln hier an. Mit "uvvote.pl -t" kannst Du sie testen.
+
diff --git a/uvballot.pl b/uvballot.pl
new file mode 100644 (file)
index 0000000..4085a7f
--- /dev/null
@@ -0,0 +1,116 @@
+#!/usr/bin/perl -w
+
+###############################################################################
+# UseVoteGer 4.09 Wahlscheingenerierung
+# (c) 2001-2005 Marc Langer <uv@marclanger.de>
+# 
+# This script package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Public License as published by the
+# Free Software Foundation.
+#
+# Use this script to create the ballot which can be inserted into the CfV.
+# Not for personal ballots (personal=1 in usevote.cfg), use uvcfv.pl instead.
+#
+# Many thanks to:
+# - Ron Dippold (Usevote 3.0, 1993/94)
+# - Frederik Ramm (German translation, 1994)
+# - Wolfgang Behrens (UseVoteGer 3.1, based on Frederik's translation, 1998/99)
+# - Cornell Binder for some good advice and code fragments
+#
+# This is a complete rewrite of UseVoteGer 3.1 in Perl (former versions were
+# written in C). Not all functions of Usevote/UseVoteGer 3.x are implemented!
+###############################################################################
+
+use strict;
+use Getopt::Long;
+use Text::Wrap qw(wrap $columns);
+use FindBin qw($Bin);
+use lib $Bin;
+use UVconfig;
+use UVtemplate;
+
+my %opt_ctl = ();
+
+print STDERR "\n$usevote_version Wahlscheingenerierung - (c) 2001-2005 Marc Langer\n\n";
+
+# unknown parameters remain in @ARGV (for "help")
+Getopt::Long::Configure(qw(pass_through bundling));
+
+# Put known parameters in %opt_ctl
+GetOptions(\%opt_ctl, qw(t|template c|config-file=s));
+
+# Additional parameters or invalid options? Show help and exit.
+help() if (@ARGV);
+
+# Get name auf config file (default: usevote.cfg) and read it
+my $cfgfile   = $opt_ctl{c} || "usevote.cfg";
+UVconfig::read_config($cfgfile);
+
+# Set columns for Text::Wrap
+$columns = $config{rightmargin};
+
+if ($config{personal}) {
+  print_ballot_personal();
+} else {
+  print_ballot();
+}
+
+exit 0;
+
+
+##############################################################################
+# Print out a proper ballot                                                  #
+##############################################################################
+
+sub print_ballot {
+
+  my $template = UVtemplate->new();
+
+  $template->setKey('votename' => $config{votename});
+  $template->setKey('nametext' => $config{nametext});
+  $template->setKey('bdsg'     => $config{bdsg});
+  $template->setKey('bdsgtext' => $config{bdsgtext});
+  $template->setKey('bdsginfo' => $config{bdsginfo});
+
+  for (my $n=0; $n<@groups; $n++) {
+     $template->addListItem('groups', pos=>$n+1, group=>$groups[$n]);
+  }
+
+  print $template->processTemplate($config{'tpl_ballot'});
+
+}
+
+
+##############################################################################
+# Generate a ballot request (if personalized ballots are activated)          #
+##############################################################################
+
+sub print_ballot_personal {
+  my $template = UVtemplate->new();
+  $template->setKey('mailaddress' => $config{mailfrom});
+  print $template->processTemplate($config{'tpl_ballot_request'});
+}
+
+
+##############################################################################
+# Print help text (options and syntax) on -h or --help                       #
+##############################################################################
+
+sub help {
+  print STDERR <<EOF;
+Usage: uvballot.pl [-c config_file]
+       uvballot.pl -h
+
+Generiert den Wahlschein fuer den CfV. Bei personalisierten Wahlscheinen
+(personal = 1 in usevote.cfg) wird nur ein Dummy-Abschnitt mit Hinweisen
+zur Wahlscheinanforderung ausgegeben.
+
+  -c config_file   liest die Konfiguration aus config_file
+                   (usevote.cfg falls nicht angegeben)
+
+  -h, --help       zeigt diesen Hilfetext an
+
+EOF
+
+  exit 0;
+}
diff --git a/uvbounce.pl b/uvbounce.pl
new file mode 100644 (file)
index 0000000..1bf5139
--- /dev/null
@@ -0,0 +1,194 @@
+#!/usr/bin/perl -w
+
+###############################################################################
+# UseVoteGer 4.09 Bounce-Verarbeitung
+# (c) 2001-2005 Marc Langer <uv@marclanger.de>
+# 
+# This script package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Public License as published by the
+# Free Software Foundation.
+#
+# Use this script to process bounce messages and generate a list of
+# undeliverable voter adresses.
+#
+# Many thanks to:
+# - Ron Dippold (Usevote 3.0, 1993/94)
+# - Frederik Ramm (German translation, 1994)
+# - Wolfgang Behrens (UseVoteGer 3.1, based on Frederik's translation, 1998/99)
+# - Cornell Binder for some good advice and code fragments
+#
+# This is a complete rewrite of UseVoteGer 3.1 in Perl (former versions were
+# written in C). Not all functions of Usevote/UseVoteGer 3.x are implemented!
+###############################################################################
+
+use strict;
+use Getopt::Long;
+use FindBin qw($Bin);
+use lib $Bin;
+use UVconfig;
+use UVreadmail;
+use UVtemplate;
+
+my %opt_ctl = ();
+my %bounces = ();
+my $pop3 = 0;
+
+print STDERR "\n$usevote_version Bounce-Verarbeitung - (c) 2001-2005 Marc Langer\n\n";
+
+# unknown parameters remain in @ARGV (for "help")
+Getopt::Long::Configure(qw(pass_through bundling));
+
+# Put known parameters in %opt_ctl
+GetOptions(\%opt_ctl, qw(h|help f|file config-file=s c=s));
+
+# Get name auf config file (default: usevote.cfg) and read it
+my $cfgfile   = $opt_ctl{'config-file'} || $opt_ctl{c} || "usevote.cfg";
+UVconfig::read_config($cfgfile);
+
+# check POP3 settings in usevote.cfg and combination with -f parameter
+$pop3 = 1 if ($config{pop3} && !$opt_ctl{f});
+
+# Additional parameters or invalid options? Show help and exit. 
+help() if ($opt_ctl{h} || !(@ARGV || $pop3));
+
+# check for lock file
+if (-e $config{lockfile}) {
+  my $lockfile = $config{lockfile};
+
+  # don't delete lockfile in END block ;-)
+  $config{lockfile} = '';
+
+  # exit
+  die UVmessage::get("ERR_LOCK", (FILE=>$lockfile)) . "\n\n";
+}
+
+# safe exit (delete lockfile)
+$SIG{QUIT} = 'sighandler';
+$SIG{INT} = 'sighandler';
+$SIG{KILL} = 'sighandler';
+$SIG{TERM} = 'sighandler';
+$SIG{HUP} = 'sighandler';
+
+# create lock file
+open (LOCKFILE, ">$config{lockfile}");
+close (LOCKFILE);
+
+# read and process mails
+# for each mail pass a reference to the sub to be called
+
+if ($pop3) {
+  unless (-d $config{archivedir}) {
+    mkdir ($config{archivedir}, 0700)
+      or die UVmessage::get("ERR_MKDIR", (DIR => $config{archivedir})) . "$!\n\n";
+  }
+
+  # mails are saved in file
+  # normally unixtime is sufficient for a unique file name, else append PID
+  my $ext = time;
+  opendir (ARCHIVE, $config{archivedir});
+  my @fertigfiles = readdir (ARCHIVE);
+  closedir (ARCHIVE);
+  # append PID if file name already exists
+  $ext .= "-$$" if (grep (/$ext/, @fertigfiles));
+  my $file = "$config{archivedir}/bounces-" . $ext;
+  UVreadmail::process($file, \&process_bounce, 2);   # 2 = POP3
+
+} else {
+  foreach my $file (@ARGV) {
+    UVreadmail::process($file, \&process_bounce, 3); # 3 = existing file
+  }
+}
+
+my $template = UVtemplate->new();
+
+foreach my $address (sort keys %bounces) {
+  my $name = $bounces{$address};
+  my $request = 0;
+  my $text;
+  if ($name eq '***request***') {
+    $name = '';
+    $text = UVmessage::get ("BOUNCE_BALLOT") . "\n";
+  } else {
+    $text = UVmessage::get ("BOUNCE_ACKMAIL") . "\n";
+  }
+  $name = ' ' unless($name);
+  $template->addListItem('bounces', name=>$name, mail=>$address, bouncetext=>$text);
+}
+
+print $template->processTemplate($config{'tpl_bouncelist'});
+exit 0;
+
+
+##############################################################################
+# Evaluate a bounce                                                          #
+# This sub is called from UVreadmail::process() for every mail               #
+# Parameters: voter address and name, date header (strings)                  #
+#             complete header and body (references to strings)               #
+##############################################################################
+
+sub process_bounce {
+  # last element of @_ is the body, other stuff not needed here
+  my $body = pop;
+  my ($address, $name);
+
+  # search body for voter name and address
+  if ($$body =~ /$config{addresstext}\s+(\S+@\S+)/) {
+    $address = $1;
+    if ($$body =~ /$config{nametext2}\s+(.*)$/m) {
+      $name = $1;
+      $bounces{$address} = $name;
+    } elsif ($$body =~ /$config{nametext}/) {
+      # Text from this config option does only appear in ballots,
+      # not in acknowledge mails. So this has to be a bounced ballot request
+      $bounces{$address} = '***request***';
+    } else {
+      $bounces{$address} = '';
+    }
+  }
+}
+
+
+END {
+  # delete lockfile
+  unlink $config{lockfile} if ($config{lockfile});
+}
+
+sub sighandler {
+  my ($sig) = @_;
+  die "\n\nSIG$sig: deleting lockfile and exiting\n\n";
+}
+
+
+##############################################################################
+# Print help text (options and syntax) on -h or --help                       #
+##############################################################################
+
+sub help {
+  print <<EOF;
+Usage: uvbounce.pl [-c config_file] [-f] DATEI1 [DATEI2 [...]]
+       uvbounce.pl -h
+
+Liest Bounces aus den uebergebenen Dateien oder aus einer POP3-Mailbox ein
+(Verhalten haengt von usevote.cfg ab) und generiert eine Liste von
+unzustellbaren Adressen, die an den 2. CfV oder das Result angehaengt
+werden kann. Falls POP3 in usevote.cfg eingeschaltet und die Option -f
+(siehe unten) nicht benutzt wurde, werden die uebergebenen Dateinamen
+ignoriert.
+
+  -c config_file   liest die Konfiguration aus config_file
+                   (usevote.cfg falls nicht angegeben)
+
+  -f, --file       liest die Bounces aus den uebergebenen Dateien, auch
+                   wenn in der Konfigurationsdatei POP3 eingeschaltet ist.
+                   Diese Option wird benoetigt, falls zwar die Stimmzettel
+                   per POP3 eingelesen werden sollen, nicht aber die Bounces.
+
+  -h, --help       zeigt diesen Hilfetext an
+
+EOF
+
+  exit 0;
+}
diff --git a/uvcfv.pl b/uvcfv.pl
new file mode 100644 (file)
index 0000000..74174c6
--- /dev/null
+++ b/uvcfv.pl
@@ -0,0 +1,282 @@
+#!/usr/bin/perl -w
+
+###############################################################################
+# UseVoteGer 4.09 Personalisierte Wahlscheine
+# (c) 2001-2005 Marc Langer <uv@marclanger.de>
+# 
+# This script package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Public License as published by the
+# Free Software Foundation.
+#
+# Use this script to read mails and send back a CfV with unique ballot id
+#
+# Many thanks to:
+# - Ron Dippold (Usevote 3.0, 1993/94)
+# - Frederik Ramm (German translation, 1994)
+# - Wolfgang Behrens (UseVoteGer 3.1, based on Frederik's translation, 1998/99)
+# - Cornell Binder for some good advice and code fragments
+#
+# This is a complete rewrite of UseVoteGer 3.1 in Perl (former versions were
+# written in C). Not all functions of Usevote/UseVoteGer 3.x are implemented!
+###############################################################################
+
+use strict;
+use Getopt::Long;
+use Digest::MD5 qw(md5_hex);
+use Text::Wrap qw(wrap $columns);
+use FindBin qw($Bin);
+use lib $Bin;
+use UVconfig;
+use UVmenu;
+use UVmessage;
+use UVreadmail;
+use UVsendmail;
+use UVtemplate;
+
+my %opt_ctl = ();
+
+print "\n$usevote_version Personalisierte Wahlscheine - (c) 2001-2005 Marc Langer\n\n";
+
+# unknown parameters remain in @ARGV (for "help")
+Getopt::Long::Configure(qw(pass_through bundling));
+
+# Put known parameters in %opt_ctl
+GetOptions(\%opt_ctl, qw(test t config-file=s c=s));
+
+# test mode? (default: no)
+my $test_only = $opt_ctl{test}          || $opt_ctl{t} || 0;
+
+# # Additional parameters or invalid options? Show help and exit.
+help() if (@ARGV);
+
+# Get name auf config file (default: usevote.cfg) and read it
+my $cfgfile   = $opt_ctl{'config-file'} || $opt_ctl{c} || "usevote.cfg";
+UVconfig::read_config($cfgfile);
+
+# Set columns for Text::Wrap
+$columns = $config{rightmargin};
+
+# read list of suspicious mail addresses from file
+my @bad_addr = UVconfig::read_badaddr();
+
+# exit if option "personal=1" in config file not set
+unless ($config{personal}) {
+  die wrap ('', '', UVmessage::get("ERR_NOTPERSONAL", (CFGFILE => $cfgfile))) . "\n\n";
+}
+
+# option -t used?
+if ($test_only) {
+  print_ballot();
+  exit 0;
+}
+
+# check for lock file
+if (-e $config{lockfile}) {
+  my $lockfile = $config{lockfile};
+
+  # don't delete lockfile in END block ;-)
+  $config{lockfile} = '';
+
+  # exit
+  die UVmessage::get("ERR_LOCK", (FILE=>$lockfile)) . "\n\n";
+}
+
+# safe exit (delete lockfile)
+$SIG{QUIT} = 'sighandler';
+$SIG{INT} = 'sighandler';
+$SIG{KILL} = 'sighandler';
+$SIG{TERM} = 'sighandler';
+$SIG{HUP} = 'sighandler';
+
+# create lock file
+open (LOCKFILE, ">$config{lockfile}");
+close (LOCKFILE);
+
+# check for tmp directory and domail file
+unless (-d $config{tmpdir}) {
+  mkdir ($config{tmpdir}, 0700)
+    or die UVmessage::get("ERR_MKDIR", (DIR => $config{tmpdir})) . "\n$!\n\n";
+}
+
+# generate filename for mail archive
+# normally unixtime is sufficient, if it is not unique append a number
+my $file = my $base = "anforderung-" . time();
+my $count = 0;
+while ($count<1000 && (-e "$config{archivedir}/$file" || -e "$config{tmpdir}/$file")) {
+  $file = "$base-" . ++$count;
+}
+die UVmessage::get("ERR_FILE_CREATION") . "\n\n" if ($count == 1000);
+
+unless ($config{pop3}) {
+  rename ($config{requestfile}, "$config{tmpdir}/$file")
+    or die UVmessage::get("ERR_RENAME_MAILFILE") . "$!\n\n";
+}
+  
+# wait, so that current mail deliveries can finalize
+sleep 2;
+
+# initiliaze random number generator
+srand;
+
+# read votes and process them
+# for each mail pass a reference to the sub to be called
+# The third parameter "1" shows that it is called from uvcfv.pl
+$count = UVreadmail::process("$config{tmpdir}/$file", \&process_request, 1);
+print "\n", UVmessage::get("CFV_NUMBER", (COUNT => $count)), "\n\n";
+UVsendmail::send();
+
+print UVmessage::get("INFO_TIDY_UP") . "\n\n";
+rename("$config{tmpdir}/$file", "$config{archivedir}/$file");
+chmod (0400, "$config{archivedir}/$file");
+
+exit 0;
+
+
+##############################################################################
+# Evaluate a ballot request                                                  #
+# Called from UVreadmail::process() for every mail                           #
+# Parameters: Voter address and name, date header of vote mail (strings),    #
+#             complete header and body (references to Strings)               #
+##############################################################################
+
+sub process_request {
+  my ($voter_addr, $voter_name, $h_date, $entity, $body) = @_;
+
+  my @header = split(/\n/, $entity->stringify_header);
+  my $head = $entity->head;
+  my $msgid = $head->get('Message-ID');
+  chomp($msgid) if ($msgid);
+
+  # found address?
+  if ($voter_addr) {
+    # check for suspicious addresses
+    foreach my $element (@bad_addr) {
+      if ($voter_addr =~ /^$element/) {
+        my (@votes, @set, $ballot_id); # irrelevant, but necessary for UVmenu::menu()
+        my @errors = ('SuspiciousAccountBallot');
+        my $res = UVmenu::menu(\@votes, \@header, $body, \$voter_addr, \$voter_name, \$ballot_id, \@set, \@errors);
+
+        # "Ignore": don't deliver a ballot
+        return 0 if ($res eq 'i');
+        if (@errors) {
+          # send error mail if address hasn't been accepted
+          my $template = UVtemplate->new();
+          $template->setKey('head' => $entity->stringify_header);
+          $template->setKey('body' => $$body);
+          my $msg = $template->processTemplate($config{tpl_invalid_account});
+          UVsendmail::mail($voter_addr, "Fehler", $msg, $msgid);
+          return 0;
+        }
+        last;
+      }
+    }
+  } else  {
+    # no address found in mail (non-RFC compliant?)
+    my (@votes, @set, $ballot_id); # irrelevant, but necessary for UVmenu::menu()
+    my @errors = ('InvalidAddressBallot');
+    my $res = UVmenu::menu(\@votes, \@header, $body, \$voter_addr, \$voter_name, \$ballot_id, \@set, \@errors);
+
+    # "ignore" or address not ok: no ballot can be sent
+    return 0 if (@errors || $res eq 'i');
+  }
+
+  my $subject = UVmessage::get("CFV_SUBJECT");
+  my $template = UVtemplate->new();
+  my $ballot_id = "";
+
+  #if ($ballot_id ne $ids{$voter_addr}) {
+  if ($ids{$voter_addr}) {
+    $ballot_id = $ids{$voter_addr};
+    $template->setKey('alreadysent' => 1) if ($ballot_id = $ids{$voter_addr});
+  } else {
+    # generate new ballot id from the MD5 sum of header, body and a random value
+    $ballot_id = md5_hex($entity->stringify_header . $body . rand 65535);
+    $ids{$voter_addr} = $ballot_id;
+
+    # write ballot id to file
+    open(IDFILE, ">>$config{idfile}")
+      or die UVmessage::get("CFV_ERRWRITE", (FILE => $config{idfile})) . "\n\n";
+    print IDFILE "$voter_addr $ballot_id\n";
+    close(IDFILE) or die UVmessage::get("CFV_ERRCLOSE") . "\n\n";
+  }
+
+  $template->setKey('ballotid' => $ballot_id);
+  $template->setKey('address'  => $voter_addr);
+  $template->setKey('bdsginfo' => $config{bdsginfo});
+
+  for (my $n=0; $n<@groups; $n++) {
+     $template->addListItem('groups', pos=>$n+1, group=>$groups[$n]);
+  }
+
+  my $msg = $template->processTemplate($config{'tpl_ballot_personal'});
+
+  # $config{voteaccount} is the Reply-To address:
+  UVsendmail::mail($voter_addr, $subject, $msg, $msgid, $config{voteaccount});
+
+}
+
+
+##############################################################################
+# Print dummy personalized ballot in STDOUT for checking purposes            #
+# Called if command line argument -t is present                              #
+##############################################################################
+
+sub print_ballot {
+  my $template = UVtemplate->new();
+
+  # generate new ballot id
+  my $ballot_id = md5_hex(rand 65535);
+
+  $template->setKey('ballotid' => $ballot_id);
+  $template->setKey('address'  => 'dummy@foo.invalid');
+  $template->setKey('bdsginfo' => $config{bdsginfo});
+
+  for (my $n=0; $n<@groups; $n++) {
+     $template->addListItem('groups', pos=>$n+1, group=>$groups[$n]);
+  }
+
+  my $msg = $template->processTemplate($config{'tpl_ballot_personal'});
+
+  print $msg, "\n";
+}
+
+
+##############################################################################
+# Handle Signals and delete lock files when exiting                          #
+##############################################################################
+
+END {
+  # delete lockfile
+  unlink $config{lockfile} if ($config{lockfile});
+}
+
+
+sub sighandler {
+  my ($sig) = @_;
+  die "\n\nSIG$sig: deleting lockfile and exiting\n\n";
+}
+
+
+##############################################################################
+# Print help text (options and syntax) on -h or --help                       #
+##############################################################################
+
+sub help {
+  print <<EOF;
+Usage: uvcfv.pl [-c config_file] [-t]
+       uvcfv.pl -h
+
+Liest Mailboxen ein und beantwortet alle Mails mit personalisierten CfVs.
+
+  -c config_file   liest die Konfiguration aus config_file
+                   (usevote.cfg falls nicht angegeben)
+
+  -t, --test       gibt einen Dummy-Wahlschein fuer Pruefzwecke aus
+
+  -h, --help       zeigt diesen Hilfetext an
+
+EOF
+
+  exit 0;
+}
diff --git a/uvcount.pl b/uvcount.pl
new file mode 100644 (file)
index 0000000..014494e
--- /dev/null
@@ -0,0 +1,475 @@
+#!/usr/bin/perl -w
+
+###############################################################################
+# UseVoteGer 4.09 Stimmauswertung
+# (c) 2001-2005 Marc Langer <uv@marclanger.de>
+# 
+# This script package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Public License as published by the
+# Free Software Foundation.
+#
+# Use this script to create voter lists and results.
+#
+# Many thanks to:
+# - Ron Dippold (Usevote 3.0, 1993/94)
+# - Frederik Ramm (German translation, 1994)
+# - Wolfgang Behrens (UseVoteGer 3.1, based on Frederik's translation, 1998/99)
+# - Cornell Binder for some good advice and code fragments
+#
+# This is a complete rewrite of UseVoteGer 3.1 in Perl (former versions were
+# written in C). Not all functions of Usevote/UseVoteGer 3.x are implemented!
+###############################################################################
+
+use strict;
+use Getopt::Long;
+use Digest::MD5 qw(md5_hex);
+use Date::Parse;
+use FindBin qw($Bin);
+use lib $Bin;
+use UVconfig;
+use UVmenu;
+use UVmessage;
+use UVtemplate;
+
+my %opt_ctl = ();
+
+print STDERR "\n$usevote_version Stimmauswertung - (c) 2001-2005 Marc Langer\n\n";
+
+# unrecognized parameters remain in @ARGV (for "help")
+Getopt::Long::Configure(qw(pass_through bundling));
+
+# recognized parameters are written into %opt_ctl
+GetOptions(\%opt_ctl, qw(l|list v|voters r|result n|nodup m|multigroup o|onegroup c|config-file=s f|result-file=s));
+
+if (!$opt_ctl{r} && ($opt_ctl{m} || $opt_ctl{o})) {
+  print STDERR "Die Optionen -m bzw. -o koennen nur in Verbindung mit -r verwendet werden!\n\n";
+  help(); # show help and exit
+} elsif (@ARGV || !($opt_ctl{l} || $opt_ctl{v} || $opt_ctl{r})) {
+  # additional parameters passed
+  help(); # show help and exit
+} elsif ($opt_ctl{l} && $opt_ctl{v}) {
+  print STDERR "Die Optionen -l und -v duerfen nicht zusammen verwendet werden!\n\n";
+  help(); # show help and exit
+} elsif ($opt_ctl{m} && $opt_ctl{o}) {
+  print STDERR "Die Optionen -m und -o duerfen nicht zusammen verwendet werden!\n\n";
+  help(); # show help and exit
+}
+
+# get config file name (default: usevote.cfg) and read it
+my $cfgfile   = $opt_ctl{c} || "usevote.cfg";
+UVconfig::read_config($cfgfile);
+
+# Overwrite result file if started with option -f
+$config{resultfile} = $opt_ctl{f} if ($opt_ctl{f});
+
+read_resultfile($opt_ctl{n});
+
+exit 0;
+
+
+##############################################################################
+# Read result file and (optionally) sort out duplicate votes                 #
+# Parameters: 1 if no duplicates should be deleted, else 0                   #
+##############################################################################
+
+sub read_resultfile {
+  my ($nodup) = @_;
+  my $num = 0;
+  my $invalid = '';
+  my $inv_count = 0;
+  my $validcount = 0;
+  my $vote = {};
+  my @votes = ();
+  my @deleted = ();
+  my @votecount = ();
+  my %vnames = ();
+  my %vaddr = ();
+  my %lists = (J => '', N => '', E => '');     # for one-group format
+  my $list = '';                               # for multiple-group format
+  my %varname = (J => 'yes', N => 'no', E => 'abstain');
+
+  # Initialization of the sum array
+  for (my $group=0; $group<@groups; $group++) {
+    $votecount[$group]->{J} = 0;
+    $votecount[$group]->{N} = 0;
+    $votecount[$group]->{E} = 0;
+  }
+
+  open(FILE, "<$config{resultfile}")
+    or die UVmessage::get("COUNT_ERR_OPEN", (FILE=>$config{resultfile})) . "\n\n";
+
+  # Read file
+  while(<FILE>) {
+    chomp;
+    $num++;
+
+    unless (/^(\w): (.*)$/) {
+      print STDERR UVmessage::get("COUNT_ERR_RESULT",
+                                  (FILE=>$config{resultfile}, LINE=>$num)) . "\n";
+      next;
+    }
+
+    my $field = $1;
+    my $content = $2;
+    $vote->{$field} = $content;
+
+    # End of a paragraph reached?
+    if ($field eq 'S') {
+
+      # The array @votes countains references to the hashes
+      push (@votes, $vote);
+
+      # For sorting and duplicate detection indexes are build from address and name.
+      # These are hashes containing references to an array of index numbers of
+      # the @votes array.
+      #
+      # Example: $vnames{'marc langer'}->[0] = 2
+      #          $vnames{'marc langer'}->[1] = 10
+      # Meaning: $votes[2] und $votes[10] contain votes of Marc Langer
+
+      push (@{$vnames{lc($vote->{N})}}, $#votes);
+
+      # Conversion in lower case, so that words with an upper case first
+      # letter are not at the top after sorting
+      push (@{$vaddr{lc($vote->{A})}}, $#votes);
+
+      # reset $vote, begin a new vote
+      $vote = {};
+    }
+  }    
+
+  close(FILE);
+
+  # delete cancelled votes
+  foreach my $addr (keys %vaddr) {
+    # Run through all votes belonging to a mail address and search for cancellation
+    for (my $n=0; $n<=$#{$vaddr{$addr}}; $n++) {
+      if ($votes[$vaddr{$addr}->[$n]]->{S} =~ /^\*/) {
+        # delete from array
+        push(@deleted, splice(@{$vaddr{$addr}}, 0, $n+1));
+        $n=-1;
+      }
+    }
+  }
+
+  # sort out duplicates?
+  unless ($nodup) {
+
+    # search for duplicate addresses
+    foreach my $addr (keys %vaddr) {
+
+      # Run through all votes belonging to a mail address.
+      # If one vote is deleted it has also to be deleted from the array
+      # so that the following addresses move up. In the other case the
+      # counter is incremented as long as further votes are to be compared.
+
+      my $n=0;
+      while ($n<$#{$vaddr{$addr}}) {
+
+        my $ask = 0;
+
+        if ($votes[$vaddr{$addr}->[$n]]->{S} =~ /!/ ||
+            $votes[$vaddr{$addr}->[$n+1]]->{S} =~ /!/)  {
+
+          # One of the votes is invalid: Ask votetaker
+          $ask = 1;
+
+        } else {
+
+          # Convert date into unixtime (str2time is located in Date::Parse)
+          my $date1 = str2time($votes[$vaddr{$addr}->[$n]]->{D});
+          my $date2 = str2time($votes[$vaddr{$addr}->[$n+1]]->{D});
+
+          # compare dates
+          my $order = $date1 <=> $date2;
+
+          # first date is earlier
+          if ($order == -1) {
+            push(@deleted, $vaddr{$addr}->[$n]);
+            # delete first element from the array
+            splice(@{$vaddr{$addr}}, $n, 1);
+
+          # second date is earlier
+          } elsif ($order == 1) {
+            push(@deleted, $vaddr{$addr}->[$n+1]);
+            # delete second element from the array
+            splice(@{$vaddr{$addr}}, $n+1, 1);
+
+          # both are equal (ask votetaker)
+          } else {
+            $ask = 1;
+          }
+
+        }
+
+        # Has votetaker to be asked?
+        if ($ask) {
+          my $default = 0;
+          my $res = UVmenu::dup_choice($votes[$vaddr{$addr}->[0]],
+                                       $votes[$vaddr{$addr}->[1]],
+                                       $default);
+
+          if ($res == 1) {
+            push(@deleted, $vaddr{$addr}->[0]);
+            # delete first element from the array
+            splice(@{$vaddr{$addr}}, $n, 1);
+
+          } elsif ($res == 2) {
+            push(@deleted, $vaddr{$addr}->[1]);
+            # delete second element from the array
+            splice(@{$vaddr{$addr}}, $n+1, 1);
+
+          } else {
+            # don't delete anything: increment counter
+            $n++;
+          }
+        }
+      }
+    }
+
+    # the same for equal names:
+    foreach my $name (keys %vnames) {
+      my $n = 0;
+      while ($n<$#{$vnames{$name}}) {
+
+        # check if vote was already deleted by prior address sorting
+        if (grep(/^$vnames{$name}->[$n]$/, @deleted)) {
+          # delete first element from the array
+          splice(@{$vnames{$name}}, $n, 1);
+          next;
+
+        } elsif (grep(/^$vnames{$name}->[$n+1]$/, @deleted)) {
+          # delete second element from the array
+          splice(@{$vnames{$name}}, $n+1, 1);
+          next;
+        }
+
+        # Convert date into unixtime (str2time is located in Date::Parse)
+        my $date1 = str2time($votes[$vnames{$name}->[$n]]->{D});
+        my $date2 = str2time($votes[$vnames{$name}->[$n+1]]->{D});
+
+        # Set default for menu choice to the earlier vote
+        my $default = ($date2 < $date1) ? 2 : 0;
+
+        my $res = UVmenu::dup_choice($votes[$vnames{$name}->[$n]],
+                                     $votes[$vnames{$name}->[$n+1]],
+                                     $default);
+
+        # delete first
+        if ($res == 1) {
+          push(@deleted, $vnames{$name}->[$n]);
+          splice(@{$vnames{$name}}, $n, 1);
+
+        # delete second
+        } elsif ($res == 2) {
+          push(@deleted, $vnames{$name}->[$n+1]);
+          # delete second element from the array
+          splice(@{$vnames{$name}}, $n+1, 1);
+       
+        # don't delete anything: increment counter
+        } else {
+          $n++;
+        }
+      }
+    }
+
+    print STDERR UVmessage::get("COUNT_DELETED", (NUM=>scalar @deleted)), "\n\n";
+  }
+
+  # Count votes and generate voter list
+  
+  my $list_tpl = UVtemplate->new();
+  $list_tpl->setKey('groupcount' => scalar @groups);
+
+  # reversed order as caption string for last column comes first
+  for (my $n=$#groups; $n>=0; $n--) {
+    $list_tpl->addListItem('groups', pos=>@groups-$n, group=>$groups[$n]);
+  }
+   
+  # loop through all addresses
+  foreach my $addr (sort keys %vaddr) {
+
+    # loop through all votes for every address
+    for (my $n=0; $n<@{$vaddr{$addr}}; $n++) {
+
+      # Ignore vote if already deleted.
+      # If $nodup is not set one single vote should remain
+      unless (grep(/^$vaddr{$addr}->[$n]$/, @deleted)) {
+
+        # extract $vote for simplier code
+        my $vote = $votes[$vaddr{$addr}->[$n]];
+
+        # vote is invalid if there is an exclamation mark
+        if ($vote->{S} =~ /!/) {
+          $inv_count++;
+        } else {
+          # split vote string into single votes and count
+          my @splitvote = split(//, $vote->{S});
+          if (@groups != @splitvote) {
+            die UVmessage::get("COUNT_ERR_GROUPCOUNT", (ADDR=>$addr, NUM1=>scalar @splitvote,
+                      NUM2=>scalar @groups), RESULTFILE=>$config{resultfile}), "\n\n";
+          }
+          for (my $group=0; $group<@splitvote; $group++) {
+            $votecount[$group]->{$splitvote[$group]}++;
+          }
+          $validcount++;
+        }
+
+        if ($opt_ctl{l} || $opt_ctl{v}) {
+
+          # vote is invalid if there is an exclamation mark
+          if ($vote->{S} =~ /!/) {
+            $list_tpl->addListItem('invalid', (name=>$vote->{N}, mail=>$vote->{A}, reason=>$vote->{S}));
+
+          # in other cases the vote is valid: generate list of votes
+          } else {
+
+            # one-group or multiple-group format?
+            # must use multiple-group data structure for voter list (2. CfV)!
+            if ($#groups || $opt_ctl{l}) {
+              $list_tpl->addListItem('multi', (name=>$vote->{N}, mail=>$vote->{A}, vote=>$vote->{S}));
+            } else {
+              my ($votestring) = split(//, $vote->{S});
+              $list_tpl->addListItem($varname{$votestring}, (name=>$vote->{N}, mail=>$vote->{A}));
+            }
+
+          }
+        }
+      }
+    }
+  }
+
+  if ($opt_ctl{r}) {
+
+    my $tplname;
+    my $result_tpl = UVtemplate->new();
+    $result_tpl->setKey('votename' => $config{votename});
+    $result_tpl->setKey('numvalid' => $validcount);
+    $result_tpl->setKey ('numinvalid', $inv_count);
+
+    # proportional vote?
+    if ($config{proportional}) {
+      $tplname = $config{'tpl_result_prop'};
+      for (my $group=0; $group<@votecount; $group++) {
+        # calculate conditions
+        my $yes = $votecount[$group]->{J};
+        my $no = $votecount[$group]->{N};
+        my $cond1 = eval $config{condition1};
+        my $proportion = 0;
+
+        # don't evaluate if division by zero
+        unless ($config{prop_formula} =~ m#.+/(.+)# && eval($1)==0) {
+          $proportion = eval $config{prop_formula};
+        }
+  
+        # generate result line
+        $result_tpl->addListItem('count', (yes        => $votecount[$group]->{J},
+                                           no         => $votecount[$group]->{N},
+                                           cond1      => $cond1,
+                                           proportion => $proportion,
+                                           result     => '', # must be set manually
+                                           group      => $groups[$group]));
+      }
+
+    } else {
+      # use one-group or multiple-group format?
+      if (@groups == 1 && (!($config{multigroup} || $opt_ctl{m}) || $opt_ctl{o})) {
+        $tplname = $config{'tpl_result_single'};
+        my $yes = $votecount[0]->{J};
+        my $no = $votecount[0]->{N};
+        my $acc1 = eval $config{condition1};
+        my $acc2 = eval $config{condition2};
+        $result_tpl->setKey('yes' => $votecount[0]->{J});
+        $result_tpl->setKey('no' => $votecount[0]->{N});
+        $result_tpl->setKey('numabstain' => $votecount[0]->{E});
+        $result_tpl->setKey('cond1' => $acc1);
+        $result_tpl->setKey('cond2' => $acc2);
+
+      } else {
+        $tplname = $config{'tpl_result_multi'};
+        $result_tpl->setKey('numabstain' => 0);
+
+        for (my $group=0; $group<@votecount; $group++) {
+          # calculate conditions
+          my $yes = $votecount[$group]->{J};
+          my $no = $votecount[$group]->{N};
+          my $cond1 = eval $config{condition1};
+          my $cond2 = eval $config{condition2};
+  
+          # generate result line
+          $result_tpl->addListItem('count', (yes    => $votecount[$group]->{J},
+                                             no     => $votecount[$group]->{N},
+                                             cond1  => $cond1,
+                                             cond2  => $cond2,
+                                             result => ($cond1 && $cond2),
+                                             group  => $groups[$group]));
+
+        }
+      }
+
+      $result_tpl->setKey ('numabstain', $votecount[0]->{E}) if (@votecount == 1);
+    }
+
+    print $result_tpl->processTemplate($tplname);
+
+  }
+
+  if ($opt_ctl{v}) {
+
+    # one-group or multiple-group format?
+    if ($#groups) {
+      print $list_tpl->processTemplate($config{'tpl_votes_multi'});
+    } else {
+      print $list_tpl->processTemplate($config{'tpl_votes_single'});
+    }
+
+  } elsif ($opt_ctl{l}) {
+    print $list_tpl->processTemplate($config{'tpl_voterlist'});
+  }
+
+}
+
+
+##############################################################################
+# Print help text (options and syntax) on -h or --help                       #
+##############################################################################
+
+sub help {
+  print STDERR <<EOF;
+Usage: uvcount.pl [-c config_file] [-f result_file] [-l | -v] [-r [-m | -o]] [-n]
+       uvcount.pl -h
+
+Zaehlt Stimmen und gibt Waehlerlisten aus.
+
+  -c config_file   liest die Konfiguration aus config_file
+                   (usevote.cfg falls nicht angegeben)
+
+  -f result_file   liest die Stimmen aus result_file (ueberschreibt
+                   die "resultfile"-Angabe aus der Konfigurationsdatei)
+
+  -l, --list       Gibt eine Liste aller Waehler aus (ohne Stimmen).
+
+  -v, --voters     Wie -l, aber mit Angabe der abgegebenen Stimmen.
+
+  -r, --result     Ausgabe des Endergebnisses (kann mit -l oder -v
+                   kombiniert werden).
+
+  -m, --multigroup Benutzt auch bei Eingruppenabstimmungen das
+                   Mehrgruppenformat beim Endergebnis (ueberschreibt
+                   die Einstellung aus usevote.cfg).
+                   Nur in Kombination mit -r verwendbar, schliesst -o aus.
+
+  -o, --onegroup   Benutzt bei Eingruppenabstimmungen immer das
+                   Eingruppenformat beim Endergebnis (ueberschreibt
+                   die Einstellung aus usevote.cfg).
+                   Nur in Kombination mit -r verwendbar, schliesst -m aus.
+
+  -n, --nodup      Verzichtet auf das Aussortieren von doppelten
+                   Stimmabgaben. Nicht empfohlen!
+
+  -h, --help       zeigt diesen Hilfetext an
+
+EOF
+
+  exit 0;
+}
diff --git a/uvvote.pl b/uvvote.pl
new file mode 100644 (file)
index 0000000..a86ef43
--- /dev/null
+++ b/uvvote.pl
@@ -0,0 +1,595 @@
+#!/usr/bin/perl -w
+
+###############################################################################
+# UseVoteGer 4.09 Wahldurchfuehrung
+# (c) 2001-2005 Marc Langer <uv@marclanger.de>
+# 
+# This script package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Public License as published by the
+# Free Software Foundation.
+#
+# The script reads usenet vote ballots from mailbox files. The format
+# can be set by changing the option "mailstart".
+#
+# Many thanks to:
+# - Ron Dippold (Usevote 3.0, 1993/94)
+# - Frederik Ramm (German translation, 1994)
+# - Wolfgang Behrens (UseVoteGer 3.1, based on Frederik's translation, 1998/99)
+# - Cornell Binder for some good advice and code fragments
+#
+# This is a complete rewrite of UseVoteGer 3.1 in Perl (former versions were
+# written in C). Not all functions of Usevote/UseVoteGer 3.x are implemented!
+###############################################################################
+
+use strict;
+use Getopt::Long;
+use Text::Wrap qw(wrap $columns);
+use FindBin qw($Bin);
+use lib $Bin;
+use UVconfig;
+use UVmenu;
+use UVmessage;
+use UVreadmail;
+use UVsendmail;
+use UVrules;
+use UVtemplate;
+
+my $clean = 0;
+my %opt_ctl = ();
+
+print "\n$usevote_version Wahldurchfuehrung - (c) 2001-2005 Marc Langer\n\n";
+
+# unknown parameters remain in @ARGV (for "help")
+Getopt::Long::Configure(qw(pass_through bundling));
+
+# Put known parameters in %opt_ctl
+GetOptions(\%opt_ctl, qw(test t config-file=s c=s));
+
+# Get name auf config file (default: usevote.cfg) and read it
+my $cfgfile   = $opt_ctl{'config-file'} || $opt_ctl{c} || "usevote.cfg";
+
+# test mode? (default: no)
+my $test_only = $opt_ctl{test}          || $opt_ctl{t} || 0;
+
+if (@ARGV){
+  # additional parameters passed
+
+  if ($ARGV[0] eq "clean") {
+    $clean = 1;
+  } else {
+    # print help and exit program
+    help();
+  }
+}
+
+UVconfig::read_config($cfgfile, 1);  # read config file, redirect errors to log
+UVrules::read_rulefile();            # read rules from file
+
+# read list of suspicious mail addresses from file
+my @bad_addr = UVconfig::read_badaddr();
+
+# option -t used?
+if ($test_only) {
+  UVconfig::test_config();
+  exit 0;
+}
+
+# check for lock file
+if (-e $config{lockfile}) {
+  my $lockfile = $config{lockfile};
+
+  # don't delete lockfile in END block ;-)
+  $config{lockfile} = '';
+
+  # exit
+  die UVmessage::get("ERR_LOCK", (FILE=>$lockfile)) . "\n\n";
+}
+
+# safe exit (delete lockfile)
+$SIG{QUIT} = 'sighandler';
+$SIG{INT} = 'sighandler';
+$SIG{KILL} = 'sighandler';
+$SIG{TERM} = 'sighandler';
+$SIG{HUP} = 'sighandler';
+
+# create lock file
+open (LOCKFILE, ">$config{lockfile}");
+close (LOCKFILE);
+
+# Set columns for Text::Wrap
+$columns = $config{rightmargin};
+
+# check for tmp and archive directory
+unless (-d $config{archivedir}) {
+  mkdir ($config{archivedir}, 0700)
+    or die UVmessage::get("ERR_MKDIR", (DIR=>$config{archivedir})) . "$!\n\n";
+}
+
+unless (-d $config{tmpdir}) {
+  mkdir ($config{tmpdir}, 0700)
+    or die UVmessage::get("ERR_MKDIR", (DIR=>$config{tmpdir})) . "$!\n\n";
+}
+
+if ($clean) {
+  # Program has been startet with "clean" option:
+  # save votes and send out acknowledge mails
+  make_clean();
+
+} else {
+  # normal processing
+
+  # generate file names for result file
+  # normally unixtime is sufficient, if it is not unique append our PID
+  my $ext = time;
+
+  opendir (TMP, $config{tmpdir});
+  my @tmpfiles = readdir (DIR);
+  closedir (TMP);
+  opendir (FERTIG, $config{archivedir});
+  my @fertigfiles = readdir (FERTIG);
+  closedir (FERTIG);
+
+  # append PID if necessary
+  $ext .= "-$$" if (grep (/$ext/, @tmpfiles) || grep (/$ext/, @fertigfiles));
+
+  my $thisresult = "ergebnis-" . $ext;
+  my $thisvotes = "stimmen-" . $ext;
+  
+  # POP3 not activated: rename votes file
+  unless ($config{pop3}) {
+    print UVmessage::get("VOTE_RENAMING_MAILBOX"), "\n";
+    rename ($config{votefile}, "$config{tmpdir}/$thisvotes")
+       or die UVmessage::get("ERR_RENAME_MAILFILE") . "$!\n\n";
+  
+    #  wait, so that current mail deliveries can finalize
+    sleep 2;
+  }
+
+  # open results file
+  open (RESULT, ">>$config{tmpdir}/$thisresult")
+     or die UVmessage::get("VOTE_WRITE_RESULTS", (FILE=>$thisresult)) . "\n\n";
+
+  # read votes and process them
+  # for each mail pass a reference to the sub to be called
+  my $count = UVreadmail::process("$config{tmpdir}/$thisvotes", \&process_vote, 0);
+
+  close (RESULT)
+     or print STDERR UVmessage::get("VOTE_CLOSE_RESULTS", (FILE=>$thisresult)) . "\n";
+
+  # no mails: exit here
+  unless ($count) {
+    print UVmessage::get("VOTE_NO_VOTES") . "\n\n";
+    exit 0;
+  }
+
+  if ($config{onestep}) {
+    # everything should be done in one step
+    print "\n" . UVmessage::get("VOTE_NUM_VOTES", (COUNT=>$count)) . "\n";
+    make_clean();
+
+  } else {
+
+    print "\n", UVmessage::get("VOTE_NOT_SAVED", (COUNT=>$count)), "\n",
+          wrap('', '', UVmessage::get("VOTE_FIRSTRUN")), "\n\n";
+  }
+}
+
+exit 0;
+
+END {
+  close (STDERR);
+
+  # delete lockfile
+  unlink $config{lockfile} if ($config{lockfile});
+
+  if (-s $config{errorfile}) {
+    # errors ocurred
+    print '*' x $config{rightmargin}, "\n",
+          UVmessage::get("VOTE_ERRORS",(FILE => $config{errorfile})), "\n",
+          '*' x $config{rightmargin}, "\n\n";
+    open (ERRFILE, "<$config{errorfile}");
+    print <ERRFILE>;
+    close (ERRFILE);
+    print "\n";
+  } else {
+    unlink ($config{errorfile});
+  }
+}
+
+
+sub sighandler {
+  my ($sig) = @_;
+  die "\n\nSIG$sig: deleting lockfile and exiting\n\n";
+} 
+
+
+##############################################################################
+# Evaluation of a vote mail                                                  #
+# Called from UVreadmail::process() for each mail.                           #
+# Parameters: voter address and name, date header of the vote mail (strings) #
+#             complete header (reference to array), body (ref. to strings)   #
+##############################################################################
+
+sub process_vote {
+  my ($voter_addr, $voter_name, $h_date, $entity, $body) = @_;
+
+  my @header = split(/\n/, $entity->stringify_header);
+  my $head = $entity->head;
+  my $msgid = $head->get('Message-ID');
+  chomp($msgid) if ($msgid);
+
+  my @votes = ();              # the votes
+  my @set;                     # interactively changed fields
+  my @errors = ();             # recognized errors (show menu for manual action)
+  my $onevote = 0;             # 0=no votes, 1=everything OK, 2=vote cancelled
+  my $voteerror = "";          # error message in case of invalid vote
+  my $ballot_id = "";          # ballot id (German: Wahlscheinkennung)
+
+  # found address?
+  if ($voter_addr) {
+    # search for suspicious addresses
+    foreach my $element (@bad_addr) {
+      if ($voter_addr =~ /^$element/) {
+        push (@errors, 'SuspiciousAccount');
+        last;
+      }
+    }
+  } else {
+    # found no address in mail (perhaps violates RFC?)
+    push (@errors, 'InvalidAddress');
+  }
+
+  # personalized ballots?
+  if ($config{personal}) {
+    if ($$body =~ /$config{ballotidtext}\s+([a-z0-9]+)/) {
+      $ballot_id = $1;
+      # Address registered? ($ids is set in UVconfig.pm)
+      if ($ids{$voter_addr}) {
+        push (@errors, 'WrongBallotID') if ($ids{$voter_addr} ne $ballot_id);
+      } else {
+        push (@errors, 'AddressNotRegistered');
+      }
+    } else {
+      push (@errors, 'NoBallotID');
+    }
+  }
+      
+  # evaluate vote strings
+  for (my $n=0; $n<@groups; $n++) {
+
+    # counter starts at 1 in ballot
+    my $votenum = $n+1;
+    my $vote = "";
+    
+    # a line looks like this: #1 [ VOTE ] Group
+    # matching only on number and vote, because of line breaks likely
+    # inserted by mail programs
+
+    # duplicate vote?
+    if ($$body =~ /#$votenum\W*?\[\s*?(\w+)\s*?\].+?#$votenum\W*?\[\s*?(\w+)\s*?\]/s) {
+      push (@errors, "DuplicateVote") if ($1 ne $2);
+    }
+
+    # this matches on a single appearance:
+    if ($$body =~ /#$votenum\W*?\[(.+)\]/) {
+      # one or more vote strings were found
+      $onevote = 1;
+      my $votestring = $1;
+      if ($votestring =~ /^\W*$config{ja_stimme}\W*$/i) {
+        $vote = "J";
+      } elsif ($votestring =~ /^\W*$config{nein_stimme}\W*$/i) {
+        $vote = "N";
+      } elsif ($votestring =~ /^\W*$config{enth_stimme}\W*$/i) {
+        $vote = "E";
+      } elsif ($votestring =~ /^\s*$/) {
+        # nothing has been entered between the [ ]
+        $vote = "E";
+      } elsif ($votestring =~ /^\W*$config{ann_stimme}\W*$/i) {
+        $vote = "A";
+        $onevote = 2;        # Cancelled vote: set $onevote to 2
+      } elsif (!$votes[$n]) {
+        # vote not recognized
+        $vote = "E";
+        push (@errors, 'UnrecognizedVote #' . $votenum . "#$votestring");
+      }
+      push (@votes, $vote);
+    } else {
+      # vote not found
+      push (@votes, 'E');
+      push (@errors, 'UnrecognizedVote #' . $votenum . '#(keine Stimmabgabe fuer "'
+           . $groups[$n] . '" gefunden)');
+    }
+  }
+
+  if ($onevote == 0) {
+    push (@errors, "NoVote") unless ($onevote);
+  } elsif ($onevote == 1) {
+    # check rules
+    my $rule = UVrules::rule_check(\@votes);
+    push (@errors, "ViolatedRule #$rule") if ($rule);
+  } else {
+    # cancelled vote: replace all votes with an A
+    @votes = split(//, 'A' x scalar @votes);
+  }
+
+  # Evaluate Data Protection Law clause (not on cancelled votes)
+  if ($config{bdsg} && $onevote<2) {
+
+    # Text in ballot complete and clause accepted?
+    # Should read like this: #a [ STIMME ] Text
+    # (Text is configurable in usevote.cfg)
+    unless ($$body =~ /$bdsg_regexp/s &&
+            $$body =~ /#a\W*?\[\W*?$config{ja_stimme}\W*?\]\W*?$bdsg2_regexp/is) {
+
+      push (@errors, 'InvalidBDSG');
+    }
+  }
+
+  # Name in body?
+  if ($$body =~ /($config{nametext}|$config{nametext2})( |\t)*(\S.+?)$/m) {
+    $voter_name = $3;
+    $voter_name =~ s/^\s+//; # strip leading spaces
+    $voter_name =~ s/\s+$//; # strip trailing spaces
+  }
+
+  if ($voter_name) {
+    # Name invalid?
+    push (@errors, 'InvalidName') unless ($voter_name =~ /$config{name_re}/);
+  } else {
+    # no name found:
+    push (@errors, 'NoName') unless ($voter_name);
+  }
+
+  # Errors encountered?
+  if (@errors) {
+    my $res = UVmenu::menu(\@votes, \@header, $body, \$voter_addr, \$voter_name,
+                           \$ballot_id, \@set, \@errors);
+    return 0 if ($res eq 'i');      # "Ignore": Ignore vote, don't save
+
+    my $tpl;
+
+    # Check Ballot ID stuff
+    if ($config{personal}) {
+      if ($ballot_id) {
+        if ($ids{$voter_addr}) {
+          if ($ids{$voter_addr} ne $ballot_id) {
+            $voteerror = UVmessage::get("VOTE_INVALID_BALLOTID");
+            $tpl = $config{tpl_wrong_ballotid};
+          }
+        } else {
+          $voteerror = UVmessage::get("VOTE_UNREGISTERED_ADDRESS");
+          $tpl = $config{tpl_addr_reg};
+        }
+      } else {
+        $voteerror = UVmessage::get("VOTE_MISSING_BALLOTID");
+        $tpl = $config{tpl_no_ballotid};
+      }
+  
+      # generate error mail (if error occurred)
+      if ($tpl) {
+        my $template = UVtemplate->new();
+        $template->setKey('head' => $entity->stringify_header);
+        $template->setKey('body' => $$body);
+        my $msg = $template->processTemplate($tpl);
+        UVsendmail::mail($voter_addr, "Fehler", $msg, $msgid) if ($config{voteack});
+      }
+    }
+  }
+  
+  # Check rules and send error mail unless rule violation was ignored in the use menu
+  # or another error was detected
+  if (grep(/ViolatedRule/, @errors) && !$voteerror && (my $rule = UVrules::rule_check(\@votes))) {
+    $voteerror = UVmessage::get("VOTE_VIOLATED_RULE", (RULE=>$rule));
+    my $template = UVtemplate->new();
+    $template->setKey('body'  => $$body);
+    $template->setKey('rules' => UVrules::rule_print($rule-1));
+    my $msg = $template->processTemplate($config{tpl_rule_violated});
+    UVsendmail::mail($voter_addr, "Fehler", $msg, $msgid) if ($config{voteack});
+  }
+
+  if (!$voteerror && @errors) {
+
+    # turn errors array into hash
+
+    my %error;
+    foreach my $error (@errors) {
+      $error{$error} = 1;
+    }
+
+    # Check uncorrected errors
+    if ($error{InvalidBDSG}) {
+      my $template = UVtemplate->new();
+      my $msg = $template->processTemplate($config{tpl_bdsg_error});
+      UVsendmail::mail($voter_addr, "Fehler", $msg, $msgid) if ($config{voteack});
+      return 0;
+    } elsif ($error{NoVote}) {
+      $voteerror = UVmessage::get("VOTE_NO_VOTES");
+      my $template = UVtemplate->new();
+      $template->setKey('body'  => $$body);
+      my $msg = $template->processTemplate($config{tpl_no_votes});
+      UVsendmail::mail($voter_addr, "Fehler", $msg, $msgid) if ($config{voteack});
+    } elsif ($error{SuspiciousAccount}) {
+      $voteerror = UVmessage::get("VOTE_INVALID_ACCOUNT");
+      my $template = UVtemplate->new();
+      $template->setKey('head' => $entity->stringify_header);
+      $template->setKey('body'  => $$body);
+      my $msg = $template->processTemplate($config{tpl_invalid_account});
+      UVsendmail::mail($voter_addr, "Fehler", $msg, $msgid) if ($config{voteack});
+    } elsif ($error{InvalidAddress}) {
+      $voteerror = UVmessage::get("VOTE_INVALID_ADDRESS");
+    } elsif ($error{InvalidName}) {
+      $voteerror = UVmessage::get("VOTE_INVALID_REALNAME");
+      my $template = UVtemplate->new();
+      $template->setKey('head' => $entity->stringify_header);
+      $template->setKey('body'  => $$body);
+      my $msg = $template->processTemplate($config{tpl_invalid_name});
+      UVsendmail::mail($voter_addr, "Fehler", $msg, $msgid) if ($config{voteack});
+    } elsif ($error{DuplicateVote}) {
+      $voteerror = UVmessage::get("VOTE_DUPLICATES");
+      my $template = UVtemplate->new();
+      $template->setKey('head' => $entity->stringify_header);
+      $template->setKey('body'  => $$body);
+      my $msg = $template->processTemplate($config{tpl_multiple_votes});
+      UVsendmail::mail($voter_addr, "Fehler", $msg, $msgid) if ($config{voteack});
+    }
+  }
+
+  # check voter name
+  unless ($voter_name || $voteerror) {
+    $voteerror = UVmessage::get("VOTE_MISSING_NAME");
+    my $template = UVtemplate->new();
+    $template->setKey('head' => $entity->stringify_header);
+    $template->setKey('body'  => $$body);
+    my $msg = $template->processTemplate($config{tpl_invalid_name});
+    UVsendmail::mail($voter_addr, "Fehler", $msg, $msgid) if ($config{voteack});
+  }
+
+  # set mark for cancelled vote
+  $onevote = 2 if ($votes[0] eq 'A');
+
+  # create comment line for result file
+  my $comment;
+  if ($config{personal}) {
+    # Personalized Ballots: insert ballot id
+    $comment = "($ballot_id)";
+  } else {
+    $comment = "()";
+  }
+
+  if (@set) {
+    $comment .= ' '.UVmessage::get("VOTE_FILE_COMMENT", (FIELDS => join(', ', @set)));
+  }
+
+  # write result file
+  print RESULT "A: $voter_addr\n";
+  print RESULT "N: $voter_name\n";
+  print RESULT "D: $h_date\n";
+  print RESULT "K: $comment\n";
+
+  # invalid vote?
+  if ($voteerror) {
+    print RESULT "S: ! $voteerror\n";
+
+  # cancelled vote?
+  } elsif ($onevote == 2) {
+    print RESULT "S: * Annulliert\n";
+
+    if ($config{voteack}) {
+      # send cancellation acknowledge
+      my $template = UVtemplate->new();
+      my $msg = $template->processTemplate($config{tpl_cancelled});
+      UVsendmail::mail($voter_addr, "Bestaetigung", $msg, $msgid);
+    }
+
+  } else {
+    print RESULT "S: ", join ("", @votes), "\n";
+
+    # send acknowledge mail?
+    if ($config{voteack}) {
+
+      my $template = UVtemplate->new();
+      $template->setKey(ballotid        => $ballot_id);
+      $template->setKey(address         => $voter_addr);
+      $template->setKey(name            => $voter_name);
+
+      for (my $n=0; $n<@groups; $n++) {
+        my $vote = $votes[$n];
+        $vote =~ s/^J$/JA/;
+        $vote =~ s/^N$/NEIN/;
+        $vote =~ s/^E$/ENTHALTUNG/;
+        $template->addListItem('groups', pos=>$n+1, vote=>$vote, group=>$groups[$n]);
+      }
+   
+      my $msg = $template->processTemplate($config{'tpl_ack_mail'});
+      UVsendmail::mail($voter_addr, "Bestaetigung", $msg, $msgid);
+    }
+  }
+}
+
+
+##############################################################################
+# Send out acknowledge mails and tidy up (we're called as "uvvote.pl clean") #
+##############################################################################
+
+sub make_clean {
+
+  # send mails
+  UVsendmail::send();
+
+  print UVmessage::get("INFO_TIDY_UP"), "\n";
+
+  # search unprocessed files
+  opendir (DIR, $config{tmpdir});
+  my @files = readdir DIR;
+  closedir (DIR);
+
+  my @resultfiles = grep (/^ergebnis-/, @files);
+  my @votefiles = grep (/^stimmen-/, @files);
+
+  unless (@resultfiles) {
+    print wrap('', '', UVmessage::get("VOTE_NO_NEW_RESULTS")), "\n\n";
+    return 0;
+  }   
+
+  foreach my $thisresult (@resultfiles) {
+    chmod (0400, "$config{tmpdir}/$thisresult");
+    rename "$config{tmpdir}/$thisresult", "$config{archivedir}/$thisresult"
+      or die UVmessage::get("VOTE_MOVE_RESULTFILE", (FILE=>$thisresult)) . "$!\n\n";
+  }
+
+  foreach my $thisvotes (@votefiles) {
+    chmod (0400, "$config{tmpdir}/$thisvotes");
+    rename "$config{tmpdir}/$thisvotes", "$config{archivedir}/$thisvotes"
+      or die UVmessage::get("VOTE_MOVE_VOTEFILE", (FILE=>$thisvotes)) . "$!\n\n";
+  }
+
+  print UVmessage::get("VOTE_CREATING_RESULTS", (FILENAME=>$config{resultfile})), "\n";
+
+  # search all result files
+  opendir (DIR, "$config{archivedir}/");
+  @files = grep (/^ergebnis-/, readdir (DIR));
+  closedir (DIR);
+
+  # Create complete result from all single result files.
+  # The resulting file (ergebnis.alle) is overwritten as there could have been
+  # made changes in the single result files
+  open(RESULT, ">$config{resultfile}");
+  foreach my $file (sort @files) {
+    open(THISRESULT, "<$config{archivedir}/$file");
+    print RESULT join('', <THISRESULT>);
+    close(THISRESULT);
+  }
+  close(RESULT);
+
+  print "\n";
+
+}
+
+
+##############################################################################
+# Print help text (options and syntax) on -h or --help                       #
+##############################################################################
+
+sub help {
+  print <<EOF;
+Usage: uvvote.pl [-c config_file] [-t]
+       uvvote.pl [-c config_file] clean
+       uvvote.pl -h
+
+Liest Mailboxen aus einer Datei oder per POP3 ein wertet die Mails
+als Stimmzettel aus. Erst beim Aufruf mit der Option "clean" werden
+die Ergebnisse endgueltig gespeichert und die Bestaetigungsmails
+verschickt.
+
+  -c config_file   liest die Konfiguration aus config_file
+                   (usevote.cfg falls nicht angegeben)
+
+  -t, --test       fuehrt einen Test der Konfiguration durch und
+                   gibt das ermittelte Ergebnis aus.
+
+  -h, --help       zeigt diesen Hilfetext an
+
+EOF
+
+  exit 0;
+}
This page took 0.84428 seconds and 4 git commands to generate.