Initial checkin of upstream version 4.09.

Signed-off-by: Thomas Hochstein <thh@inter.net>
This commit is contained in:
Thomas Hochstein 2010-08-16 22:16:26 +02:00
commit ac7e2c541a
47 changed files with 8045 additions and 0 deletions

484
CHANGES Normal file
View file

@ -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

339
COPYING Normal file
View file

@ -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.
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.)
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.
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.
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
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.

1284
README Normal file

File diff suppressed because it is too large Load diff

329
UVconfig.pm Normal file
View file

@ -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;

566
UVformats.pm Normal file
View file

@ -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>

380
UVmenu.pm Normal file
View file

@ -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;

32
UVmessage.pm Normal file
View file

@ -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;

225
UVreadmail.pm Normal file
View file

@ -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;

409
UVrules.pm Normal file
View file

@ -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;

336
UVsendmail.pm Normal file
View file

@ -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;

810
UVtemplate.pm Normal file
View file

@ -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>

13
bdsgtext.cfg Normal file
View file

@ -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.

42
mailpatterns.cfg Normal file
View file

@ -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@

194
messages.cfg Normal file
View file

@ -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...

46
templates/ack-mail Normal file
View file

@ -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]\]

View file

@ -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]\]

34
templates/ballot Normal file
View file

@ -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 =-=-=-=-=-=-=-=-

63
templates/ballot-personal Normal file
View file

@ -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]\]

10
templates/ballot-request Normal file
View file

@ -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 =-=-=-=-=-=-=-=-

29
templates/bdsg-error Normal file
View file

@ -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]\]

10
templates/bouncelist Normal file
View file

@ -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]

12
templates/cancelled Normal file
View file

@ -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]\]

27
templates/invalid-account Normal file
View file

@ -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]\]

19
templates/invalid-name Normal file
View file

@ -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]\]

16
templates/mailheader Normal file
View file

@ -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

19
templates/multiple-votes Normal file
View file

@ -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]\]

16
templates/no-ballot Normal file
View file

@ -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]\]

19
templates/no-ballotid Normal file
View file

@ -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]\]

20
templates/no-votes Normal file
View file

@ -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]\]

31
templates/result-multi Normal file
View file

@ -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.

View file

@ -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.

28
templates/result-single Normal file
View file

@ -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.

20
templates/rule-violated Normal file
View file

@ -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]\]

12
templates/voterlist Normal file
View file

@ -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]

36
templates/votes-multi Normal file
View file

@ -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]

33
templates/votes-single Normal file
View file

@ -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]

23
templates/wrong-ballotid Normal file
View file

@ -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]\]

0
tmp/ergebnis-1191790177 Normal file
View file

0
tmp/stimmen-1191790177 Normal file
View file

0
uidlcache Normal file
View file

293
usevote.cfg Normal file
View file

@ -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)

68
usevote.rul Normal file
View file

@ -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.

116
uvballot.pl Normal file
View file

@ -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;
}

194
uvbounce.pl Normal file
View file

@ -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;
}

282
uvcfv.pl Normal file
View file

@ -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;
}

475
uvcount.pl Normal file
View file

@ -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;
}

595
uvvote.pl Normal file
View file

@ -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;
}