From: Thomas Hochstein Date: Mon, 16 Aug 2010 20:16:26 +0000 (+0200) Subject: Initial checkin of upstream version 4.09. X-Git-Tag: 4.0.9 X-Git-Url: https://code.th-h.de/?p=usenet%2Fusevote.git;a=commitdiff_plain;h=ac7e2c541a18c5c910548f5cddaf56502b6e51b0;ds=sidebyside Initial checkin of upstream version 4.09. Signed-off-by: Thomas Hochstein --- ac7e2c541a18c5c910548f5cddaf56502b6e51b0 diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..2d0d6d2 --- /dev/null +++ b/CHANGES @@ -0,0 +1,484 @@ +UseVoteGer Versionshistorie (aktuelle Version: 4.09, released 07.10.2007) +========================================================================= + +TODO: +- aussortieren von Bounces aus Stimmenliste +- Unterstuetzung von Maildir +- [Zugschlus] ich fänd es klasse, wenn man eine Kopie des Wahlscheines ins +Abstimmungsverzeichnis legt und Usevote einem dann auf Abruf ein diff +oder wdiff zwischen Sollwahlschein und wirklich eingreichtem +Wahlschein macht. Auf diese Weise erwischt man auch kleine Änderungen +am Datenschutzhinweis (da bin ich eben in eine Falle von th-h getappt). + + +Version 4.09 (14.09.2007): +- "votefile"-Option in usevote.cfg an passendere Stelle verschoben und + den Kommentar korrigiert (natuerlich gilt pop3=0 als Bedingung, nicht + etwa wie vorher angegeben smtp=0) +- Fehler in Template für Ergebnisausgabe korrigiert (fehlendes Newline + nach umgebrochenen Abstimmungspunkten) +- Fehler in Doku der Kommandozeilenoptionen von uvcount.pl behoben + (--voters statt --votes) +- Date-Header in Englisch erzeugen (statt in eingestellter locale) + (verwendet nun Modul Email::Date) +- Message-ID-Header selbst erzeugen + +Version 4.08 (06.10.2005): +- beim Ignorieren von Regelverletzungen (im Menue mit "Stimmen OK" bestaetigt) + wird jetzt keine (dann ja unangebrachte) Fehlermail mehr verschickt. +- neuer Buchstabe I bzw. i in usevote.rul, der auf NEIN und ENTHALTUNG matcht. + Damit lässt sich eine Stichwahl realisieren, bei der nur für eine von + zwei Möglichkeiten mit JA gestimmt werden darf und im anderen Feld entweder + NEIN oder ENTHALTUNG (bzw. garnichts, was Enthaltung enspricht) + eingetragen werden muss. +- analog neuer Buchstabe H bzw. h für JA/ENTHALTUNG, der Vollstaendigkeit halber +- uvvote.pl sortiert die Liste der Ergebnisdateien jetzt vorm Zusammenfuegen + zur neuen ergebnis.alle, so dass die Reihenfolge auf jeden Fall stimmt, + auch wenn das System die Dateien unsortiert liefert +- wenn uvcount.pl in der ergebnis.alle auf eine falsche Anzahl von Abstimmungspunkten + bei einer Stimme trifft (z.B. versehentlich Leerzeichen oder Buchstabe + am Ende einer Zeile zuviel, wenn manuell editiert wurde), bricht es ab und + weist auf die fehlerhafte Stimme hin. Vorher wurde das als weiterer + Abstimmungspunkt gezählt. +- Doku ergänzt: "envelopefrom" bezieht sich nur auf SMTP, ansonsten muss + das in "mailcmd" konfiguriert werden +- Bug bei Eingruppenmodus behoben: Es wurde immer das selbe ausgegeben, + unabhaengig vom Ergebnis (keine 60 Stimmen, keine 2/3 Mehrheit) + +Version 4.07 (26.09.2004): +- wenn "nodup=1" gesetzt war, wurden auch keine Annullierungen aussortiert. + Ausserdem wurde ansonsten der Wahlleiter unnoetig gefragt, welche + Stimme aussortiert werden soll, auch wenn letztlich beide annulliert + waren (die Annullierung aber erst spaeter eingegangen war). + Um diese Fehler zu beheben, wurde ein zusaetzlicher Verarbeitungsschritt + in uvcount.pl eingefuehrt, der sich nur um Annullierungen kuemmert und + die gleich am Anfang verarbeitet. +- es ist jetzt auch moeglich, nach einer Annullierung mit derselben + Mailadresse nochmal abzustimmen. Vorher wurde so eine Stimmabgabe + durch die vorher erfolgte Annullierung mit erfasst +- Template result-proportional korrigiert. Es kam zu Darstellungsfehlern + bei umgebrochenen Gruppennamen/Wahlgegenstaenden (falsche Einrueckung, + falscher Umbruch) +- Formatfunktion 'generate-date-header' fuer Templates eingefuehrt +- Template 'mailheader' um Date-Header ergaenzt +- chomp auf Message-ID nur noch machen, wenn eine Message-ID vorhanden ist + (gibt sonst Warnung wegen undef) +- wenn die Option "mailcc" gesetzt ist, wurden die Hochkommata in der + domail-Datei (siehe Changelog von Version 4.06) um beide Adressen gesetzt. + Jetzt wird in einer Schleife jede Adresse einzeln gequotet. +- RegEx fuer Realnamenserkennung um den Bindestrich erweitert, damit + Doppelnamen anerkannt werden + +Version 4.06 (18.06.2004): +- Es werden nun "In-Reply-To:" und "References:" Header in den + generierten Mails erzeugt +- beim Schreiben des domail-Scripts (Verschicken von Mails ohne SMTP) + wurde ein fehlerhafter Zeilenumbruch eingefuegt +- Leerzeichen am Zeilenende in der usevote.cfg hatten dazu geführt, + dass Einstellungen nicht korrekt eingelesen wurden. Jetzt werden + beim Einlesen der Konfiguration solche Leerzeichen gelöscht (außer + wenn der Teil rechts vom Gleichzeichen durch Anführungsstriche umschlossen + ist) +- Mailadresse und Waehlername werden jetzt korrekt zurueckgesetzt, so + dass bei fehlendem From-Header nicht noch die Daten der vorherigen Mail + in den Variablen stehen +- Mailadresse wird nun in Hochkommata eingeschlossen, wenn sie in die + "domail"-Datei geschrieben wird (bei smtp=0 in usevote.cfg), damit + Shell-Metazeichen nicht beim Ausfuehren des MTA interpretiert und damit + die Mailadresse veraendert bzw. potentiell schaedlicher Code ausgefuehrt + wird +- Es ist jetzt moeglich, bei der Warnung "Es wurden nicht alle Fehler behoben, + der Waehler wird eine Fehlermail erhalten" zurueck ins Menue zu gehen + und die Fehler doch noch zu beheben +- in uvcfv.pl (Verschicken von personalisierten Wahlscheinen) einige + Bugs behoben [warum hatte das ueberhaupt so funktioniert? Schroedinger + laesst gruessen.] und den Hinweistext "Wahlschein wurde bereits einmal + zugeschickt" aus dem Perlcode in das Template ballot-personal verlagert +- mittels "uvcfv.pl -t" laesst sich jetzt ein personalisierter + Dummy-Wahlschein ausgeben (wenn "personal=1" in usevote.cfg), um ihn + vorab der dana-Moderation zur Pruefung zukommen lassen zu koennen + +Version 4.05 (27.12.2003): +- Aendern von Mailadressen oder Namen im Menue fuehrte zu "keine Scheinkennung" + Fehlern, auch wenn die personalisierten Wahlscheine nicht aktiviert waren + (personal=0 in usevote.cfg). +- Fehler beim Verarbeiten von Mailbox-Files behoben. In den letzten + Versionen funktionierte nur POP3. +- In Wahlschein-Templates "kann fuer ungueltig erklaert werden" in + "wird fuer ungueltig erklaert werden" geaendert (bei falschem Realname) +- kosmetische Aenderungen in Templates (Anpassung an neue Rechtschreibung, + Entfernung von Umlauten fuer einheitliches Schriftbild) + +Version 4.04 (22.11.2003): +- uvcount.pl: Fehler beim Aussortieren von Duplikaten behoben, was + i.d.R. nur bei doppelten Mailadressen, nicht aber bei doppelten Namen + funktionierte (falsche Regular Expression und Probleme bei + unterschiedlicher Gross-/Kleinschreibung der Namen) + +Version 4.03 (19.10.2003): +- UVsendmail.pm: Vernuenftige Fehlerbehandlung bei SMTP implementiert: + Bei fehlgeschlagenen Zustellversuchen wird jetzt die Datei ack.control + passend neu geschrieben, so dass mit "uvvote.pl clean" ein neuer + Versuch unternommen werden kann. Vorher wurde zwar eine Fehlermeldung + angezeigt, die Mail aber einfach geloescht... +- es wird kein Fehler mehr angezeigt, wenn ack.control bereits existiert, + da der Code durchaus damit umgehen kann (es wird einfach an die + Datei angehaengt). Entsprechende Fehlermeldung aus messages.txt + entfernt +- Schreibfehler in messages.cfg behoben (ggf. statt ggfls.) +- me@privacy.net in mailpatterns.cfg aufgenommen +- UIDLs werden jetzt in der Reihenfolge gespeichert, in der die + Mails auf dem POP3-Server lagen. Dadurch ist bei einem Abbruch + waehrend der Auswertung leichter kontrollierbar, welche Mails noch + einmal abgerufen werden sollen (einfach die letzten X UIDLs aus + der Datei uidlcache loeschen) +- uvcfv.pl, uvbounce.pl und uvvote.pl besitzen jetzt einen Locking- + Mechanismus, der ein gleichzeitiges bzw. mehrfaches Starten dieser + Programme unterbindet. Andernfalls koennte es zu Inkonsistenzen + im Datenbestand kommen (gleichzeitiger Abruf derselben Mailbox, + Auswertung noch nicht fertig geschriebener Ergebnisdateien) +- es koennen jetzt zusaetzliche Konfigurationsdateien in usevote.cfg + eingebunden werden, um z.B. die immer gleichen Einstellungen nur + einmal zentral abzulegen. Hierzu einfach eine Zeile + include dateiname + einfuegen. Die Position ist wichtig: Bei mehrfacher Definition + der selben Option gilt die letzte. Daher sollte eine globale + Konfigurationsdatei am Anfang eingebunden werden, um die + Einstellungen bei Bedarf mit wahlspezifischen ueberschreiben zu + koennen + +Version 4.02 (31.05.2003): +- UVpath.pm wieder entfernt, da mittlerweile eine bessere Loesung + gefunden: Das Modul FindBin wird eingesetzt, um den Pfad der + ausgefuehrten .pl Datei zu ermitteln. Wenn die .pm Dateien im selben + Verzeichnis liegen, werden sie dort gefunden. Ausserdem wurde der + Hinweis auf die Umgebungsvariable PERL5LIB in die README Datei + aufgenommen, die ansonsten auch auf den Pfad zu den Usevote-Perlmodulen + gesetzt werden kann. + +Version 4.01 (29.05.2003): +- Wahlschein-, Result- und Bestaetigungsmail-Templates angepasst, so dass + bei langem "votename" ein Umbruch im Wahlschein erfolgt und auch bei einer + zweistelligen Anzahl von Wahlgegenstaenden eine buendige Ausgabe erfolgt +- uvballot.pl und Template "result-multi" angepasst, so dass bei + Mehrgruppenabstimmungen die Anzahl der Enthaltungen nicht ausgegeben + wird (laesst sich nicht als Gesamtzahl ermitteln, koennte man hoechstens + fuer jede Gruppe einzeln angeben) +- Es brauchen jetzt nur noch die Konfigurationsdateien sowie die + UVpath.pm in einem Abstimmungsverzeichnis zu liegen, die .pl und .pm + Dateien koennen zentral fuer mehrere Abstimmungen abgelegt werden. +- Formatierungsfunktion "replace" in UVformats.pm implementiert, mit + deren Hilfe die Ersetzung von Zeichen oder Zeichenketten in Templates + moeglich ist. Praktische Anwendung ist z.B. die Verfremdung von + Mailadressen im Result als trivialer Spamschutz. Wie die Templates + dafuer geaendert werden muss, ist in der README Datei im Abschnitt 10 + beschrieben +- Bei den Standard-Funktionen append und justify (inkl. justify-before + und justify-behind) wird der uebergebene Key jetzt rekursiv ueber die + Formatdefinitionen im Template aufgeloest. Das ermoeglicht die + Vorbehandlung eines Wertes, z.B.: + mail := value mail | replace '@' '-at-' + line := value name | justify-before mail 70 + Hier wurde vorher die unveraenderte Mailadresse benutzt, jetzt wird + die obere Definition beachtet und zunaechst die Ersetzung durchgefuehrt. +- Formatierungsfunktion "sprintf" in UVformats.pm implementiert, um + z.B. Verhaeltnisse in Results formatiert ausgeben zu koennen +- Auswertung nach Verhaeltnis Ja- zu Nein-Stimmen implementiert + (in usevote.cfg proportional=1 setzen und prop_formula passend waehlen). + Damit ist z.B. fuer jeden Abstimmungsgegenstand das Verhaeltnis oder + auch die Differenz zwischen Ja- und Nein-Stimmen ermittelbar. Letzteres + wird fuer Moderationsnachwahlen benoetigt. Kombiniert werden kann dies + mit einer weiteren Bedingung, z.B. mindestens soviele Ja- wie Nein-Stimmen. + +Version 4.0 (22.03.2003): +- UVformats.pm dokumentiert +- Defaultwert fuer "formats" korrigiert (UVconfig.pm) +- Defaultwert fuer "bdsgfile" fehlte (UVconfig.pm) +- kosmetische Aenderung (fehlende Leerzeichen) an Template result-multi +- Windows-Pager-Empfehlung in README und usevote.cfg geaendert (vorher + wurde "more" empfohlen, da mitgeliefert, aber more ist so buggy, dass + jetzt die Installation von "less" nahegelegt wird +- Fehler in UVsendmail.pm behoben: Wenn beim "uvvote.pl clean" Aufruf keine + Mails zu verschicken waren, wurde das Programm in UVsendmail::send() + einfach mit "exit 0" beendet (korrigiert in "return 0"). Ausserdem + wurde die Fehlermeldung wegen eines Schreibfehlers im Konstantennamen + nicht angezeigt. + +Version 4.0beta15: +- Fehlermeldung bei nicht vorhandener messages.cfg korrigiert + (Dateiname wurde wegen falschen Configschluessels nicht angezeigt) +- Bei manuell eingegebener Scheinkennung wurde irrtuemlicherweise eine + Fehlermeldung angezeigt, auch wenn die Kennung zur Mailadresse passte +- Bei neu eingegebener Mailadresse wurde die Zugehoerigkeit der + Scheinkennung nicht neu geprueft +- bei nicht erkannten Abstimmungspunkten im Wahlschein wurde eine + Warnmeldung angezeigt, die auf Nicht-Wertung hinwies. In Wirklichkeit + wurde die Stimme aber normal bestaetigt und lediglich alle nicht erkannten + Punkte als "Enthaltung" gewertet. Die Warnmeldung erscheint jetzt nicht + mehr, um keine Verwirrung zu stiften. +- Fehler in UVmessage.pm behoben: Der Wert 0 wurde durch den leeren String + ersetzt. Jetzt wird defined() eingesetzt statt auf true/false zu pruefen. +- Fehler in UVsendmail.pm behoben: Wenn kein SMTP aktiviert war, wurde + die "domail" Datei mit den MTA-Aufrufen zwar geschrieben, aber nicht + ausgefuehrt. Dadurch schlug auch das Loeschen der Temp-Dateien fehl, + was beim naechsten Aufruf Fehlermeldungen verursachte. +- Templates eingebaut +- acktext.txt durch Templates ersetzt. BDSG-Text ist jetzt in der + Datei bdsgtext.cfg +- "cfvfile" Option entfernt (nicht mehr noetig, durch Templates abgeloest) +- Bedingungen fuer Wahlerfolg ueber usevote.cfg konfigurierbar gemacht. + Standardwerte: + condition1 = $yes>=2*$no + condition2 = $yes>=60 +- usevote.cfg bzgl. der Ueberschriften "jedes Mal anpassen" / "nur einmal + anpassen" ein wenig umsortiert +- wenn kein "smtphelo" definiert wurde, wird jetzt der eigene + Hostname genommen +- README an aktuelle Aenderungen angepasst (Dateilisten, Beschreibungen + der Menues) und vervollstaendigt + +Version 4.0beta14: +- Fehler in UVmenu.pm behoben, der beim Auswaehlen von + "Stimmen vom Waehler annulliert" im Menue auftrat +- Erkennung von doppelten, sich widersprechenden Stimmabgaben in einer + Mail funktioniert jetzt +- beim Verschicken per SMTP gibt es die neue Option envelopefrom, die + die Absenderadresse im Envelope (Return-Path) enthaelt, an die auch + Bounces zurueckgehen +- Neu: Erkennung von fehlenden Abstimmungspunkten im Wahlschein, Behandlung + wie bei unleserlichen Stimmabgaben mit entsprechendem Hinweis im Menue +- Fehler in uvvote.pl behoben, der bei unleserlichen Stimmabgaben auftrat +- statt encode_mimewords wird jetzt encode_mimeword verwendet und das + "Drumherum" komplett selbst gemacht. Man schaue in den Code von + MIME::Words::encode_mimewords(), dann weiss man, warum ;-) +- Weitere Texte in messages.txt ausgelagert (uvvote.pl, uvcount.pl) +- config test (-t Option) gibt jetzt auch Auskunft ueber die Konfiguration + (falls Option nicht in usevote.cfg gesetzt, wird der Standardwert + ausgegeben) + +Version 4.0beta13: +- Fehlerbehandlung bei SMTP eingefuehrt, so dass keine Mails verloren gehen +- es laesst sich jetzt ein anderer Port fuer SMTP/POP3 angeben +- uvbounce.pl benutzt jetzt auch POP3, falls dieses in usevote.cfg aktiviert + wurde. Mit der Option -f ist aber unabhaengig davon das Einlesen der + Bounces aus einer Datei in jedem Fall moeglich +- auftretende Fehler beim Ausfuehren von uvvote.pl werden jetzt in eine + Datei geschrieben und beim Verlassen wird darauf hingewiesen +- Wenn das interaktive Menue ausgeblendet und dazu der Bildschirm geloescht + wird, informiert jetzt eine Meldung darueber, dass Mails verarbeitet werden +- Menues so umgestellt, dass [a] immer fuer "alles OK" steht, egal ob + Mailadresse, Name, Stimmen oder die BDSG-Klausel strittig sind +- Ausgaben/Texte von UVreadmail.pm und UVsendmail.pm nach messages.txt + ausgelagert +- Verzeichnisnamen "fertig" und "tmp" jetzt konfigurierbar +- Zeilen "Waehleradresse: " und "Wahlscheinkennung: " im Wahlschein + konfigurierbar gemacht +- Pager konfigurierbar gemacht (vorher immer "more") und standardmaessig + auf "less" gesetzt, weil "more" mit der Umleitung von STDERR Probleme hat +- kleinere Bugs behoben + +Version 4.0beta12: +- Fehler in Menue behoben +- im Menue kann man jetzt explizit Stimmen ungueltig werten, indem man + den Namen, die Adresse oder die Stimmen ungueltig macht. Es wird eine + passende Fehlermail generiert. +- genauso kann man jetzt explizit annullieren (sinnvoll, falls der Waehler + z.B. "annullierung" falsch buchstabiert hat *g*), wobei automatisch + diverse andere Probleme als irrelevant erkannt werden (z.B. braucht man + in dem Fall keinen Datenschutzhinweis zu akzeptieren und nicht unbedingt + einen Namen anzugeben, falls die Adresse stimmt) +- MIME-Kodierung fuer Subject- und From-Header in UVsendmail.pm eingefuehrt +- In Bestaetigungsmails gibt es jetzt kein gesondertes Feld mehr zum + Korrigieren des Namens, sondern es kann einfach die ohnehin vorhandene + Zeile "Wahlername: Vorname Nachname" editiert werden. +- "nametext2" in usevote.cfg ist jetzt "Waehlername:" und wird auch + statt des fest kodierten Strings an den entsprechenden Codestellen + verwendet +- uvcount.pl: Bugs bei Annullierungen und fehlendem Namen behoben +- uvbounce an geaendertes UVreadmail.pm angepasst (funktioniert bei + Aktivierung des POP3-Zugriffs nicht mehr) +- saemtliche Ausgaben/Texte in UVmenu.pm nach messages.txt ausgelagert + +Version 4.0beta11: +- kompletter Rewrite der Ueberpruefungsfunktionen in uvvote.pl und UVmenu.pl. + Es werden jetzt alle Fehler an ein Array angehaengt und in einem Rutsch + von der Menue-Funktion verarbeitet. Fuer Darstellung der Votemail wird + "more" benutzt. + +Version 4.0beta10: +- Zeilenumbrueche richten sich jetzt nach der "rightmargin"-Einstellung + aus usevote.cfg (vorher waren die Zeilenlaengen teilweise noch hartkodiert) +- Fehler beim Erstellen des domail-Scripts behoben +- kosmetische Code-Aenderungen + +Version 4.0beta9: +- Auch bei zurueckgeschickten (korrigierten) Wahlbestaetigungen wird + jetzt der Name automatisch im Body erkannt (Zeile "Waehlername:"). +- Regular Expressions zur Stimmerkennung geaendert: Manche komische + Mailprogramme benutzen zum Kodieren von Leerzeichen =A0, was aber + nach der Dekodierung nicht als \s erkannt wird. \W ist nicht optimal, + aber funktioniert. +- uvbounce.pl: Bounces von Antworten auf Wahlscheinanforderungen werden + jetzt erkannt und mit einem gesonderten Hinweis gekennzeichnet + ("Wahlschein nicht zustellbar") +- in den Config Files koennen die Kommentarzeichen escaped werden: \# + +Version 4.0beta8: +- beim Einlesen aus usevote.cfg wird ein eventuelles \r geloescht +- es werden nicht mehr jedes Mal saemtliche Mails abgerufen (bei POP3), + sondern es wird mit dem UIDL Kommando geprueft, ob schon ein vorheriger + Abruf stattfand. Ausserdem ist es jetzt moeglich, die Mails vom Server + zu loeschen. +- uvcfv.pl kann jetzt auch richtig mit POP3 umgehen +- Platzhalter im Wahlschein bei personalisierten Wahlscheinen geaendert +- Aktuelle Werte werden teilweise jetzt im Menue angezeigt (wenn man + Stimmen, Name oder Mailadresse neu gesetzt hat) +- Dokumentation verbessert + +Version 4.0beta7: +- kosmetische Code-Aenderungen (Vereinfachungen, Verschoenerungen, ...) +- Inhalt der Datei bdsgtext.txt als Abschnitt [BDSG Hinweis] in + acktext.txt uebernommen (es gibt keinen Grund dafuer, dass dieser + Text eine eigene Datei bekommen sollte...) +- Testweise einige Programm-Meldungen in externe Datei (meldungen.cfg) + ausgelagert, um eine leichtere Anpassung zu ermoeglichen (z.B. + Uebersetzung in andere Sprachen). Nach und nach werden saemtliche + Meldungen in diese Konfigurationsdatei wandern. +- acktext.txt in acktext.cfg umbenannt: Alle Konfigurationsdateien + haben damit die Endung .cfg +- POP3-Abruf und Verschicken per SMTP eingebaut +- Shellbefehl-Aufrufe (chmod- und mkdir) durch Perl-Pendants ersetzt, + um Plattformunabhaengigkeit zu bieten +- In uvcfv.pl Doppelung im Mailsubject geloescht +- In uvcount.pl stimmte die Zuordnung von Abstimmungsgegenstand zur + einzelnen Stimmabgabe nicht (umgekehrte Reihenfolge) +- in UVmenu.pm entstand bei Mehrgruppenabstimmungen der Kommentar + "Wahlleiter setzte Stimmen, Stimmen, Stimmen" (jetzt nur noch + einmal gesetzt statt fuer jede Gruppe) +- Falls keine Scheinkennung und keine BDSG-Zustimmung: Bislang wurden + dann zwei Mails generiert (keine Abfrage auf bereits aufgetretenen + Fehler), nun behoben + +Version 4.0beta6: +- RegExp fuer Namensangabe im Body verbessert (wenn kein Name angegeben + wurde und auch im Header keiner zu finden war, wurde der nachfolgende + Hinweissatz "Wenn Du keinen Namen angibst..." als Realname erkannt +- RegExp fuer Namenserkennung konfigurierbar gemacht (usevote.cfg) + und um Accents erweitert +- RegExp fuer Erkennung verdaechtiger Adressen trifft jetzt nur noch + zu, wenn der String direkt am Anfang der Adresse steht. Gegenteiliges + Verhalten kann durch Wildcards herbeigefuehrt werden +- Statt manuellen Trennens von Header und Body wird in UVreadmail.pm + jetzt das Modul MIME::Parser eingesetzt +- kleinere Bugs behoben (z.B. einfache vs. doppelte Anfuehrungsstriche) +- Fehler bei UVmenu::menu-Aufruf im Falle von "keine Stimmen" behoben + (eine Variable fehlte in der Uebergabeliste) +- uvcount.pl um Eingruppen-Format ergaenzt +- uvballot.pl um Option -t ergaenzt, um eine Vorlage fuer cfv.txt bei + Verwendung von personalisierten Wahlscheinen zu erzeugen +- uvbounce.pl zur Generierung von ungueltigen Adressen aus einer + Mailbox mit Bounces implementiert +- Bei Annullierungen wird jetzt nicht mehr die BDSG-Klausel geprueft + (es erfolgt ja ohnehin eine Loeschung der Stimmabgabe) +- Statt Mail::Field wird nun eine eigene RegExp verwendet (Danke an + Marc Brockschmidt fuer die Idee), Mail::Field hat einige unschoene Bugs. +- Bei nicht erkannten Stimmabgaben bei Abstimmungen mit nur einem + Abstimmungsgegenstand wird nun auch die Ungueltigwertung angeboten + (als Alternative zu "Enthaltung"). + +Version 4.0beta5: +- die Mailboxdatei wird nun vor der Verarbeitung verschoben, so dass waehrend + des uvvote-Laufs keine neuen Mails angehaengt werden koennen +- es wird jetzt fuer jeden Durchlauf eine gesonderte Ergebnisdatei angelegt +- neu eingefuehrter Parameter "clean", der Bestaetigungen verschickt, + Ergebnisdatei und die Mailbox in das Verzeichnis fertig/ verschiebt, + temporaere Dateien loescht und aus allen Einzelergebnissen eine neue + Gesamtergebnisdatei (normalerweise "ergebnis.alle") erstellt +- dadurch sind jetzt zwei Durchgaenge erforderlich: Erster Aufruf ohne + die Option "clean" (Erzeugt die Ergebnisse und Mailvorlagen), dann kann + eine Kontrolle erfolgen, anschliessend ein weiterer Aufruf mit der Option + "clean" zum Aufraeumen und Verschicken der Bestaetigungen +- neues Modul UVsendmail.pm, in das die Funktion zum Erstellen von + Mails ausgelagert wurde +- neues Modul UVmenu.pm mit der Menuefunktionalitaet zum interaktiven + Eingriff in die Stimmwertung +- Bugs bei Stimmaufzeichnung behoben: Fehlerhafte Stimmen wurden teilweise + nicht in der Ergebnisdatei vermerkt. Ausserdem wurde nicht konsequent + auf die Option "voteack" geprueft +- Reply-To kann jetzt beachtet werden (muss in usevote.cfg eingeschaltet + werden) und ueberschreibt das From. Mit Vorsicht zu geniessen, da so + jeder Waehler fuer andere Stimmen abgeben und die Bestaetigungen zu + sich umlenken kann! +- Umgang mit personalisierten Wahlscheinen (Abschnitt 6a der Wahlregeln + fuer de.*), Generierung und Pruefung von Scheinkennungen. Siehe Optionen + "personal" und "idfile" in usevote.cfg sowie das Programm uvcfv.pl zum + Verschicken der persoenlichen Wahlscheine +- Das Flag beim Aufruf der Menuefunktion ist jetzt ein Hash, in dem + diverse Werte ueber- und zurueckgegeben werden. Dadurch auch feine + Steuerung der aktiven Menuepunkte moeglich +- Ausgabe der Fehlermeldung in UVmenu.pm verlagert (wird in besagtem + Hash uebergeben) +- Kommentarfeld fuer Ergebisdatei ("Wahlleiter setzte xyz") wird erst + zum Schluss erzeugt, vorher werden nur die vom Wahlleiter manuell + veraenderten Felder in einem Array mitgefuehrt +- uvcfv.pl zum Generieren der persoenlichen Wahlscheine und Verschicken + des CfVs implementiert +- Bisherige Funktionalitaet von uvack, uvcount, uvdup in uvcount.pl + implementiert +- uvballot.pl zum Erstellen eines Musterwahlscheins +- uvcfv.pl: Es wird nun auch die Vollstaendigkeit von $config{bdsgtext} + im Wahlschein geprueft und bei Auswahl von "Ende+Speichern" wird + noch einmal zur Sicherheit gefragt, ob die Stimme wirklich + gespeichert und verarbeitet werden soll +- Kompatibilitaetsprobleme mit Perl 5.6.1 bei Variablenzuweisungen behoben + +Version 4.0beta4: +- bei Regelverletzung wird jetzt ein interaktives Menue aufgerufen, + so dass der Wahlleiter entscheiden kann, was er machen will +- Stimmerkennung legt unbekannte Vote-Strings (nicht Ja, Nein, + Enthaltung oder Annullierung) jetzt dem Wahlleiter vor und + laesst ihn entscheiden (Default ist Enthaltung). Fehlermail ist + in so einem Fall bislang nicht vorgesehen, laesst sich aber noch + einbauen, falls erforderlich. Wenn der Waehler die Bestaetigung + ueberprueft, reicht es auch, wenn dort "Enthaltung" auftaucht... +- Pruefung auf vollstaendige Bestaetigung der Datenschutz-Klausel + ist implementiert und ueber usevote.cfg und bdsgtext.txt konfigurierbar +- Regelpruefung kompakter und dreimal schneller gemacht (Idee von + Cornell Binder), dafuer Code schlechter lesbar... aber dokumentiert ;-) +- Alle Regel-Subs in UVrules.pm ausgelagert + +Version 4.0beta3: +- Einlesen und Testen der Konfiguration sowie Ausgabe der Regeln + im Klartext sind jetzt im externen Modul UsevoteConfig.pm untergebracht +- Das Einlesen der Mail und MIME-Bearbeitung erfolgt wurde in das + Modul UsevoteReadmail.pm ausgelagert +- verdaechtige Mailadressen werden nun in einer gesonderten Datei + konfiguriert (Default: mailpatterns.cfg) +- Fehler bei Parsing von %body% und %headbody% in acktext.txt behoben + +Version 4.0beta2: +- Auslagerung des Abschnittes [Realname Info] nach acktext.txt + (vorher hardcoded) +- Einfuehrung des Platzhalters %version% fuer acktext.txt +- Einfuehrung der Option voteack (Einzelbestaetigung kann deaktiviert werden) +- saemtliche Konfigurationsoptionen sind nun mit Defaultwerten belegt, + aber wahlspezifische Optionen (Gruppennamen etc.) muessen natuerlich + auf jeden Fall gesetzt werden +- Aenderung der Stimmerkennungsmethode. Um problemlos mit eigenwilligen + Zeilenumbruechen diverser schrottiger Software umgehen zu koennen, + wird ein Identifier an den Zeilenanfang gesetzt und direkt dahinter + die Stimme. Der Gruppenname kann dann ruhig umgebrochen sein. + +Version 4.0beta1: +- kompletter Rewrite in Perl. Noch ziemlich unvollstaendig (nur uvvote.pl) + +bis Version 3.1beta7 (Wolfgang Behrens): +- Funktionialitaet fuer persoenliche Wahlschein eingebaut +- Reply-To Auswertung u.a. auf GVV-Beduerfnisse angepasst + +Version Usevote 3.0a +- Uebersetzung ins Deutsche und Anpassung an Wahlregeln in de.* + (Frederik Ramm) + +Version Usevote 3.0 +- Urversion von Ron Dippold (Englisch), nicht an hiesige Wahlregeln angepasst diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e77696a --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + 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. + + + Copyright (C) 19yy + + 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. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/README b/README new file mode 100644 index 0000000..e2a7e66 --- /dev/null +++ b/README @@ -0,0 +1,1284 @@ +UseVoteGer 4.09 (c) 2001-2007 Marc Langer + +UseVoteGer is a voting software for usenet votes. + +This script package is free software; you can redistribute it and/or +modify it under the terms of the GNU Public License as published by the +Free Software Foundation. + +Many thanks to: +- Ron Dippold (Usevote 3.0, 1993/94) +- Frederik Ramm (German translation, 1994) +- Wolfgang Behrens (UseVoteGer 3.1, based on Frederik's translation, 1998/99) +- Cornell Binder for some good advice and code fragments + (e.g. UVtemplate.pm, UVformats.pm) + +This is a complete rewrite of UseVoteGer 3.1 in Perl (former versions were +written in C). Not all functions of Usevote/UseVoteGer 3.x are implemented! + +------------------------------------------------------------------------------- + +UseVoteGer 4.09 - Usenet-Abstimmungssoftware +=========================================== + +von Marc Langer + +aktuelle Versionen: http://www.usevote.de + + +Inhaltsverzeichnis +================== + +1. Kurzbeschreibung +2. Usevote fuer Ungeduldige +3. Kompatibilitaet zu frueheren Versionen +4. Liste der Dateien +5. Anfangs-Konfiguration +6. Konfiguration fuer einzelne Abstimmungen +7. Wahlschein generieren +8. Stimmen verarbeiten +9. Unzustellbare Bestaetigungen +10. 2. CfV / Result +11. Datenschutz +12. Personalisierte Wahlscheine +13. Die einzelnen Programme +14. Die Konfigurationsdateien +15. Templates + + +1. Kurzbeschreibung +=================== + +Diese Software vereinfacht die Durchfuehrung von Usenet-Abstimmungen. +Usevote in der Urversion wurde von Ron Dippold fuer die Reorganisation +der Gruppe comp.sys.ibm.pc.games geschrieben und spaeter fuer die +allgemeine Verwendung verbessert. + +Die deutsche Uebersetzung von Frederik Ramm diente jahrelang als +Grundlage fuer CfV-Auswertungen im deutschsprachigen Raum. +Wolfgang Behrens von den German Volunteer Votetakers (GVV) passte +es spaeter an neue Begebenheiten (z.B. personalisierte Wahlscheine) +an. Jedoch liefen die UseVoteGer 3.1beta-Versionen aufgrund eines +unbekannten Fehlers nicht auf jedem System (Segmentation Fault). + +Um groessere Flexibilitaet und Plattformunabhaengigkeit zu bieten, +habe ich mich entschlossen, UseVoteGer in Perl neu zu implementieren. +Getestet wurde diese Version mit Perl 5.x unter Linux sowie +ActiveState Perl unter Windows 98. + + +2. Usevote fuer Ungeduldige +=========================== + +I. Einmalig bei der Usevote-Erstinstallation + (1) usevote.cfg nach Bedarf anpassen (selbsterklaerend) + ACHTUNG: Unter Windows gibt es das Programm "less" nicht, da "more" + aber einigermassen broken ist, sollte man sich less fuer Windows + herunterladen: http://www.greenwoodsoftware.com/less/less381d.zip + Notfalls laesst sich auch in usevote.cfg die Einstellung "pager" + auf "more" setzen, aber viel Freude hat man damit nicht... + (2) Evtl. die Templates im gleichnamigen Unterverzeichnis fuer + eigenen Geschmack anpassen. Die Templates enthalten saemtliche + Texte fuer Bestaetigungs-/Fehlermails sowie die Ausgabeformate + und Texte fuer die CfVs und das Result. + (3) Perlmodule MIME::Parser, MIME::QuotedPrint, Digest::MD5, + Date::Parse und Email::Date installieren, falls noch nicht vorhanden. + Bei Benutzung von POP3 oder SMTP (siehe usevote.cfg) muss + Libnet installiert sein (Net::POP3 und Net::SMTP). + (Fuer Perl-Einsteiger: Unter Unix geht die Modul-Installation + sehr einfach mit "perl -MCPAN -e shell", ActivePerl fuer + Windows hat einen eigenen Paketmanager) + + Achtung: Bei meinem ActivePerl 5.6.1 war eine alte Libnet-Version + enthalten, die noch keine SMTP-Authentication unterstuetzte. + Bei Benutzung dieses Features muss evtl. erst upgedated werden. + +II. Fuer jedes Voting + (1) usevote.cfg auf das durchzufuehrende Voting anpassen + (selbsterklaerend) + (2) bei Sonderregeln (Abhaengigkeiten der einzelnen Abstimmungspunkte) + in usevote.rul Regeln definieren (selbsterklaerend) + (3) CfV erstellen und Wahlschein mit uvballot.pl generieren + (4) Fertigen CfV einreichen und auf Veroeffentlichung warten ;-) + (5) Bis zum 2. CfV regelmaessig (an den ersten Tagen mehrmals taeglich, + spaeter einmal taeglich) dies tun: + - uvvote.pl durchlaufen lassen + - im Verzeichnis tmp/ die Datei ergebnis.* (Ergebnis der Auswertung) + und evtl. ack.* (Bestaetigungsmails an die Waehler) kontrollieren + - Falls es Probleme gab: Bitte den Abschnit "Stimmen verarbeiten" + weiter unten in dieser Datei genau durchlesen. Dort steht + beschrieben, wie Fehler korrigiert werden koennen + - "uvvote.pl clean" aufrufen, um die Bestaetigungen zu verschicken + und das Ergebnis zu speichern/archivieren + - Falls Wahlbestaetigungen als unzustellbar zurueckkommen, solltest + Du diese zusammen in einer Datei speichern oder in ein spezielles + POP3-Postfach leiten + (6) Zwischenbestaetigung fuer den 2. CfV mit "uvcount.pl -l" erzeugen und + veroeffentlichen. Falls Wahlbestaetigungen als unzustellbar + zurueckkamen, kannst Du mit "uvbounce.pl dateiname" automatisch eine + Liste der ungueltigen Adressen erzeugen ("dateiname" ist die Datei, + in die Du zuvor die Bounces gespeichert hattest). Siehe auch Abschnitt + "Unzustellbare Bestaetigungen" weiter unten in dieser Datei, dort + ist auch erklaert, wie die Bounces aus einem POP3-Postfach gelesen + werden koennen. + (7) Bis zum Result wieder wie (5) + (8) Zum vorgegebenen Zeitpunkt die Wahl abschliessen, + mit "uvcount.pl -r -v" Endergebnis erzeugen und veroeffentlichen + (zuvor wiederum Liste der ungueltigen Mailadressen erzeugen, siehe + Punkt 6) + +Weitere Details finden sich im Folgenden. +Jeder Nutzer dieser Software sollte sich diesen Text wenigstens einmal +komplett durchlesen. + +Abschnitt 6 beschreibt, wie mehrere Abstimmungen gleichzeitig durchgefuehrt +werden koennen, ohne saemtliche Programmdateien zu kopieren. + + +3. Kompatibilitaet zu frueheren Versionen +========================================= + +- englische Usevote-Versionen: keine Kompatibilitaet + +- deutsche Usevote-Versionen (Usevote 3.0a, UseVoteGer 3.1): + * Wahlscheine haben neue Markierungen zum sicheren Erkennen + der Stimmen. Bisherige Wahlscheine sind mit dieser Version + nicht mehr einsetzbar! + + * acktext.txt gibt es nicht mehr, statt dessen sind im Unterverzeichnis + "templates" Vorlagen zu finden, die diese Texte enthalten. + + * usevote.cfg ist komplett inkompatibel + + * usevote.rul: voll kompatibel, ohne Einschraenkungen + + * Ergebnisdateien: Inhalt kompatibel, aber Namensgebung geaendert + (obwohl irrelevant, da Umstieg auf diese Version waehrend + einer laufenden Wahl nicht moeglich) + + * scheinkennungen: Dateiformat gaendert (aus selbigen Gruenden + nicht weiter wichtig) + + * uvshell: integriert in uvvote.pl, ueber usevote.cfg steuerbar + + * uvvote.pl: Aufrufparameter geaendert / Zusammenfuehrung mit uvshell + + * uvcfv.pl: Alte Funktionalitaet ist nicht mehr implementiert, + statt dessen dient das Tool jetzt zum Generieren der persoenlichen + Wahlscheine und Verschicken des CfV an die Waehler + + * uvcount.pl: Beinhaltet auch uvack und uvdup. Drei Einzelprogramme + schienen mir ineffizient. + + * uvbounce.pl: Ausgabeformat etwas anders + + +4. Liste der Dateien +==================== + +* Ausgelieferte Dateien: + +CHANGES Versionsaenderungen +COPYING GNU General Public License +README Diese Datei +UVconfig.pm Routinen zum Einlesen und Pruefen der Konfiguration +UVformats.pm Routinen zum Formatieren von Texten in Templates +UVmenu.pm Routinen zur Interaktion mit dem Wahlleiter +UVmessage.pm Routinen zum Parsen von Texten und Ersetzen von Platzhaltern +UVreadmail.pm Routinen zum Einlesen und MIME-Decodieren der Mails +UVrules.pm Routinen zur Regelverarbeitung (usevote.rul) +UVsendmail.pm Routinen zum Erzeugen von Mails +UVtemplate.pm Routinen zur Verarbeitung von Vorlagen (Templates) +bdsgtext.cfg Spezieller Text fuer den Wahlschein (Hinweis auf + Datenschutzgesetz), muss ausserhalb Deutschlands ggfls. + angepasst oder kann ignoriert werden (bdsg=0 in usevote.cfg) +mailpatterns.cfg Wildcards fuer verdaechtige Mailadressen +messages.cfg Programm-Meldungen (Ressourcen-Datei) +scheinkennungen Speicherung der Scheinkennungen bei personalisierten + Wahlscheinen. Muss zu Beginn der Abstimmung leer sein. +usevote.cfg Konfigurationsdatei +usevote.rul Abstimmungsregeln +uvballot.pl Wahlschein aus Abstimmungsdaten erstellen +uvbounce.pl Listen von unzustellbaren Bestaetigungen erzeugen +uvcfv.pl Erstellen und Verschicken von personalisierten Wahlscheinen +uvcount.pl Stimmenzaehlung, Erstellen von Waehlerlisten und + Aussortieren von doppelten Stimmabgaben +uvvote.pl Stimmen verarbeiten (fuer die taegliche Arbeit) + +templates/ Verzeichnis mit Templates (Vorlagen) + ack-mail Template fuer Bestaetigungsmails + address-not-registered Fehlermail: Noch keine Scheinkennung fuer Adresse + vergeben + ballot Template fuer einen Wahlschein + ballot-personal Template fuer personalisierten Wahlschein + ballot-request Template fuer eine Wahlscheinanforderung + bdsg-error Fehlermail: Datenschutzklausel nicht akzeptiert + bouncelist Template fuer Liste der ungueltigen Mailadressen + cancelled Template fuer Bestaetigung der Stimmannullierung + invalid-account Fehlermail: Fragwuerdige Mailadresse (Role Account) + invalid-name Fehlermail: Kein gueltiger Name in Mail/Wahlschein + gefunden + mailheader Template fuer den Header von Mails an die Waehler + multiple-votes Fehlermail: Widerspruechliche Stimmen im Wahlschein + no-ballot Fehlermail: Kein Wahlschein in Mail gefunden + no-ballotid Fehlermail: Keine Wahlscheinkennung gefunden + no-votes Fehlermail: Keine Stimmen abgegeben + result-multi Template fuer ein Wahlergebnis mit mehreren Gruppen + result-single Template fuer ein Wahlergebnis mit einer Gruppe + result-proportional Template fuer ein Wahlergebnis, bei dem das Verhaeltnis + oder die Differenz von Ja- und Nein-Stimmen entscheidet + (z.B. Moderationsnachwahlen, "proportional=1" gesetzt) + rule-violated Fehlermail: Wahlregeln aus usevote.rul verletzt + voterlist Template fuer eine Waehlerliste (2. CfV) + votes-multi Template fuer eine Stimmenliste mit mehreren Gruppen + votes-single Template fuer eine Stimmenliste bei einer Gruppe + wrong-ballotid Fehlermail: Falsche Wahlscheinkennung angegeben + +* Beim Programmlauf erstellte Dateien (Name ist konfigurierbar): + +ergebnis.alle Saemtliche Einzelergebnisse zusammengefasst (wird + bei jedem Lauf von uvvote.pl neu erstellt) +errors.log Fehlermeldungen, die beim Programmlauf auftauchen, + werden hier abgelegt. Normalerweise bleibt die Datei + leer, ansonsten wird beim Programmende eine Warnung + mit Verweis auf die Datei ausgegeben. +uidlcache Eindeutige IDs der abgerufenen Mails (bei Benutzung + von POP3). Darf waehrend einer laufenden Abstimmung + keinesfalls geloescht werden, um Mehrfachzaehlung von + Stimmen zu verhindern. +uidlcache_req dto, fuer die Mailbox mit den Wahlscheinanforderungen + (bei Verwendung von personalisierten Wahlscheinen) +uidlcache_bounce dto, fuer die Mailbox mit den Bounces (wird von + uvbounce.pl ausgewertet) +usevote.lock Temporaere Lockdatei, um gleichzeitiges Ausfuehren + mehrerer Instanzen zu verhindern. Kann (falls vorhanden) + gefahrlos geloescht werden, wenn gerade keines der + Usevote-Scripts laeuft. + +* Folgende Unterverzeichnisse werden von Usevote im aktuellen + Verzeichnis (CWD) angelegt: + +fertig/ Verzeichnis fuer Archivierung verarbeiteter Dateien +tmp/ Verzeichnis fuer temporaere Dateien + +* Von Usevote dort erstellte Dateien: + +ack.* Bestaetigungsmails (in tmp/) +ack.control Steuerungsdatei fuer Bestaetigungsmails (in tmp/) +domail Shellscript zum Verschicken der Mails (in tmp/) +ergebnis.* Ergebnisdateien einzelner Durchlaeufe (zunaecht in tmp/, + spaeter nach fertig/ verschoben und archiviert) +stimmen.* Archivierte Stimmendateien (dto.) + + +5. Anfangs-Konfiguration +======================== + +Zuerst solltest Du "usevote.cfg" fuer Dein System anpassen (Details +dort in den Kommentarzeilen und weiter hinten in dieser Dokumentation). +Unter Windows werden die Regular Expressions fuer die Stimmenerkennung +moeglicherweise lauter unlesbare Zeichen enthalten (anderer Zeichensatz), +dort sollten saemtliche in Namen vorkommende Umlaute und Buchstaben mit +Accents etc. aufgefuehrt werden. Weiter ist bei Nutzung von Windows zu +beachten, dass das Kommando zum Loeschen des Bildschirms "cls" und nicht +"clear" heisst. Dazu gibt es auch eine Einstellung in usevote.cfg. Den +standardmaessig eingestellten Pager "less" gibt es unter Windows zunaechst +nicht, more ist aber so buggy, dass man sich am besten "less" fuer +Windows herunterladen sollte: +http://www.greenwoodsoftware.com/less/less381d.zip + +Im Unterverzeichnis "templates" findest Du Vorlagen, die fuer die +einzelnen Bestaetigungstexte, Mails, Wahlscheine und Waehlerlisten +verwendet werden. Hier kannst Du unter Beachtung der Syntax (siehe +gesonderte Dokumentation) alle Texte an Deinen Geschmack und Deine +Beduerfnisse anpassen. + + +6. Konfiguration fuer einzelne Abstimmungen +=========================================== + +Fuer jede Abstimmung muss eine eigene Konfigurationsdatei geschrieben +werden. Aendere einfach usevote.cfg entsprechend der Regeln fuer die +betreffende Abstimmung. Falls Du haeufiger Wahlleiter spielst, solltest +Du Dir eine globale Konfigurationsdatei mit Standardeinstellungen +(z.B. als ~/.usevote-defaults.cfg) anlegen und in der usevote.cfg nur +noch die Einstellungen setzen, die sich fuer jede Abstimmunge aendern. +Die globale Datei kann am Anfang der usevote.cfg mit folgender Zeile +eingebunden werden: + +include ~/.usevote-defaults.cfg + +Zu beachten ist aber, dass jeweils die letzte Definition einer Option +benutzt wird. Die usevote.cfg sollte daher wirklich nur noch +abstimmungsspezifische Einstellungen enthalten, um nicht versehentlich +die globalen Optionen zu überschreiben. + +Ausserdem muss die Datei "usevote.rul" geprueft werden. Wenn Deine +Abstimmung irgendwelche speziellen Regeln enthaelt (z.B. man kann +nur fuer Gruppe x stimmen, wenn man auch fuer y gestimmt hat, oder man +muss fuer den .misc-Abschnitt stimmen, wenn man fuer irgendeinen +anderen Abschnitt gestimmt hat), dann solltest Du diese Datei lesen +und entsprechend aendern. Allerdings empfiehlt es sich, bereits im +RfD deutlich auf die Regeln hinzuweisen, die zur Anwendung kommen sollen. +Die eingegebenen Regeln koennen dann mit "uvvote -t" getestet werden. + +Es ist auch moeglich, mehrere Abstimmungen gleichzeitig durchzufuehren, +ohne die .pl und .pm Dateien jedesmal mitzukopieren. Folgende Dateien +sollten im Abstimmungsverzeichnis vorhanden sein (notwendig ist nur +usevote.cfg, darin können alle anderen Pfade angepasst werden): + +bdsgtext.cfg +mailpatterns.cfg +messages.cfg +scheinkennungen +usevote.cfg +usevote.rul + +Ggf. sollte das "templates"-Verzeichnis kopiert werden, falls +individuelle Templates fuer einzelne Abstimmungen verwendet werden. + +Außerdem muessen i.d.R. einige Pfade in der usevote.cfg angepasst +werden. Dadurch ist auch die Angabe eines anderen Verzeichnisses +fuer einige der oben genannten Konfigurationsdateien moeglich, +falls diese zentral gepflegt werden sollen (z.B. messages.cfg und +mailpatterns.cfg). + +Damit die Usevote-Perlmodule gefunden werden, sollten sie im +selben Verzeichnis wie die Programmdateien liegen bleiben. Ansonsten +muss eine Umgebungsvariable gesetzt werden: + +PERL5LIB=.. + +.. steht für das übergeordnete Verzeichnis. Ggf. muss hier ein +absoluter Pfad eingetragen werden, wenn das Abstimmungsverzeichnis +kein Unterverzeichnis des Usevote-Verzeichnisses ist. + +Je nach Betriebssystem kann die obige Zeile etwas anders aussehen, +unter Windows sollte z.B. "SET PERL5LIB=C:\PERL\USEVOTE" in die +autoexec.bat aufgenommen oder sonstwie ausgeführt werden (Pfad +natürlich entsprechend anpassen). + + +7. Wahlschein generieren +======================== + +Der Wahlschein ist ein Abschnitt des CfV, den die Leute zur Stimmabgabe +ausfuellen und zurueckschicken sollen. Am einfachsten erzeugst Du +diesen nach Anpassung der Konfiguration und ggfls. des Templates +mit "uvballot.pl". Folgendes Format ist erforderlich: + +#1 [ Stimme ] +#2 [ Stimme ] +[...] + +Eine zusaetzliche Beschreibung rechts daneben macht es dem Waehler +einfacher, ist aber fuer Usevote nicht zwingend erforderlich. Wichtig +sind nur die fortlaufenden Nummern, die entsprechend der Eintraege +in usevote.cfg zugeordnet werden. + +Je nach Konfiguration koennen weitere Zeilen noetig sein, dazu spaeter. + +Als Stimme in den eckigen Klammern werden diverse Strings erkannt, +die in usevote.cfg definiert werden koennen. JA, NEIN und ENTHALTUNG +sind Standard, einige Abwandlungen sind ebenfalls moeglich. +Zusaetzlich kann ANNULLIERUNG verwendet werden, um eine schon abgegebene +Stimme zu loeschen und somit nicht im Result aufzutauchen. + +Bei der Verwendung von persoenlichen Wahlscheinen (Abschnitt 6a der +Wahlregeln fuer de.*) ist das Vorgehen etwas anders, siehe hierzu +den gesonderten Abschnitt weiter hinten. + + +8. Stimmen verarbeiten +====================== + +Falls POP3 nicht benutzt werden soll (siehe usevote.cfg), werden alle +Wahlmails zu einer Abstimmung aus einer Mailboxdatei gelesen +(Name/Pfad der Datei in usevote.cfg einstellbar). Standardmaessig +wird das Unix-Mailboxformat erkannt, dieses ist aber ueber "mailstart" +in usevote.cfg konfigurierbar (bei komplett anderen Formaten muesste +der Quellcode geaendert werden). + +Wechsele nun in das Verzeichnis, in dem die passenden +Konfigurationsdateien liegen (normalerweise werden diese aus +dem aktuellen Verzeichnis gelesen, was mehrere gleichzeitige +Verfahren moeglich macht) und starte uvvote.pl. Bei Problemen +mit einzelnen Stimmen wirst Du interaktiv durch Menues gefuehrt. +Meistens gibt es folgende Moeglichkeiten, die durch die +eingeklammerten Buchstaben und Zahlen aufgerufen werden koennen: + +(1) Anzeigen der Wahlmail + +Bestaetigen oder Aendern von Wahlschein-Eigenschaften: +(2) Mailadresse +(3) Waehlername +(4) Stimmen +(5) Scheinkennung +(6) Datenschutzklausel + +(i) Diese Stimme ignorieren (ohne Benachrichtigung verwerfen) +(w) Weiter + +Teilweise gibt es Unteroptionen: + (a) OK (Zweifel sind unbegründet, ist alles in Ordnung) + (b) Aendern (z.B. wenn der Realname von Usevote nicht vollstaendig + erkannt wurde) + (c) Ungueltig (es handelt sich nach Deinem Ermessen nicht um + eine gueltige Stimmabgabe) + +Nun solltest Du zunaechst mit (1) die Mail ansehen und anhand +der bemaengelten Punkte entscheiden, ob der Wahlschein gueltig +oder ungueltig ist bzw. welche Maengel tatsaechlich zutreffen. + +Mit den Optionen (2) bis (6) kannst Du einzelne Eigenschaften +des Wahlscheins nachbessern (wenn Usevote z.B. einen Namen oder +eine Stimme nicht korrekt erkannt hat) oder auch einfach die +Korrektheit bestaetigen. + +Wenn Du alle oben auf der Seite aufgelisteten Maengel beseitigst, +indem Du die entsprechenden Optionen waehlst, wird die Stimme +akzeptiert. Bleiben Zweifel unausgeraeumt, wird eine entsprechende +Fehlermail verschickt (Du erhaeltst dann noch eine gesonderte +Warnung). + +Zum Schluss kannst Du (w) wählen, um fortzufahren. Möchtest Du, +dass eine Stimme komplett ignoriert wird (weil es sich z.B. +um Spam handelt), kannst Du (i) waehlen und die folgende +Sicherheitsfrage mit "JA" beantworten. + +Ist uvvote.pl durchgelaufen, koennen im Unterverzeichnis tmp/ +(wird ebenfalls aktuellen Verzeichnis angelegt!) die ermittelten +Ergebnisse und erzeugten Mails an die Waehler noch einmal geprueft +werden. Ist alles in Ordnung, werden mittels "uvvote.pl clean" +die Mails verschickt und die Ergebnisse gespeichert sowie die +Originalmails archiviert. Hierzu wird das Verzeichnis fertig/ +verwendet. + +Falls der komplette uvvote-Lauf verworfen werden soll, kann +die Datei mit den Waehlermails (stimmen-xyz im tmp-Verzeichnis, +xyz entspricht der aktuellen Unixtime waehrend des Durchlaufs) +wieder an den urspruenglichen Platz zurueckkopiert werden +(bei POP3: fuer den Neuversuch POP3 abschalten und die +Maildatei einlesen lassen, da auf dem Server die Mails +moeglicherweise geloescht wurden) und das komplette tmp-Verzeichnis +geloescht werden. Anschliessend den uvvote-Lauf erneut durchfuehren. +Wichtig ist nur, dass das Verzeichnis fertig/ waehrend der ganzen +Abstimmung mitsamt seines Inhaltes erhalten bleibt. + +Noch ein kurzes Wort zum Verschicken der Mails: Jede Mail wird +in einer gesonderten durchnummerierten Datei (ack.1, ack.2, ...) +gespeichert, zusaetzlich wird eine Steuerungsdatei ("ack.control") +erzeugt, welche zu jeder Mail Dateinamen und Empfaenger auflistet. +Je nach Einstellung werden die Mails dann per SMTP verschickt oder +es wird ein Shellscript ("domail") erzeugt, welches nach und +nach die Mails in Sendmail einspeist. Zwecks Serialisierung ist +in usevote.cfg die Einstellung "sleepcmd" konfigurierbar, +normalerweise wird ein "sleep 1" nach jeder Mail ausgefuehrt. +Das Verschicken der Mails (und damit ggfls. der Aufruf von "domail") +erfolgt automatisch mit "uvvote.pl clean". + +Unter Windows ist nur SMTP moeglich, es sei denn, es gibt +die Befehle sendmail, sleep und rm ;-) + + +9. Unzustellbare Bestaetigungen +=============================== + +Leider kommt es immer mal wieder vor, dass jemand bei der Abstimmung +eine ungueltige Mailadresse angegeben hat. In diesem Fall erhaeltst Du +einen Bounce an die Absenderadresse zurueck (diese laesst sich in +usevote.cfg mit der Option "envelopefrom" einstellen bzw. bei +direkten Aufruf eines Mailers über die entsprechende Kommandozeilenoption +in der mailcmd-Einstellung, z.B. bei Sendmail -f). Du solltest +solche Fehlermails in einer gesonderten Datei speichern oder in +einem gesonderten POP3-Postfach halten und beim Erstellen +des 2. CfVs und des Results uvbounce.pl aufrufen. + +Ausgegeben wird von uvbounce.pl eine Liste mit ungueltigen Adressen, +die einfach an den Abschnitt "ungueltige Stimmen" des 2. CfVs bzw. +Results angehaengt werden kann. Nicht vergessen, die Stimmen manuell +aus der Liste der gueltigen Stimmen zu loeschen (falls Du die +Policy verfolgst, nur Stimmen mit gueltiger Absenderadresse zu werten, +was empfehlenswert ist) und bei Bedarf das Ergebnis zu korrigieren! + + +10. 2.CfV / Result +=================== + +Zum Erstellen des 2. CfVs kannst Du erst einmal den 1. CfV als Vorlage +benutzen. Am Ende sollte allerdings eine Liste der Personen folgen, +die bereits abgestimmt haben. Diese laesst sich mit "uvcount.pl -l" +erzeugen. Moeglicherweise fragt uvcount.pl wegen scheinbar doppelter +Stimmabgaben nach und bietet an, eine oder beide Stimmen zu ignorieren. +Du solltest das dann genauer untersuchen und die passende Wahl treffen. + +Vorsicht bei doppelten Namen: Es gibt einige doppelt vorkommende +Namen, hinter denen sich voellig unterschiedliche Personen +verbergen! Die Mailadresse sollte in solchen Faellen Auskunft +geben koennen, notfalls einmal nachfragen. + +Das Endergebnis kannst Du mit "uvcount.pl -r" ausgeben lassen. +Bei Verfahren mit mehreren Abstimmungspunkten wird automatusch +das folgende tabellarische Format verwendet: + + Ja Nein : 2/3? >=60? : ang.? : Gruppe +==== ==== : ==== ===== : ===== : ======================================= + 100 70 : Nein Ja : Nein : Einrichtung von xyz + +Bei Abstimmungen ueber nur einen Punkt gibt es auch die Moeglichkeit, +einen einfachen beschreibenden Text zu verwenden: +"Es gab 100 JA und 70 NEIN-Stimmen ...". + +Falls in usevote.cfg die Einstellung "multigroup = 1" gesetzt ist, +wird immer die tabellarische Form gewaehlt, bei "multigroup = 0" +benutzt uvcount.pl bei nur einem Abstimmungspunkt den beschreibenden +Text. Überschreiben laesst sich dieses mit Aufrufparametern: + +uvcount.pl -r -m = Tabellarisch (m = multigroup) +uvcount.pl -r -o = Text (o = onegroup) + +Falls in usevote.cfg die Option "proportional = 1" aktiviert ist, +wird standardmaessig folgendes Ausgabeformat angewandt: + + Ja Nein : J>=N? Ja/Nein : ang.? : Gruppe +==== ==== : ===== ======= : ===== : ==================================== + 100 70 : Ja 1.429 : : Einrichtung von abc + 80 90 : Nein 0.888 : : Einrichtung von xyz + +Das genaue Aussehen ist im Template "result-proportional" aenderbar, +die Bedingungen und Auswertungsformeln sind ueber usevote.cfg +einzustellen (prop_formula und condition1). Die Spalte "ang.?" muss +in diesem Fall manuell ausgefüllt werden, da manchmal auch mehrere +Abstimmungsgegenstände angenommen werden, z.B. bei Moderationsnachwahlen +für verschiedene Posten. + +Die Waehlerliste mit abgegebenen Stimmen erzeugst Du mit +"uvcount -v". Diese Option laesst sich auch mit -r kombinieren, +so dass Du Dir das doppelte Aussortieren von Duplikaten sparst. +Wichtig: Diese Liste darf erst im Result veroeffentlicht werden, +nicht im 2. CfV! + +Falls ein trivialer Schutz vor Spammern gewuenscht ist, koennen durch +eine Aenderung in den Template "voterlist", "votes-single", "votes-multi" +und "bouncelist" alle Mailadressen verfremdet werden. Entweder Du fuegst +folgende Definition ein: + +mail := value mail | replace '@' ' -at - ' + +Oder Du veraenderst direkt die vorgegebenen Formate, indem Du +die Mailadresse mit Hilfe des Pipe-Symbols durch die replace-Funktion +schickst: + +multi-line := value mail | replace '@' ' -at- ' | justify-behind name 72 + +Die Replace-Funktion erwartet wie im Beispiel gezeigt zwei Werte, +die zu ersetztende Zeichenfolge (hier das '@') und eine neue Zeichenfolge, +die statt dessen eingesetzt werden soll und frei gewaehlt werden kann. + +Wie bereits beschrieben, sollte am Ende des 2. CfVs bzw. Results noch die +Liste der gebouncten Mailadressen angehaengt werden (z.B. mit dem Programm +uvbounce.pl) und bei Bedarf die urspruenglichen Stimmen geloescht und das +Ergebnis korrigiert werden. + + +11. Datenschutz +=============== + +Zum Schutz der deutschen Wahlleiter wurden spezielle Klauseln +aufgenommen, die vom Waehler die Erlaubnis zur Verarbeitung +der Stimme verlangen. Die Option "bdsg" in usevote.cfg schaltet +diese Sonderbehandlung ein oder aus. Folgende Besonderheiten +gelten bei Aktivierung: + +- Im Wahlschein ist ein spezieller Hinweistext enthalten, der + aus der Datei bdsgtext.cfg genommen wird. Beim Verarbeiten + abgegebener Stimmen wird der Abschnitt wieder mit der Datei + verglichen. Fehlt er oder wurde er veraendert, wird der + Wahlschein dem Wahlleiter zur Kontrolle vorgelegt. + Die Fehlerpruefung ignoriert Quote- und Leerzeichen sowie + Zeilenumbrueche, kann aber versagen, wenn dem Quotezeichen + Initialen vorangestellt sind (wie in manchen Mailboxnetzen + ueblich) oder eine Silbentrennung ueber den Text gelaufen ist. + +- Ausserdem enthaelt der Wahlschein eine spezielle Frage, + die mit JA zu beantworten ist. Anderenfalls muss ebenfalls + Kontrolle durch den Wahlleiter erfolgen. Der Text dieser + Frage ist ueber usevote.cfg einzustellen. + +Erfolgt eine derartige Kontroll-Vorlage beim uvvote.pl-Lauf, +sollte der Wahlleiter sich den Wahlschein genau anschauen +und dann entweder im Menue die Option (6)(a) waehlen oder +mit (w) fortfahren. Im letzteren Fall wird die Stimme +verworfen und eine Hinweismail zurueckgeschickt. + +Wenn eine Wahlbestaetigung veraendert und zurueckgeschickt +wird, fehlt der entsprechende Datenschutzhinweis. Da in +diesem Fall die Zustimmung aber bereits bei der ersten +Stimmabgabe gegeben wurde, kann der Wahlleiter beruhigt +die Korrektheit wie oben beschrieben bestaetigen. + + +12. Personalisierte Wahlscheine +=============================== + +Die Wahlregeln fuer de.* sehen im Abschnitt 6a ein Verfahren +mit persoenlichen Wahlscheinen vor, die mit einer speziellen +Kennung versehen mehr Sicherheit vor Manipulation bieten. + +Um dieses Verfahren einzuschalten, muss zunaechst die Option +"personal = 1" in usevote.cfg gesetzt werden und das Template +"ballot-personal" im Unterverzeichnis "templates" um den Text +des CfVs erweitert werden (einfach statt des von Sternchen +umrandeten Kommentars in der Datei einfuegen). Bitte eventuell +im Text vorkommende eckige Klammern wie folgt unschaedlich +machen: \[Text\] (Backslash voranstellen), da in den Templates +diese Klammern spezielle Bedeutungen haben (siehe gesonderten +Abschnitt zu dem Thema in dieser Datei). + +Der gepostete CfV enthaelt i.d.R. nur einen Dummy-Abschnitt +mit Hinweis auf die gesonderte Wahlscheinanforderung. So ein +Abschnitt kann mit uvballot.pl generiert werden. + +Der dana-Moderation sollte beim Einreichen des CfVs auch +ein Dummy-Wahlschein mitgesendet werden, wie er bei der +Wahlscheinanforderung verschickt wird. Dieser laesst sich mit +"uvcfv.pl -t" erzeugen. + +Die eingehenden Wahlscheinanforderungen sollten an eine +spezielle Mailadresse gehen und dann mit uvcfv.pl verarbeitet +werden. Je nach Konfiguration in usevote.cfg werden die +Mails aus einer Mailboxdatei oder per POP3 eingelesen und jeweils +mit einem personalisierten Wahlschein beantwortet. Bitte vor +dem Posten des CfVs einen Test durchfuehren, um sicherzustellen, +dass die generierten Wahlscheine korrekt sind und auch bei +der Auswertung akzeptiert werden (hierbei wird die speziell +eingefuegte Wahlscheinkennung in Verbindung mit der Absenderadresse +geprueft). + + +13. Die einzelnen Programme +=========================== + +uvballot.pl + Generiert den Wahlschein fuer den CfV. Bei personalisierten + Wahlscheinen (personal = 1 in usevote.cfg) wird nur ein Dummy- + Abschnitt mit Hinweisen zur Wahlscheinanforderung ausgegeben. + + Die Vorlage fuer den Wahlschein ist im templates-Unterverzeichnis + konfigurierbar: + - ballot Standard-Wahlschein (personal=0) + - ballot-request Dummy-Abschnitt mit Hinweisen zur + Wahlscheinanforderung (personal=1) + - ballot-personal Wahlschein mit Scheinkennung, wird von + uvcfv.pl verwendet + + Aufrufparameter: + + -c config_file liest die Konfiguration aus config_file + (usevote.cfg falls nicht angegeben) + -h + --help zeigen einen Hilfetext an + + +uvbounce.pl + Liest Bounces aus den uebergebenen Dateien oder aus einer POP3- + Mailbox ein (Verhalten haengt von usevote.cfg ab) und generiert + eine Liste von unzustellbaren Adressen, die an den 2. CfV oder das + Result angehaengt werden kann. Falls POP3 in usevote.cfg + eingeschaltet und die Option -f (siehe unten) nicht benutzt wurde, + werden die uebergebenen Dateinamen ignoriert. + + Die Vorlage fuer die ausgegebene Liste ist im templates- + Unterverzeichnis konfigurierbar (Datei "bouncelist"). + + Aufrufparamter: + + -c config_file liest die Konfiguration aus config_file + -f dateiname(n) + --file dateiname(n) lesen die Bounces aus den uebergebenen Dateien, + auch wenn in der Konfigurationsdatei POP3 + eingeschaltet ist. Diese Option wird benoetigt, + falls zwar die Stimmzettel per POP3 eingelesen + werden sollen, nicht aber die Bounces. + Die Dateinamen werden durch Leerzeichen getrennt. + -h + --help zeigen einen Hilfetext an + + +uvcfv.pl + + Liest Mailboxen ein und beantwortet alle Mails mit personalisierten + CfVs (falls personal=1 in usevote.cfg). + + Die Vorlage fuer die ausgegebene Liste ist im templates- + Unterverzeichnis konfigurierbar (Datei "ballot-personal"). + + Aufrufparamter: + + -c config_file liest die Konfiguration aus config_file + (usevote.cfg falls nicht angegeben) + -t + --test gibt einen Dummy-Wahlschein mit Scheinkennung + an der Standardausgabe aus, zur Pruefungszwecken + -h + --help zeigen einen Hilfetext an + + +uvcount.pl + + Zaehlt Stimmen und gibt Waehlerlisten aus. Dient zur Erstellung + von 2.CfV und Result. + + Es werden die folgenden Vorlagen aus dem templates-Unterverzeichnis + benutzt: + + - result-multi Ergebnisauszaehlung bei mehreren Gruppen + - result-single Ergebnisauszaehlung bei nur einem Abstimmungs- + gegenstand (nicht tabellarisch, sondern im + Fliesstext), falls in usevote.cfg multigroup=0 + gesetzt ist oder die Option -o verwendet wird + (siehe unten) + - voterlist Reine Auflistung der bisherigen Waehler ohne + Stimmen (fuer 2. CfV) + - votes-multi Stimmenliste im Mehrgruppenformat (eine Liste + mit allen Waehlern, rechts daneben die jeweils + abgegebenen Stimmen) + - votes-single Stimmenliste im Eingruppenformat (getrennte + Listen mit Ja-, Nein- und Enthaltungsstimmen) + + Aufrufparamter: + + -c config_file liest die Konfiguration aus config_file + (usevote.cfg falls nicht angegeben) + -f result_file liest die Stimmen aus result_file (ueberschreibt + die "resultfile"-Angabe aus der Konfigurationsdatei) + -l + --list Geben eine Liste aller Waehler aus (ohne Stimmen). + -v + --voters Wie --list, aber mit Angabe der abgegebenen Stimmen. + -r + --result Ausgabe des Endergebnisses (kann mit --list oder + --voters kombiniert werden). + -m + --multigroup Benutzt auch bei Eingruppenabstimmungen das + Mehrgruppenformat beim Endergebnis (ueberschreibt + die Einstellung aus usevote.cfg). + Nur in Kombination mit --result verwendbar, + schliesst --onegroup aus. + -o + --onegroup Benutzt bei Eingruppenabstimmungen immer das + Eingruppenformat beim Endergebnis (ueberschreibt + die Einstellung aus usevote.cfg). + Nur in Kombination mit --result verwendbar, + schliesst --multigroup aus. + -n + --nodup Verzichtet auf das Aussortieren von doppelten + Stimmabgaben. Nicht empfohlen! + -h + --help zeigen einen Hilfetext an + + + +uvvote.pl + + Liest Mailboxen aus einer Datei oder per POP3 ein wertet die Mails + als Stimmzettel aus. Erst beim Aufruf mit der Option "clean" werden + die Ergebnisse endgueltig gespeichert und die Bestaetigungsmails + verschickt. + + Es werden diverse Vorlagen aus dem templates-Unterverzeichnis + benutzt, um Bestaetigungs- und Fehlermails zu generieren. + + Aufrufparameter: + + -c config_file liest die Konfiguration aus config_file + (usevote.cfg falls nicht angegeben) + -t + --test fuehrt einen Test der Konfiguration durch und + gibt das ermittelte Ergebnis aus. + -h + --help zeigen einen Hilfetext an + + + +14. Die Konfigurationsdateien +============================= + +mailpatterns.cfg +---------------- + +Diese Datei enthaelt einen regulaeren Ausdruck (Perl-Syntax) pro +Zeile. Passt die Mailadresse eines Abstimmenden auf eines der Muster, +wird dem Wahlleiter bei der Auswertung eine Warnung angezeigt. +In der Regel kann man diese ignorieren, weil heutzutage viele +Privatleute ihre eigene Domain haben und Role-Accounts wie news@ +benutzen. Sieht man aber so etwas wie news@t-online.de ist +Vorsicht angebracht und man sollte durch Auswahl von "(W)eiter" +die Stimme ungueltig werten und eine entsprechende Fehlermail +zurueckschicken lassen. + + +messages.cfg +------------ + +Hier sind alle kuerzeren Texte enthalten, die Usevote ausgibt, +unabhaengig davon, ob sie nur dem Wahlleiter bei der Programmausfuehrung +ausgegeben werden oder in persoenlichen Mails bzw. dem Result +auftauchen. Laengere Textpassagen sind als eigene Dateien im +"templates"-Verzeichnis zu finden und bieten weitergehende +Formatierungsmoeglichkeiten. + +Jede Zeile in der messages.cfg besteht aus einem Identifier, einem +Gleichheitszeichen und dem eigentlichen Text. Der Identifier ist +grundsaetzlich in Grossbuchstaben gehalten und vom Programmcode fest +vorgegeben. Aenderungen duerfen nur rechts vom Gleichzeichen vorgenommen +werden, wenn Dir z.B. Formulierungen nicht gefallen oder Du anders- +sprachige Ausgaben haben moechtest. + +Variablen sind ueber ${VARIABLE} einzubauen, allerdings sind die +Variablennamen und deren Inhalte vom Programmcode vorgegeben und +sollten beibehalten werden. Es gibt keine vordefinierten Variablen, +die Du selbst einbauen kannst, es sei denn, Du erweiterst den +Programmcode an der entsprechenden Stelle. + +Die einzigen Texte, die fest im Programmcode stehen, sind die +Hilfetexte der einzelnen Programme (ueber die Option -h oder --help +aufrufbar) sowie die Startmeldungen (Copyright etc.) und initiale +Fehlermeldungen (usevote.cfg oder messages.cfg nicht gefunden, +ungueltige oder falsch kombinierte Aufrufparameter). + + +usevote.rul +----------- + +Mit dieser Datei koennen spezielle Regeln fuer eine gueltige Stimmabgabe +bei Mehrgruppenabstimmungen erstellt werden, z.B. dass man Punkt 3 oder 4 +waehlen muss, wenn man fuer Punkt 2 gestimmt hat (quasi als weitergehende +Auswahlmoeglichkeit). Solche Regeln verkomplizieren aber die Abstimmung +und sollten nur in Ausnahmefaellen benutzt werden. Urspruenglich gedacht +war diese Moeglichkeit z.B. fuer .misc Gruppen, die zwangsweise bei einer +Aufteilung mit angelegt werden mussten. Dadurch, dass das in den +Wahlregeln fuer de.* als Automatismus eingebaut ist, kann man sich +so etwas meistens sparen. Fuer den Fall, dass bei einer vorgeschlagenen +neuen Gruppe der Name noch unklar ist, gibt ebenfalls das Benutzen +von speziellen Wahlregeln wenig Sinn, da man auch eine Meinung ueber +den Gruppennamen haben kann, wenn man eigentlich gegen die Gruppe ist. + +Wenn Du aber ein Verfahren betreust, in dem Du solche Sonderregeln +benoetigst, kannst Du eine Regel pro Zeile in der folgenden Syntax schreiben: + +if .jjjjj then J.... + +Eine Regel beginnt immer mit "if", und danach folgen eine Anzahl Symbole; +diese Anzahl muss gleich der Anzahl der Gruppen sein, ueber die abgestimmt +wird. Oben geht es also um eine Abstimmung ueber sechs Gruppen. +Die Symbole hinter "if" geben an, in welchem Fall diese Regel Anwendung +finden soll. Falls sie zutrifft, wird die Bedingung hinter "then" +geprueft (dort muessen nochmal so viele Symbole folgen, wie Gruppen +im Wahlschein vorkommen). Ist diese Bedingung nicht erfuellt, wird +der Wahlschein dem Wahlleiter als regelverletzend vorgelegt, und +wenn diese mit "(w)eiter" den Fehler bestaetigt, wird eine entsprechende +Fehlermeldung zurueckgeschickt und die Stimme als ungueltig gewertet. + +Folgende Symbole sind erlaubt: + + J eine JA-Stimme + N eine NEIN-Stimme + E eine Enthaltung + S eine JA- oder NEIN-Stimme + H eine Enthaltung oder JA-Stimme + I eine Enthaltung oder NEIN-Stimme + . egal (Ja, nein oder Enthaltung) + j eine oder mehrere der markierten Gruppen hat JA-Stimme + n "" "" "" "" "" "" "" NEIN-Stimme + e "" "" "" "" "" "" "" Enthaltung + s "" "" "" "" "" "" "" Ja- oder Nein-Stimme + h "" "" "" "" "" "" "" Enthaltung oder Ja-Stimme + i "" "" "" "" "" "" "" Enthaltung oder Nein-Stimme + +Hier noch ein Beispiel: + if S... then .ss. + if .S.. then ..E. + if ..S. then .E.. + +Diese Regeln sagen: Wer fuer die erste Gruppe abstimmt, der muss auch +fuer die zweite und dritte Gruppe abstimmen - egal wie. Ausserdem muss er +(Regeln 2 und 3) sich bei 3 enthalten, wenn er bei 2 eine Stimme abgibt +und umgekehrt. Die vierte Gruppe wird hier gar nicht betroffen. + + +usevote.cfg +----------- + +Dieses ist die zentrale Konfigurationsdatei fuer Usevote. Einige +Einstellungen sind nur einmal auf das System und Deine Gewohnheiten +anzupassen, andere muessen fuer jede Abstimmung geaendert werden. +Alle Einstellungen sind mit mehr oder weniger sinnvollen +Standardwerten vorgegeben, die sich bei leerer usevote.cfg mittels +"uvvote.pl -t" ermitteln lassen. + +Es lassen sich durch eine Zeile "include dateiname" weitere +Konfigurationsdateien einbinden. Die Stelle, an der dieses geschieht, +ist dabei wichtig, da die usevote.cfg von oben nach unten gelesen +wird (zusätzlich eingebundene Konfigurationsdateien werden an der +Stelle der "include"-Zeile eingefügt) und bei mehrmals vorkommenen +Optionen der jeweils letzte gesetzte Wert benutzt wird. +Daher sollte eine Datei mit systemweiten Defaults am besten am +Anfang der usevote.cfg eingebunden werden, die dort gesetzten +Optionen sollten aber unbedingt aus der usevote.cfg geloescht werden, +damit die systemweiten Einstellungen nicht gleich wieder ueberschrieben +werden. + +Das Format ist allgemein: + +Option = Wert + +Der Wert muss in manchen Faellen 0 (=aus) oder ungleich 0 (=an) sein, +in anderen Faellen muss eine exakte Zahl oder Zeichenkette eingegeben +werden. + +Alles, was hinter einem # Zeichen steht, wird als Kommentar gewertet +und ignoriert. Steht hinter einem Wert so ein Kommentar, wird +normalerweise alles bis zum Kommentarzeichen noch in den String +einbezogen. In so einem Fall sollte der Wert von Anfuehrungszeichen +umschlossen werden. Im folgenden Beispiel wird ein Dateiname +definiert: + +# Der Dateiname endet mit 10 Leerzeichen (bis zum Kommentarzeichen): +tpl_mailheader = mailheader # Kommentar + +# Der Dateiname enthaelt keine Leerzeichen, da mit Anfuehrungsstrichen +# begrenzt: +tpl_mailheader = "mailheader" # Kommentar + +Folgt kein Kommentar in der selben Zeile, koennen die Anfuehrungsstriche +weggelassen werden. + +Hier eine Auflistung der immer anzupassenden Optionen +(BOOL bedeutet, dass 0 (aus) oder 1 (an) eingetragen werden muss): + +votename Name der Abstimmung +group1 Erster Abstimmungsgegenstand +group2 Zweiter (und so weiter durchnummerieren) +votefile Mailbox mit eingehenden Stimmen (falls POP3 ausgeschaltet) +personal Personalisierte Wahlscheine verwenden? [BOOL] + (siehe gesonderten Abschnitt weiter oben) +voteaccount Mailadresse, unter der abgestimmt werden kann + (Reply-To im CfV auf diese Adresse setzen) +requestfile Datei mit Anforderungsmails bei "personal=1", falls + POP3 ausgeschaltet ist +bdsg Datenschutzklausel generieren und auf deren Existenz + pruefen? [BOOL] (siehe gesonderten Abschnitt weiter oben) +replyto Reply-To Header auswerten? [BOOL] (nicht empfohlen und in + de.* von den GVV nicht praktiziert) +voteack Jede Stimme mit einer Bestaetigungsmail beantworten? [BOOL] + (in de.* so ueblich) +mailcc weitere Mailadresse, an die eine Kopie jeder Bestaetigungs- + oder Fehlermail geschickt wird (fuer Archivzwecke) +onestep Mails direkt verschicken und Ergebnisse speichern? [BOOL] + ("uvvote clean" Aufruf entfaellt) +multigroup Fuer das Ergebnis auch bei Eingruppenabstimmung des + Mehrgruppenformat waehlen? [BOOL] +condition1 Bedingungen fuer einen Erfolg der Abstimmung in Perl-Syntax +condition2 (normalerweise "$yes>=2*$no" und "$yes>=60" +resultfile Datei fuer Gesamtergebnis (normalerweise ergebnis.alle) +idfile Datei mit Scheinkennungen (bei "personal=1") +pop3 POP3 benutzen? [BOOL] (andernfalls Stimmen aus Datei lesen) +pop3server POP3-Server fuer eingehende Wahlscheine +pop3port POP3-Serverport (normalerweise 110) +pop3user Benutzername +pop3pass Passwort +pop3delete Mails nach Abruf vom Server loeschen? [BOOL] +pop3uidlcache Dateiname zum Speichern bereits abgerufener Mail-IDs (UIDL) +pop3*_req entsprechende Optionen fuer Mailbox mit eingehenden + Wahlscheinanforderungen (bei "personal=1") +pop3*_bounce entsprechende Optionen fuer Mailbox mit Bounces (Fehlermails) + fuer die Verarbeitung durch uvbounce.pl +smtp SMTP zum Verschicken von Mails benutzen? [BOOL] + Andernfalls wird direkt an den MTA uebergeben (nur Unix) +smtpserver SMTP-Server +smtpport SMTP-Serverport (normalerweise 25) +smtpauth SMTP-Authentication nach RFC 2554 benutzen? [BOOL] +smtpuser SMTP-Benutzername +smtppass SMTP-Passwort +smtphelo String fuer die HELO-Greeting bei SMTP (Default: Hostname) +fqdn String fuer den Host-Teil der Message-ID + (Fully Qualified Domain Name) (Default: Hostname) +archivedir Verzeichnis fuer Archivierung von verarbeiteten Stimmen +tmpdir temporaeres Verzeichnis +templatedir Verzeichnis mit Templates (Vorlagen) fuer diverse Zwecke + (normalerweise "templates", dort liegen bereits vordefinierte + Templates, die i.d.R. nicht angepasst zu werden brauchen) +formats Konvertierungsfunktionen fuer die Templates, mit Komma getrennt + (mitgelieferte UVformats.pm muss hier aufgefuehrt werden, + desweiteren koennen eigene Module mit weiteren Funktionen + hier eingebunden werden) +controlfile Name der Steuerungsdatei fuer den Mailversand (tmp/ack.control) +domailfile Name des Shellscripts zum Versenden der Bestaetigungsmails + (falls smtp=0, normalerweise tmp/domail) +mailcmd Aufruf des Mail Transfer Agents (MTA) zum Verschicken der + Bestaetigungsmails (falls smtp=0), z.B. + "sendmail -oi -oem -femail@adresse" +sleepcmd Weiteres Kommando, welches nach jeder Mail aufgerufen werden + soll (falls smtp=0). Sinnvoll ist ein "sleep x", wobei x bei + langsamen Systemen hoeher gewaehlt werden sollte. +clearcmd Shellbefehl zum Loeschen des Bildschirms (Standard: clear), + muss unter Windows auf "cls" geaendert werden. Falls Shell + oder Betriebssystem keinen solchen Befehl bereitstellen, + sollte ein Kommando verwendet werden, welches eine Trennlinie + oder aehnliches auf dem Bildschirm ausgibt, z.B. mit "echo" +pager Shellbefehl zum seitenweisen Darstellen von Mails auf dem + Bildschirm (normalerweise "less"). Unter Windows muss "more" + benutzt werden, unter Unix hingegen gibt es moeglicherweise + bei "more" Probleme mit der Umleitung von STDERR in eine Datei + (wie es z.B. von uvvote.pl benoetigt wird), daher ist "less" + der Standardwert. +mailfrom Absender fuer den From-Header der Bestaetigungsmails. Wird + auch in Bestaetigungsmails als Unterschrift eingefuegt und + sollte daher am besten in folgendem Format sein: + Vorname Nachname + (ohne Anfuehrungsstriche, falls rfc-konform moeglich) +envelopefrom Absender fuer den Envelope (Return-Path) der Bestaetigungsmails + bei Verwendung von SMTP (bitte nur die Adresse eintragen, ohne + Klammern und Zusaetze). Wenn SMTP ausgeschaltet ist, muss + diese Einstellung entsprechend der Mailer-Doku in "mailcmd" + gesetzt werden (z.B. bei Sendmail und kompatiblen mit -f) +messagefile Datei mit diversen Meldungen und Textfragmenten (messages.cfg) +rulefile Datei mit Wahlregeln (usevote.rul) +badaddrfile Datei mit verdaechtigen Mailadressen (mailpatterns.cfg) +errorfile Datei fuer Fehlermeldungen beim Programmlauf (errors.log) +mailstart Einleitungszeile fuer naechste Mail (RegExp), falls Mails + aus einer Datei eingelesen werden (smtp=0), normalerweise + "^From" +begin_divider Trennlinien vor und nach dem Wahlschein +end_divider ("Alles vor/nach dieser Zeile bitte loeschen") +nametext Text fuer die Namens-Angabe im Wahlschein. Muss im + Wahlschein genauso stehen und wird beim Standardtemplate + auch aus dieser Einstellung uebernommen. Beispieltext: + "Dein Realname, falls nicht im FROM-Header:" +nametext2 Text fuer Namens-Angabe in Bestaetigungsmails + (dito, Standardwert "Waehlername:") +addresstext Text fuer die Adress-Angabe im Wahlschein + (dito, Standardwert "Waehleradresse:") +ballotidtext Text für die Angabe der Wahlscheinkennung (falls personal=1) + (dito, Standardwert "Wahlscheinkennung:") +bdsgtext Text fuer Datenschutzklausel (falls bdsg=1), erscheint als + Abstimmungspunkt (z.B. "Datenschutzklausel - Zustimmung: Ich + bin mit der Verarbeitung meiner Daten wie oben beschrieben + einverstanden") +bdsgfile Datei mit Erklaerungstext fuer BDSG-Klausel (bdsgtext.cfg) +rightmargin Zeilenbreite bei Ausgaben, die nicht durch Templates + gesteuert werden. Hat vor allem auf Bildschirmausgaben + Auswirkung. +name_re Regulaerer Ausdruck fuer Erkennung eines gueltigen Realnamens. + Beispiel: "[a-zA-ZäöüáàâéèêíìîóòôúùûÄÖÜÁÁÂÉÈÊÍÌÎÓÒÔÚÙÛß-]{2,} + + .*[a-zA-ZäöüáàâéèêíìîóòôúùûÄÖÜÁÁÂÉÈÊÍÌÎÓÒÔÚÙÛß]{2,}" + (muss ohne Umbruch in eine Zeile) + +Regulaere Ausdruecke fuer Erkennung der Stimmen +(Gross-/Kleinschreibung spielt keine Rolle): + +ja_stimme Standardwert: (J\s*A|J|(D\s*A\s*)?F\s*U\s*E\s*R) +nein_stimme Standardwert: (N\s*E\s*I\s*N|N|(D\s*A\s*)?G\s*E\s*G\s*E\s*N) +enth_stimme Standardwert: (E|E\s*N\s*T\s*H\s*A\s*L\s*T\s*U\s*N\s*G) +ann_stimme Standardwert: A\s*N\s*N\s*U\s*L\s*L\s*I\s*E\s*R\s*U\s*N\s*G + +Definition der verwendeten Templatedateien +(werden im Unterverzeichnis "templates" mitgeliefert): + +tpl_mailheader "mailheader" # generally used mail header +tpl_bouncelist "bouncelist" # used by uvbounce.pl +tpl_result_multi "result-multi" # used by uvcount.pl -r -m +tpl_result_single "result-single" # used by uvcount.pl -r -o +tpl_votes_multi "votes-multi" # used by uvcount.pl -v (multiple groups) +tpl_votes_single "votes-single" # used by uvcount.pl -v (single group only) +tpl_voterlist "voterlist" # used by uvcount.pl -l (2nd CfV) +tpl_ballot "ballot" # used by uvballot.pl (personal = 0) +tpl_ballot_request "ballot-request" # used by uvballot.pl (personal = 1) +tpl_ballot_personal "ballot-personal" # used by uvcfv.pl (personal = 1) +tpl_addr_reg "address-not-registered" # used by uvvote.pl (personal = 1) +tpl_no_ballotid "no-ballotid" # used by uvvote.pl (personal = 1) +tpl_wrong_ballotid "wrong-ballotid" # used by uvvote.pl (personal = 1) +tpl_bdsg_error "bdsg-error" # used by uvvote.pl (bdsg = 1) +tpl_ack_mail "ack-mail" # used by uvvote.pl (voteack = 1) +tpl_cancelled "cancelled" # used by uvvote.pl +tpl_invalid_account "invalid-account" # used by uvvote.pl +tpl_invalid_name "invalid-name" # used by uvvote.pl +tpl_multiple_votes "multiple-votes" # used by uvvote.pl +tpl_no_ballot "no-ballot" # used by uvvote.pl +tpl_no_votes "no-votes" # used by uvvote.pl +tpl_rule_violated "rule-violated" # used by uvvote.pl (siehe usevote.rul) + + +15. Templates +============= + +Die mitgelieferten Templates befinden sich im Verzeichnis "templates". +In usevote.cfg koennen weitere Verzeichnisse definiert werden, in +denen auch nach Templates gesucht wird ("templatedir", kommaseparierte +Liste mit Verzeichnissen). Außerdem koennen dort die Dateinamen der einzelnen +Templates eingestellt werden, wodurch es moeglich ist, in einzelnen +Abstimmungen andere, selbst definierte Templates zu verwenden, die in +einem gesonderten Verzeichnis abgelegt sind. + +Eine Templatedatei besteht aus zwei Teilen. Am Anfang werden die +Formatierungen bestimmter Schluessel definiert und nach einem Trenner +folgt der eigentlich Template-Koerper, der dann von Programm bearbeitet +und ausgegeben wird. + + format-key := function1 param | function2 param + + == TEMPLATE ==================================== + + Ich bin nun das eigentliche Template: + + format-key: [$format-key] + +Der Trenner beginnt mit den Zeichen '== TEMPLATE' danach koennen beliebige +Zeichen folgen um die beiden Sektionen optisch voneinander abzugrenzen. + +Wenn es keine Formatierungsanweisungen gibt, kann der Trenner auch +weggelassen werden. D.h. wenn kein Trenner gefunden wird, wird der +komplette Text als Template-Koerper betrachtet. + + +Template-Koerper +---------------- + +Im Template-Koerper werden die zu ersetzenden Token durch eckige +Klammern abgegrenzt. Sollen eckige Klammern im Text ausgegeben werden, +muessen diese durch einen Backslash freigestellt werden. + + [$termersetzung] [@schleife] nur eine \[ Klammer + + +Termersetzung: + + Ersetzt den Token durch den Wert des angegeben Schluessels: + + [$formatierung] [$schluessel] + + Es wird zuerst nach einer Formatierung mit den entsprechenden + Bezeichner gesucht. Ist diese vorhanden, werden die entsprechenden + Funktionen ausgefuehrt (siehe folgender Abschnitt). + + Kann kein Format gefunden werden, wird direkt in der im Programmcode + gefuellten Datenstruktur nach einem Schluessel mit dem angegeben + Bezeichner gesucht und sein Wert eingesetzt. Alle Einstellungen + aus der usevote.cfg sind automatisch als Schluessel definiert, es + kann also z.B. [$nametext] jederzeit verwendet werden. Eingesetzt + wird an der Stelle der mit "nametext = " definierte Text aus + usevote.cfg. + + Schlussendlich ist es noch moeglich einen default-Wert zu + definieren, der eingesetzt wird, wenn keiner der obigen Wege + erfolgreich war: + + Hallo [$name|Unbekannter]! + + Diese Zeile gibt, wenn kein Name definiert wurde, das Wort + "Unbekannter" aus. + + +Bedingte Verzeigung: + + Ueberprueft ob der Wert des angegebenen Formats/Schluessel boolsch + WAHR ist. Dementsprechend wird der then oder else Block eingefuegt: + + [?if|then|else] oder auch nur [?if|then] + + Die then/else Bloecke werden natuerlich auch auf Tokens geparst und + diese dementsprechend ersetzt. + + +Schleifen/Listen: + + Der nachfolgende Textblock wird fuer alle Elemente des durch den + Schluessel bezeichneten Arrays ausgefuehrt und eingefuegt. + + [@schluessel|block] oder [@schluessel|block|sep] + + Als zweiter Parameter kann ein Separtor definiert werden, mit dem + sich z.B. kommaseparierte Listen erzeugen lassen, da der Separator + eben nur zwischen den Elementen eingefuegt wird. + + Auch fuer Schleifen koennen Formatierungen genutzt werden.Allerdings + darf kein String zurueckgegeben werden, sondern ein Array mit einer + Menge von UVtemplate-Objekten. + + +Kommentare: + + Token die nach der Bearbeitungen entfernt werden: + + [# mich sieht man nicht] + + +Sonstiges: + + Um in Listen einen Zeilenumbruch zu erzwingen, muss lediglich ein + '\n' eingefuegt werden, falls eine kompakte Definition der Liste + erfolgen soll. + + [@names|[name] [email]\n] + + +Formatierungen +-------------- + +Eine Formatierung besteht eigentlich nur aus dem entsprechenden +Namen und einer beliebigen Anzahl von Funktionsaufrufen: + + format := funktion param1 "param 2" | funktion param + +Aehnlich der Unix-Shell-Funktionalitaet, wird dabei die Ausgabe +einer Funktion an die folgende weitergeleitet. So ist es moeglich, +verschiedenste simple Formatierungen zu kombinieren um nicht fuer +jeden Spezialfall eine neue Funktion schreiben zu muessen. + +Die jeweilige Formatierungsfunktion erhaelt als Input die +Datenstruktur, den Output der vorherigen Funktion und die definierten +Parameter in der entsprechenden Reihenfolge. + +Zahlen und einfache Bezeichner koennen direkt definiert werden. +Sollen Sonderzeichen oder Leerzeichen uebergeben werden, muessen +diese gequotet werden. Dazu kann ' oder " verwendet werden. + +Die Funktionen geben im Allgemeinen einen String zurueck. Im Rahmen +von Listen können auch Arrays uebergeben werden. + +Die erste Funktion duerfte ueblicherweise 'value' sein. Sie gibt +den Wert des angegeben Schluessel zurueck, der dann von den +folgenden Funktionen definiert wird: + + name-60 := value name | fill-right 60 + +Das Format "name-60" definiert also den Wert des Schluessel "name", +der um Leerzeichen aufgefuellt wird, bis eine Laenge von 60 Zeichen +erreicht wird. + + name-email := value name | justify-behind mail 72 + +"name-email" resultiert in einem String, der zwischen den Werten von +"name" und "email" genau so viele Leerzeichen enthaelt, damit der +gesamte String 72 Zeichen lang ist. + +Wird dieses Format in einer Liste angewandt, erhaelt man eine Tabelle +in der die linke Spalte linksbuendig und die rechte Spalte +entsprechend rechtsbuendig ist. + +Soweit ein kleiner Ueberblick ueber die Formatierungen. Ausfuehrliche +Funktionsbeschreibungen und weitere Beispiele finden sich in der +POD-Dokumentation des Moduls UVformat ("perldoc UVformat.pm"). + +Es ist moeglich, selbst eigene Module mit Formatierungsfunktionen +zu schreiben und ueber die Option "formats" in usevote.cfg einzubinden. +Am besten kann dazu UVformats.pm als Vorlage genommen werden. +Wichtig: der Name einer Formatierungsfunktionen darf nur aus +Gross- und Kleinbuchstaben, Zahlen und Minuszeichen bestehen. +Unterstriche, Punkte etc. sind nicht erlaubt! + +Falls eine elementare Funktion fehlt, kannst Du Dich auch gerne +bei mir melden und ich pruefe, wie sie sich einbauen laesst. + +Marc Langer, im Oktober 2005 diff --git a/UVconfig.pm b/UVconfig.pm new file mode 100644 index 0000000..c93f135 --- /dev/null +++ b/UVconfig.pm @@ -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 = ; + 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 = ; + 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 () { + 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 () { + chomp; + # Format: mailaddress (whitespace) ballot ID + if (/^(.+@.+)\s+([a-z0-9]+)/) { + # $1=mailadresse, $2=ballot ID + $ids{$1} = $2; + } + } + close(FILE); + return 0; +} + + +############################################################################## +# Funktionen für Templates laden # +############################################################################## + +sub load_formats { + my $modules = $config{formats}; + + my @modules = split(/\s*,\s*/, $modules); + + foreach my $module (@modules){ + if (-r $module){ + require $module; + } + } +} + + +############################################################################## +# config test # +############################################################################## + +sub test_config { + print UVmessage::get("CONF_CONFIG"), "\n\n"; + foreach my $option (keys %config) { + print "$option = $config{$option}\n"; + } + + print "\n", UVmessage::get("CONF_TEST_RULES"); + if (@rules) { + print "\n\n"; + for (my $n=0; $n<@rules; $n++) { + my $text = UVrules::rule_print($n); + print $text; + } + print "\n"; + } else { + print UVmessage::get("CONF_NO_RULES"), "\n\n"; + } +} + +1; diff --git a/UVformats.pm b/UVformats.pm new file mode 100644 index 0000000..09e79c5 --- /dev/null +++ b/UVformats.pm @@ -0,0 +1,566 @@ +#---------------------------------------------------------------------- + package UVformats; +#---------------------------------------------------------------------- + +=head1 NAME + +UVformats - Methoden zur Stringformatierung + +=head1 SYNOPSIS + + value + append + + fill-left + fill-right + fill-center + + justify + justify-before + justify-behind + + first-words + drop-words + create-lines + + multi-graph + multi-line + + quote + replace + sprintf + + 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' | ... + +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 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 werden die Zeichen vorranggestellt, +bei C angehaengt. C 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 haengt den Wert des Schluessel an das Ende des Strings, +C stellt es davor. + + justify-behind: existing-string.........value-of-key + justify-before: value-of-key.........existing-string + +C ist lediglich ein Alias auf C. + +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 +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 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 die Laenge des zu erzeugenden Strings. +C und C 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 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' => \"e, + 'replace' => \&replace, + 'sprintf' => \&sprintf, + + 'generate-date-header' => \&generate_date_header, + 'generate-msgid' => \&generate_msgid + ); +} + +1; + +#---------------------------------------------------------------------- + +=back + +=head1 SEE ALSO + +L + +=head1 AUTHOR + +Cornell Binder +Marc Langer diff --git a/UVmenu.pm b/UVmenu.pm new file mode 100644 index 0000000..d90da9c --- /dev/null +++ b/UVmenu.pm @@ -0,0 +1,380 @@ +# UVmenu: menu for interaction with the votetaker +# Used by uvvote.pl, uvcfv.pl, uvcount.pl + +package UVmenu; + +use strict; +use UVconfig; +use UVmessage; +use UVrules; +use vars qw($VERSION); + +use Text::Wrap qw(wrap $columns); + +# Module version +$VERSION = "0.4"; + +############################################################################## +# Menu for interaction with the votetaker # +# Parameters: votes list and header (references to arrays) # +# Body, Mailadress, Name, Ballot ID (references to strings) # +# List of newly set fields (reference to array) # +# List of errors to correct (Array-Ref) # +# Return Values: 'w': proceed # +# 'i': ignore (don't save vote) # +############################################################################## + +sub menu { + my ($votes, $header, $body, $addr, $name, $ballot_id, $set, $errors) = @_; + my $input = ""; + my $voter_addr = $$addr || ''; + my $voter_name = $$name || ''; + my @newvotes = @$votes; + my $mailonly = 0; + my %errors; + $$ballot_id ||= ''; + + foreach my $error (@$errors) { + + # unrecognized vote: extract group number und display warning + if ($error =~ /^UnrecognizedVote #(\d+)#(.+)$/) { + $errors{UnrecognizedVote} ||= UVmessage::get("MENU_UNRECOGNIZEDVOTE"); + $errors{UnrecognizedVote} .= "\n " . UVmessage::get("MENU_UNRECOGNIZED_LIST") + . " #$1: $2"; + + # violated rule: extract rule number and display warning + } elsif ($error =~ /^ViolatedRule #(\d+)$/) { + $errors{ViolatedRule} ||= UVmessage::get("MENU_VIOLATEDRULE", (RULE => "#$1")); + + } else { + # special handling if called from uvballot.pl + $mailonly = 1 if ($error =~ s/Ballot$//); + + # get error message for this error from messages.cfg + $errors{$error} = UVmessage::get("MENU_" . uc($error)); + } + } + + # This loop is only left by 'return' + while (1) { + + system($config{clearcmd}); + print UVmessage::get("MENU_PROBLEMS") . "\n"; + + foreach my $error (keys %errors) { + print "* $errors{$error}\n"; + } + + my $menucaption = UVmessage::get("MENU_CAPTION"); + print "\n\n$menucaption\n"; + print "=" x length($menucaption), "\n\n"; + print "(1) ", UVmessage::get("MENU_SHOW_MAIL"), "\n\n", + UVmessage::get("MENU_CHANGE_PROPERTIES"), "\n", + "(2) ", UVmessage::get("MENU_ADDRESS"), " [$voter_addr]\n"; + + # don't print these options if called from uvcfv.pl + unless ($mailonly) { + print "(3) ", UVmessage::get("MENU_NAME"), " [$voter_name]\n"; + print "(4) ", UVmessage::get("MENU_VOTES"), " [", @$votes, "]\n"; + print "(5) ", UVmessage::get("MENU_BALLOT_ID"), " [$$ballot_id]\n" + if ($config{personal}); + print "(6) ", UVmessage::get("MENU_BDSG"), "\n" if ($config{bdsg}); + } + + print "\n", + "(i) ", UVmessage::get("MENU_IGNORE"), "\n", + "(w) ", UVmessage::get("MENU_PROCEED"), "\n\n", + UVmessage::get("MENU_PROMPT"); + + do { $input = ; } 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 = ; + + } 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 = ; + } 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 = ; + 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 = ; + } 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 = ; + 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 = ; + } 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 = ; + 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 = ; + } 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 = ; + 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 = ; + } 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 ( 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 = ; + 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 = ; + chomp $input; + } until ($input eq '' || ($input >= 0 && $input<3)); + + return $input || $default; +} + +1; diff --git a/UVmessage.pm b/UVmessage.pm new file mode 100644 index 0000000..a52d8d5 --- /dev/null +++ b/UVmessage.pm @@ -0,0 +1,32 @@ +# UVmessages: parses resource strings and substitutes variables +# Used by all components + +package UVmessage; + +use strict; +use vars qw(@ISA @EXPORT_OK $VERSION); + +require Exporter; +@ISA = qw(Exporter); +@EXPORT_OK = qw(get); + +# Module version +$VERSION = "0.1"; + +sub get { + my ($key, %param) = @_; + + my $string = $UVconfig::messages{$key} || return ''; + + while ($string =~ m/\$\{([A-Za-z0-9_-]+)\}/) { + my $skey = $1; + my $sval = $param{$skey}; + $sval = '' unless defined($sval); + + $string =~ s/\$\{$skey\}/$sval/g; + } + + return $string; +} + +1; diff --git a/UVreadmail.pm b/UVreadmail.pm new file mode 100644 index 0000000..e1e599c --- /dev/null +++ b/UVreadmail.pm @@ -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 = ) { + 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 () { + 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*]+)>?[^\(\)]*/) { + ($voter_addr, $voter_name) = ($1, ''); + } else { + # initialize with empty value + $voter_addr = ''; + $voter_name = ''; + } + + # look at reply-to? + if ($config{replyto}) { + + my $replyto = Mail::Field->new('Reply-To', $head->get('Reply-To')); + + # Address in Reply-To? + ($voter_addr) = $replyto->addresses() if ($replyto->addresses()); + + # Name in reply-to? + if ($replyto->names()) { + my ($nametmp) = $replyto->names(); + $voter_name = $nametmp unless ($nametmp =~ /^\s*$/); + } + + } + + # decode body + my $encoding = $head->get('Content-Transfer-Encoding') || ''; + if ($encoding =~ /quoted-printable/i) { + $body = decode_qp($entity->stringify_body); + } elsif ($encoding =~ /base64/i) { + $body = decode_base64($entity->stringify_body); + } else { + $body = $entity->stringify_body; + } + + my $h_date = $head->get('Date') || ''; + chomp $h_date; + + # call referred sub and increase counter + &$callsub($voter_addr, $voter_name, $h_date, $entity, \$body); + $count++; + } + + return $count; +} + +1; diff --git a/UVrules.pm b/UVrules.pm new file mode 100644 index 0000000..76e260f --- /dev/null +++ b/UVrules.pm @@ -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 () { + chomp; + s/#.*$//; # delete comments + + # does line match correct if-then syntax? + if (/^\s*if\s+(\S+)\s+then\s+(\S+)\s*$/) { + my $if = $1; + my $then = $2; + + # $num contains the rule's array index + my $num = @rules; + + # check for correct length of condition + my $errortext; + if (length($if) < @groups) { + $errortext = UVmessage::get("RULES_TOOSHORT", (NUM=>$num+1, TYPE=>"if")); + + } elsif (length($if) > @groups) { + $errortext = UVmessage::get("RULES_TOOLONG", (NUM=>$num+1, TYPE=>"if")); + + } elsif (length($then) < @groups) { + $errortext = UVmessage::get("RULES_TOOSHORT", (NUM=>$num+1, TYPE=>"then")); + + } elsif (length($then) > @groups) { + $errortext = UVmessage::get("RULES_TOOLONG", (NUM=>$num+1, TYPE=>"then")); + } + die $errortext . ": $_\n\n" if ($errortext); + + # check for correct characters in conditions + if ($if !~ /^[JjNnEeSsHhIi\.]+$/) { + die UVmessage::get ("RULES_INVCHARS", (NUM=>$num+1, TYPE=>"if")) . ": $if\n\n"; + + } elsif ($then !~ /^[JjNnEeSsHhIi\.]+$/) { + die UVmessage::get ("RULES_INVCHARS", + (NUM=>$num+1, TYPE=>"if")) . ": $then\n\n"; + } + + # Zur Speicherung der Regeln (to be translated): + # - if_compl und then_compl sind die kompletten Bedingungen als Strings, + # werden fuer die Sprachausgabe der Regeln benoetigt + # - zusaetzlich werden der if- und then-Teil fuer die einfachere + # Verarbeitung in zwei Teile gesplittet: Eine Positiv-Regex, die auf + # die Grossbuchstaben (explizite Forderungen, UND-Verknuepfungen) + # matched, und eine Negativ-Regex, die bei den Kleinbuchstaben + # (optionale Felder, ODER-Verknuepfungen) verwendet wird. + + my %rule = ( if_compl => $if, + if_pos => make_regex_pos($if), + if_neg => make_regex_neg($if), + then_compl => $then, + then_pos => make_regex_pos($then), + then_neg => make_regex_neg($then) ); + + push (@rules, \%rule); + + } + } +} + + +############################################################################## +# Generates a RegEx for positive matching of the rules # +# # +# All lower case characters are replaced with dots, as they are to be # +# matched by the negativ RegEx. Furthermore the symbol S is replaced by [JN] # +# and I is replaced by [EN] (for use in combined votings when only one # +# option may be accepted and the others must be rejected or abstained. # +# As a result we have a regular expression that can be matched against the # +# received votes. # +############################################################################## + +sub make_regex_pos { + my $pat = $_[0]; + + $pat =~ s/[hijens]/./g; + $pat =~ s/S/[JN]/g; + $pat =~ s/H/[EJ]/g; + $pat =~ s/I/[EN]/g; + + return $pat; +} + + +############################################################################## +# Generates a RegEx for negative matching of the rules # +# # +# All upper case characters are replaced with dots, as they are to be # +# matched by the positiv RegEx. If lower case characters are found the # +# condition is reversed, so that we are able to match votes *not* # +# corresponding to this rule # +############################################################################## + +sub make_regex_neg { + my $pat = $_[0]; + + # upper case characters are replaced with dots + # (are covered by make_regex_pos) + $pat =~ s/[HIJENS]/./g; + + # reverse lower case characters + $pat =~ s/j/[NE]/g; + $pat =~ s/n/[JE]/g; + $pat =~ s/e/[JN]/g; + $pat =~ s/s/E/g; + $pat =~ s/h/N/g; + $pat =~ s/i/J/g; + + # If the string contained only upper case characters they are now all + # replaced with dots and the RegEx would match everything, i.e. declare + # every vote as invalid. In this case an empty pattern is returned. + $pat =~ s/^\.+$//; + + return $pat; +} + + +############################################################################## +# Check a voting for rule compliance # +# Parameters: Votes (Reference to Array) # +# Return value: Number of violated rule or 0 (everything OK) # +# (Internally rules are saved with indexes starting at 0) # +############################################################################## + +sub rule_check { + my ($voteref) = @_; + + # Turn array reference into a string + my $vote = join ('', @$voteref); + + # For compliance with the rules every rule has to be matched against the + # the vote. If the IF clause matches but not the THEN clause the vote is + # invalid and the rule number is returned. + + for (my $n = 0; $n < @rules; $n++) { + return $n+1 if ($vote =~ m/^$rules[$n]->{if_pos}$/ && + $vote !~ m/^$rules[$n]->{if_neg}$/ && + not($vote =~ m/^$rules[$n]->{then_pos}$/ && + $vote !~ m/^$rules[$n]->{then_neg}$/ )); + } + + return 0; +} + + +############################################################################## +# Print rules in human readable format # +# Parameter: rule number # +# Return value: rule text # +############################################################################## + +sub rule_print { + my ($n) = @_; + + my $and = UVmessage::get ("RULES_AND"); + my $or = UVmessage::get ("RULES_OR"); + my $yes = UVmessage::get ("RULES_YES"); + my $no = UVmessage::get ("RULES_NO"); + my $abst = UVmessage::get ("RULES_ABSTAIN"); + + $n++; + my $text = UVmessage::get ("RULES_RULE") . " #$n:\n"; + $text .= " " . UVmessage::get ("RULES_IF") . "\n"; + + my @rule = split (//, $rules[$n-1]->{if_compl}); + my $firstrun = 1; + my $fill = ""; + + for (my $i=0; $i<@rule; $i++) { + my $text1 = ""; + + if ($rule[$i] eq 'J') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'N') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$no, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'E') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'S') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", + (VOTE=>"$yes $or $no", GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'H') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", + (VOTE=>"$abst $or $yes", GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'I') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", + (VOTE=>"$abst $or $no", GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'j') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'n') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$no, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'e') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 's') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", + (VOTE=>"$yes $or $no", GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'h') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", + (VOTE=>"$abst $or $yes", GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'i') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_IFCLAUSE", + (VOTE=>"$abst $or $no", GROUP=>$groups[$i])); + } + + if ($text1) { + if ($firstrun) { + $text .= " " . $text1 . "\n"; + $firstrun = 0; + } else { + $text .= $fill . $text1 . "\n"; + } + } + } + + @rule = split (//, $rules[$n-1]->{then_compl}); + $text .= " ..." . UVmessage::get ("RULES_THEN") . "\n"; + $firstrun = 1; + + for (my $i=0; $i<@rule; $i++) { + my $text1 = ""; + if ($rule[$i] eq 'J') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'N') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$no, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'E') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'S') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", + (VOTE=>"$yes $or $no", GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'H') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", + (VOTE=>"$abst $or $yes", GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'I') { + $fill = " $and "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", + (VOTE=>"$abst $or $no", GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'j') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$yes, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'n') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$no, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'e') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", (VOTE=>$abst, GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 's') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", + (VOTE=>"$yes $or $no", GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'h') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", + (VOTE=>"$abst $or $yes", GROUP=>$groups[$i])); + } elsif ($rule[$i] eq 'i') { + $fill = " $or "; + $text1 = UVmessage::get ("RULES_THENCLAUSE", + (VOTE=>"$abst $or $no", GROUP=>$groups[$i])); + } + + if ($text1) { + if ($firstrun) { + $text .= " " . $text1 . "\n"; + $firstrun = 0; + } else { + $text .= $fill . $text1 . "\n"; + } + } + } + return $text . "\n"; +} + +1; diff --git a/UVsendmail.pm b/UVsendmail.pm new file mode 100644 index 0000000..a773733 --- /dev/null +++ b/UVsendmail.pm @@ -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 = ; + 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('', ); + close (MAIL); + + next unless $message; + + $smtp->reset(); + $smtp->mail($config{envelopefrom}); + unless ($smtp->ok()) { + print STDERR UVmessage::get("SENDMAIL_SMTP_INVRCPT", (RCPT => $envelope)), + "\n", $smtp->code(), ' ', $smtp->message(), "\n"; + $errors++; + next; + } + + my $onesent = 0; + my $onefail = 0; + foreach my $addr (split(/ +/, $envelope)) { + $smtp->to($addr); + if ($smtp->ok()) { + $onesent = 1; + } else { + print CONTROL ($onefail ? " " : "$file\t"); + print CONTROL $addr; + print STDERR UVmessage::get("SENDMAIL_SMTP_INVRCPT", (RCPT => $envelope)), + "\n", $smtp->code(), ' ', $smtp->message(), "\n"; + $errors++; + $onefail = 1; + next; + } + } + + print CONTROL "\n" if ($onefail); + next unless $onesent; + + $smtp->data(); + if ($smtp->ok()) { + $smtp->datasend($message); + $smtp->dataend(); + } + unless ($smtp->ok()) { + print STDERR UVmessage::get("SENDMAIL_SMTP_INVRCPT", (RCPT => $envelope)), + "\n", $smtp->code(), ' ', $smtp->message(), "\n"; + $errors++; + next; + } + unlink ($file) unless ($onefail); + } + + $smtp->quit(); + close (CONTROL) or die "\n\n", + UVmessage::get("SENDMAIL_ERRCLOSECONTROL", (FILE => $config{controlfile})), "\n"; + + if ($errors) { + print STDERR "\n".wrap('', '', "$errors ".UVmessage::get("SENDMAIL_ERROCCURED"))."\n\n"; + } + + if ($missingfiles) { + print STDERR wrap('', '', "$missingfiles " . + UVmessage::get("SENDMAIL_MISSINGFILES")), "\n\n"; + } + + } else { + + foreach my $mail (@mailinfo) { + next unless $mail; + chomp($mail); + my ($file, @rcpt) = split(/\s+/, $mail); + open (DOMAIL, ">>$config{domailfile}"); + print DOMAIL "$config{mailcmd} "; + foreach my $rcpt (@rcpt) { + print DOMAIL "'$rcpt' "; + } + print DOMAIL "<$file && rm $file ; $config{sleepcmd}\n"; + close (DOMAIL) + or print STDERR "\n\n", UVmessage::get("SENDMAIL_ERRCLOSEDOMAIL"), "\n"; + } + chmod(0700, $config{domailfile}); + system($config{domailfile}); + + } + + opendir (DIR, $config{tmpdir}); + my @files = grep (/^ack\.\d+/, readdir (DIR)); + closedir (DIR); + return 0 if (@files); + + unlink $config{controlfile} or print STDERR "\n\n", + UVmessage::get("SENDMAIL_ERRDELCONTROL", (FILE => $config{controlfile})), "\n"; + + unless ($config{smtp}) { + unlink $config{domailfile} or print STDERR "\n\n", + UVmessage::get("SENDMAIL_ERRDELCONTROL", (FILE => $config{domailfile})), "\n"; + } + +} + + +############################################################################## +# Encodes a string for use in mail headers # +# # +# Parameters: $text = string to encode. # +# Returns: $newtext = encoded string. # +############################################################################## + +sub mimeencode { + my ($text) = @_; + my @words = split(/ /, $text); + my $line = ''; + my @lines; + + foreach my $word (@words) { + my $sameword = 0; + $word =~ s/\n//g; + my $encword; + if ($word =~ /[\x7F-\xFF]/) { + $encword = MIME::Words::encode_mimeword($word, 'Q', 'ISO-8859-1'); + } elsif (length($word) > 75) { + $encword = MIME::Words::encode_mimeword($word, 'Q', 'us-ascii'); + } else { + $encword = $word; + } + + # no more than 75 chars per line allowed + if (length($encword) > 75) { + while ($encword) { + if ($encword =~ /(^=\?[-\w]+\?\w\?)(.{55}.*?)((=.{2}|[^=]{3}).*\?=)$/) { + addword($1 . $2 . '?=', \$line, \@lines, $sameword); + $encword = $1 . $3; + } else { + addword($encword, \$line, \@lines, $sameword); + $encword = ''; + } + $sameword = 1; + } + } else { + addword($encword, \$line, \@lines, $sameword); + } + } + + my $delim = (@lines) ? ' ' : ''; + push(@lines, $delim . $line) if ($line); + return join('', @lines); +} + + +############################################################################## +# Adds a word to a MIME encoded string, inserts linefeed if necessary # +# # +# Parameters: # +# $word = word to add # +# $line = current line # +# $lines = complete text (without current line) # +# $sameword = boolean switch, indicates that this is another part of # +# the last word (for encoded words > 75 chars) # +############################################################################## + +sub addword { + my ($word, $line, $lines, $sameword) = @_; + + # If the passed fragment is a new word (and not another part of the + # previous): Check if it is MIME encoded + if (!$sameword && $word =~ /^(=\?[^\?]+?\?[QqBb]\?)(.+\?=[^\?]*)$/) { + + # Word is encoded, save without the MIME header + # (e.g. "t=E4st?=" instead of "?iso-8859-1?q?t=E4st?=") + my $charset = $1; + my $newword = $2; + + if ($$line =~ /^(=\?[^\?]+\?[QqBb]\?)(.+)\?=$/) { + # Previous word was encoded, too: + # Delete the trailing "?=" and insert an underline character (=space) + # (space between to encoded words is ignored) + if ($1 eq $charset) { + if (length($1.$2)+length($newword)>75) { + my $delim = (@$lines) ? ' ' : ''; + push(@$lines, "$delim$1$2_?=\n"); + $$line = $word; + } else { + $$line = $1 . $2 . '_' . $newword; + } + } else { + if (length("$$line $word")>75) { + my $delim = (@$lines) ? ' ' : ''; + push(@$lines, "$delim$1$2_?=\n"); + $$line = $word; + } else { + $$line = "$1$2_?= $word"; + } + } + return 0; + } + } + + # New word is not encoded: simply append it, but check for line length + # and add a newline if necessary + if (length($$line) > 0) { + if (length($$line) + length($word) >= 75) { + my $delim = (@$lines) ? ' ' : ''; + push(@$lines, "$delim$$line\n"); + $$line = $word; + } else { + $$line .= " $word"; + } + } else { + # line is empty + $$line = $word; + } +} + +1; diff --git a/UVtemplate.pm b/UVtemplate.pm new file mode 100644 index 0000000..35f28db --- /dev/null +++ b/UVtemplate.pm @@ -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 = ; + 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[|]] + push (@token, 'VAR'); + + }elsif ($string =~ s/^\?//s){ + # Bedingung - Syntax: [?if|[|]] + push (@token, 'IF'); + + }elsif ($string =~ s/^\@//s){ + # Schleifen - Syntax: [@key|[|]] + 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 + +=head1 AUTHOR + +Cornell Binder diff --git a/bdsgtext.cfg b/bdsgtext.cfg new file mode 100644 index 0000000..28bc3b5 --- /dev/null +++ b/bdsgtext.cfg @@ -0,0 +1,13 @@ +# Diese Datei enthaelt den Hinweistext auf die Datenschutzklausel, +# der im Wahlschein erscheint und auch bei eingegangenen Wahlscheinen +# wieder abgeprueft wird. Alle Zeilen, die mit dem Kommentarzeichen # +# anfangen, werden ignoriert. +# +Zur Verarbeitung des Wahlscheines und inbesondere der Veroeffentlichung +des Ergebnisses ist deine Zustimmung zur Speicherung, Auswertung und +Veroeffentlichung deiner Stimmdaten (Name und E-Mail-Adresse in +Verbindung mit dem Stimmverhalten) im Rahmen dieses Verfahrens +erforderlich. Wenn du im Feld unterhalb dieses Absatzes "JA" +eintraegst, erklaerst du dich damit einverstanden. In allen anderen +Faellen wird der Wahlschein mit Ruecksicht auf das deutsche +Bundesdatenschutzgesetz verworfen und nicht gewertet. diff --git a/mailpatterns.cfg b/mailpatterns.cfg new file mode 100644 index 0000000..0fbbd31 --- /dev/null +++ b/mailpatterns.cfg @@ -0,0 +1,42 @@ +# Mailadressen, die auf diese Kriterien zutreffen, werden als verdaechtig +# angesehen und zurueckgewiesen. Im interaktiven Modus kann der Wahlleiter +# im Einzelfall entscheiden. +# +# Bitte ein Pattern (Regular Expression) pro Zeile eingeben. +# Achtung, es wird so benutzt, wie es hier steht, also auf fuehrende oder +# abschliessende Leerzeichen aufpassen! +# +# Alle Zeilen, die mit dem Kommentarzeichen # anfangen, werden ignoriert. + +@anon\.penet\.fi +@anon\.sub\.net +anonymous@mixmaster\.nullify\.org +me@privacy.net +admin@ +daemon@ +anon@ +audit@ +bin@ +decnet@ +default@ +field@ +guest@ +hostmaster@ +install@ +mailer.*@ +maint.*@ +news@ +newsmaster@ +nobody@ +operator@ +postmaster@ +root@ +shutdown@ +sync@ +sys@ +sysop@ +system@ +test@ +tutor@ +usenet@ +uucp@ diff --git a/messages.cfg b/messages.cfg new file mode 100644 index 0000000..6b1dd5d --- /dev/null +++ b/messages.cfg @@ -0,0 +1,194 @@ +# Messages used in Usevote. Variables are enclosed in ${... }. +# Mostly these is interactive output for the votetaker, but texts from +# uvbounce.pl and uvrules.pl are also partly mailed back to the voters. +# +# Comments are allowed, but the # sign must be at the beginning of the line +# (no leading spaces or others characters allowed). +# +# General format: +# Identifier = message +# (Identifier has to start at the beginning of the line, without leading space) +# +################################################################################### +# +# UVconfig.pm +# +CONF_NOGROUPS = Kein Abstimmungsgegenstand definiert (siehe ${CONFIGFILE})! +CONF_NOBDSGFILE = Datei mit Datenschutzhinweis ("${BDSGFILE}") nicht lesbar! +CONF_NOSIG = Abschnitt [Signatur] nicht in Datei ${FILE} gefunden! +CONF_NOBADADDR = Datei mit verdaechtigen Adressen "${BADADDRFILE}" nicht lesbar! +CONF_TEST_RULES = Regeln aus usevote.rul: +CONF_NO_RULES = (keine Regeln definiert) +CONF_CONFIG = Konfiguration: +# +# UVmenu.pm +# +MENU_INVALIDNAME = Ungueltiger Name. +MENU_NONAME = Kein Name angegeben. +MENU_INVALIDBDSG = Datenschutzhinweis fehlerhaft oder nicht bestaetigt. +MENU_DUPLICATEVOTE = Doppelte Stimmabgabe gefunden. +MENU_NOVOTE = Keine Stimmabgabe gefunden. +MENU_INVALIDVOTE = Ungueltige Stimmabgabe. +MENU_NOBALLOTID = Keine Scheinkennung gefunden. +MENU_WRONGBALLOTID = Scheinkennung falsch. +MENU_ADDRESSNOTREGISTERED = Adresse nicht registriert. +MENU_INVALIDADDRESS = Ungueltige Mail-Adresse. +MENU_INVALIDADDRESS2 = Es wird keine Mail verschickt! +MENU_SUSPICIOUSACCOUNT = Verdaechtige Adresse gefunden. +MENU_UNRECOGNIZEDVOTE = Stimmen nicht vollstaendig erkannt. Im Zweifelsfall "Enthaltung" angenommen. +MENU_UNRECOGNIZED_LIST = Stimme +MENU_VIOLATEDRULE = Regel ${RULE} verletzt. +MENU_PROBLEMS = Die folgenden Probleme muessen beseitigt werden: +MENU_CAPTION = Auswahlmenue: +MENU_SHOW_MAIL = Anzeigen der Wahlmail +MENU_CHANGE_PROPERTIES = Bestaetigen oder Aendern von Wahlschein-Eigenschaften: +MENU_ADDRESS = Mailadresse +MENU_ADDRESS_CHANGE = Adresse aendern +MENU_ADDRESS_OK = Adresse OK +MENU_ADDRESS_INVALID = Adresse ungueltig +MENU_ADDRESS_PROMPT = Waehleradresse: +MENU_NAME = Waehlername +MENU_NAME_CHANGE = Namen aendern +MENU_NAME_OK = Name OK +MENU_NAME_INVALID = Name ungueltig +MENU_VOTES = Stimmen +MENU_VOTES_RESET = Stimmen neu setzen +MENU_VOTES_OK = Stimmen OK +MENU_VOTES_INVALID = Stimmen ungueltig +MENU_VOTES_CANCELLED = Stimmen vom Waehler annulliert +MENU_VOTES_REENTER = Stimme fuer ${GROUP} (J, N oder E): +MENU_VOTES_REENTER_ASK = Bitte die Stimmen neu eingeben. Die aus dem Wahlschein erkannten Stimmen sind jeweils der Standardwert und werden in [ ] aufgefuehrt. +MENU_VOTES_REENTER_LEGEND = J: Ja | N: Nein | E: Enthaltung +MENU_VOTES_REENTER_OLD = Bisherige Stimmen: +MENU_VOTES_REENTER_NEW = Neue Stimmen: +MENU_VOTES_REENTER_ACK = Stimmen uebernehmen +MENU_VOTES_REENTER_NACK = bisherige Stimmen belassen +MENU_BALLOT_ID = Scheinkennung +MENU_BDSG = Datenschutzklausel +MENU_BDSG_ACCEPTED = Datenschutzklausel wurde akzeptiert +MENU_BDSG_DECLINED = Datenschutzklausel nicht akzeptiert bzw. Text veraendert +MENU_IGNORE = Diese Stimme ignorieren (ohne Benachrichtigung verwerfen) +MENU_IGNORE_WARNING = Die Stimme wird nicht aufgezeichnet, und es wird keine Bestaetigung verschickt. ${MENU_IGNORE_STRING} eingeben, wenn Du sicher bist: +MENU_IGNORE_STRING = JA +MENU_PROCEED = Weiter +MENU_PROMPT = Eingabe: +MENU_GETKEY = *** Return druecken, um fortzufahren *** +MENU_PROCESSING = Verarbeite Mails... +MENU_ERROR_WARNING = WARNUNG +MENU_ERROR_TEXT = Es wurden nicht alle Fehler behoben. Der Waehler wird eine Fehlermail erhalten und die Stimme wird ungueltig gewertet. +MENU_ERROR_GETKEY = Bitte mit 'y' bestätigen oder mit jeder anderen Eingabe zurück: +MENU_DUP_VOTE = Moeglicherweise doppelte Stimmabgabe! +MENU_DUP_FIRST = Erste Stimme: +MENU_DUP_SECOND = Zweite Stimme: +MENU_DUP_DELFIRST = Erste loeschen +MENU_DUP_DELSECOND = Zweite loeschen +MENU_DUP_DELNONE = Keine loeschen +# +# UVreadmail.pm +# +READMAIL_STATUS = Abruf neuer Mails vom POP3-Server... +READMAIL_NOCONNECTION = Verbindung zum POP3-Server fehlgeschlagen! +READMAIL_NOLOGIN = Anmeldung am POP3-Server nicht moeglich! +READMAIL_NOMAILFILE = Maildatei ${FILE} nicht lesbar! +READMAIL_GET_PROBLEM = Warnung! Konnte Mail Nr. ${NR} nicht abrufen. +READMAIL_DEL_PROBLEM = Warnung! Konnte Mail Nr. ${NR} nicht loeschen. +READMAIL_UIDL_PROBLEM = Warnung! Konnte Liste mit UIDLs nicht speichern. +READMAIL_UIDL_PROBLEM2 = Beim naechsten Lauf keine Erkennung bereits abgerufener Mails moeglich! +READMAIL_UIDL_CLOSE = Warnung! Konnte UIDL-Cachedatei nicht ordnungsgemaess schliessen. +READMAIL_ARCHIVE_PROBLEM = Warnung! Wahlmails konnten nicht in Datei ${FILE} gesichert werden. +READMAIL_ARCHIVE_CLOSE = Warnung! Konnte Wahlmail-Archivdatei ${FILE} nicht schliessen. +# +# UVrules.pm +# +RULES_ERROPENFILE = Kann Regeldatei ${FILE} nicht oeffnen +RULES_TOOSHORT = Regel ${NUM}: '${TYPE}'-Bedingung zu kurz +RULES_TOOLONG = Regel ${NUM}: '${TYPE}'-Bedingung zu lang +RULES_INVCHARS = Regel ${NUM}: '${TYPE}'-Bedingung enthaelt nicht erlaubte Zeichen +RULES_RULE = Wahlregel +RULES_IF = Wenn Du +RULES_THEN = musst Du +RULES_AND = und +RULES_OR = oder +RULES_YES = JA +RULES_NO = NEIN +RULES_ABSTAIN = ENTHALTUNG +RULES_IFCLAUSE = ${VOTE} stimmst fuer ${GROUP} +RULES_THENCLAUSE = ${VOTE} stimmen fuer ${GROUP} +# +# UVsendmail.pm +# +SENDMAIL_ERROPENCONTROL = FEHLER! Steuerungsdatei ${FILE} konnte nicht geoeffnet werden. +SENDMAIL_ERRCLOSECONTROL = FEHLER! Steuerungsdatei ${FILE} konnte nicht geschlossen werden. +SENDMAIL_ERRDELCONTROL = FEHLER! Steuerungsdatei ${FILE} konnte nicht geloescht werden. +SENDMAIL_ERROPENMAIL = FEHLER! Mail-Datei ${FILE} konnte nicht geoeffnet werden. +SENDMAIL_ERRCLOSEMAIL = FEHLER! Mail-Datei ${FILE} konnte nicht geschlossen werden. +SENDMAIL_NOMAILS = Keine Mails zu verschicken (Datei ${FILE} nicht gefunden). +SENDMAIL_SMTP_CONNREFUSED = Keine Verbindung zum SMTP-Server moeglich! +SENDMAIL_SMTP_AUTHERR = Anmeldung am SMTP-Server fehlgeschlagen! +SENDMAIL_ERRNOTFOUND = Datei ${FILE} nicht gefunden. +SENDMAIL_SMTP_INVRCPT = Mail an ${RCPT} konnte nicht verschickt werden: +SENDMAIL_SENDING = Mails werden verschickt... +SENDMAIL_ERROCCURED = Fehler aufgetreten. Bitte kontrollieren und "uvvote.pl clean" ggf. noch einmal starten! +SENDMAIL_MISSINGFILES = Dateien konnten nicht gefunden werden. Dieser Fehler kann auftreten, falls bei einem vorherigen Lauf nur einzelne Mails verschickt werden konnten (diese Dateien wurden dann bereits geloescht). +SENDMAIL_ERRCLOSEDOMAIL = FEHLER! domail-Datei konnte nicht geschlossen werden. +SENDMAIL_ACKTXT_MISSING = KONFIGURATIONSFEHLER: Abschnitt [${SECTION}] in ${FILE} existiert nicht! Mails an die Waehler sind moeglicherweise unvollstaendig. +# +# uvballot.pl +# +BALLOT_NO_PERSONAL = Die Option -t kann nur in Zusammenhang mit persoenlichen Wahlscheinen verwendet werden (Option "personal" in ${CFGFILE}). +# +# uvbounce.pl +# +BOUNCE_BALLOT = ! Wahlschein nicht zustellbar (Adresse ungueltig) +BOUNCE_ACKMAIL = ! Bestaetigung nicht zustellbar (Adresse ungueltig) +# +# uvcfv.pl +# +CFV_NUMBER = Es werden ${COUNT} CfVs verschickt. +CFV_ERRWRITE = Kann nicht in Scheinkennungsdatei ${FILE} schreiben! +CFV_ERRCLOSE = Kann Scheinkennungsdatei nicht schliessen! +CFV_ERROPENCFV = Kann CfV-Datei ${FILE} nicht lesen! +CFV_SUBJECT = Wahlschein +# +# uvcount.pl +# +COUNT_ERR_OPEN = Kann Ergebnisdatei ${FILE} nicht oeffnen! +COUNT_ERR_RESULT = Fehler in ${FILE} Zeile ${LINE} +COUNT_ERR_GROUPCOUNT = Bei Stimme von <${ADDR}>: ${NUM1} statt ${NUM2} Stimmen gefunden (${RESULTFILE} kontrollieren!) +COUNT_DELETED = ${NUM} Stimme(n) geloescht. +# +# uvvote.pl +# +VOTE_RENAMING_MAILBOX = Benenne Stimmdatei um... +VOTE_WRITE_RESULTS = Ergebnisdatei ${FILE} nicht schreibbar! +VOTE_CLOSE_RESULTS = Ergebnisdatei ${FILE} konnte nicht erfolgreich geschlossen werden! +VOTE_NO_VOTES = Keine Stimmen zu verarbeiten. +VOTE_NUM_VOTES = ${COUNT} Stimmen bearbeitet. +VOTE_NOT_SAVED = ${COUNT} Stimmen bearbeitet, aber nicht gespeichert. +VOTE_FIRSTRUN = 'uvvote clean' aufrufen, um Ergebnisse zu speichern und Bestaetigungen zu verschicken. +VOTE_ERRORS = Folgende Fehler sind aufgetreten (siehe auch ${FILE}): +VOTE_INVALID_BALLOTID = Scheinkennung ungueltig +VOTE_MISSING_BALLOTID = Scheinkennung fehlt +VOTE_UNREGISTERED_ADDRESS = Adresse nicht registriert +VOTE_INVALID_VOTE = Ungueltige Stimmabgabe +VOTE_VIOLATED_RULE = Regel ${RULE} verletzt +VOTE_NO_VOTES = Keine Stimmen abgegeben +VOTE_INVALID_ACCOUNT = Ungueltiger Account +VOTE_INVALID_ADDRESS = Ungueltige Adresse +VOTE_INVALID_REALNAME = Ungueltiger Realname +VOTE_MISSING_NAME = Kein Name angegeben +VOTE_DUPLICATES = Verschiedene Stimmen fuer gleichen Abstimmungspunkt +VOTE_FILE_COMMENT = Wahlleiter setzte ${FIELDS} +VOTE_NO_NEW_RESULTS = Keine noch nicht verarbeiteten Ergebnisdateien gefunden! Bitte uvvote.pl zunaechst ohne die 'clean'-Option starten. +VOTE_MOVE_RESULTFILE = Fehler! Konnte Ergebnisdatei ${FILE} nicht verschieben: +VOTE_MOVE_VOTEFILE = Fehler! Konnte Stimmdatei ${FILE} nicht verschieben: +VOTE_CREATING_RESULTS = Erstelle neue Gesamtergebnisdatei ${FILENAME}... +# +# Allgemeine Meldungen +# +ERR_MKDIR = Kann Verzeichnis '${DIR}' nicht anlegen: +ERR_NOTPERSONAL = Usevote ist nicht fuer die Verwendung personalisierter Wahlscheine konfiguriert (Option "personal" in ${CFGFILE}) +ERR_FILE_CREATION = Fataler Fehler: Konnte keine Datei anlegen. +ERR_RENAME_MAILFILE = Konnte Maildatei nicht umbenennen: +ERR_LOCK = Lockfile ${FILE} bereits vorhanden! Wurde UseVoteGer mehrfach gestartet? +INFO_TIDY_UP = Aufraeumen... diff --git a/templates/ack-mail b/templates/ack-mail new file mode 100644 index 0000000..150df0b --- /dev/null +++ b/templates/ack-mail @@ -0,0 +1,46 @@ +ballotid-line := value ballotidtext | fill-right 20 | append ballotid +address-line := value addresstext | fill-right 20 | append address +name-line := value nametext2 | fill-right 20 | append name +pos := value pos | fill-right 2 +group-first := value group | first-words 50 +group-more := value group | drop-words 50 | create-lines 50 +vote := value vote | fill-both 10 +votetaker := value mailfrom | fill-left 65 + +== TEMPLATE ================================================================= +Diese automatische Nachricht wurde dir nach Zaehlung deiner Stimme +zugesandt. Wenn alles stimmt, gibt es keinen Anlass fuer eine Reaktion. +Wenn deine Stimme falsch registriert wurde, stimme bitte erneut ab, +indem du diese Mail komplett zitierst und die falschen Wertungen +korrigierst (zwischen die eckigen Klammern schreiben). Dabei darf +keinesfalls die laufende Nummer am Zeilenanfang entfernt werden. + +Diese Wahl ist oeffentlich, und die Adressen aller Waehlerinnen und +Waehler werden am Ende bekanntgegeben. Wenn du deine Adresse & Stimme +loeschen willst, kannst du erneut abstimmen und dabei 'ANNULLIERUNG' +anstelle von 'JA' oder 'NEIN' angeben. \[Doppel-N, Doppel-L :-)\] + +[?personal|Da diese Abstimmung mit personalisierten Wahlscheinen durchgefuehrt +wird, sind auch saemtliche Aenderungen nur mit Angabe der folgenden +Wahlscheinkennung gueltig! + +[$ballotid-line]] +[$address-line] +[$name-line] + (Real-Namen sind fuer diese Abstimmung vorgeschrieben. Wenn hier + nicht Dein wirklicher Name steht, dann korrigiere die Zeile + bitte und sende die Nachricht zurueck; sonst kann die Stimme + spaeter als ungueltig gewertet werden.) + + +Deine Stimmabgabe wurde wie folgt erkannt: + +[@groups|#[$pos] \[[$vote]\] fuer [$group-first] +[@group-more| [$line]\n]|\n] + + +Danke fuer deine Stimmabgabe. Eine Kopie des CfV kannst du von mir er- +halten (bitte Namen der Abstimmung angeben, falls mehrere laufen). + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/address-not-registered b/templates/address-not-registered new file mode 100644 index 0000000..f12ae79 --- /dev/null +++ b/templates/address-not-registered @@ -0,0 +1,25 @@ +votetaker := value mailfrom | fill-left 65 +head := value head | quote "> " +body := value body | quote "> " + +== TEMPLATE ================================================================= +Deine Adresse ist beim Wahlleiter bisher nicht mit einer Wahlschein- +kennung registriert, mit einer EMail an die im CfV genannten Adresse +kannst du dir eine Kennung zuteilen lassen. + +Solltest du bereits eine Kennung erhalten haben, so achte bitte darauf, +diese nur mit genau der Mailadresse zu verwenden, fuer die sie +registriert ist. + +Achtung, die Mailadresse wird unter Beachtung von Gross- und Klein- +schreibung ausgewertet, Meier und meier sind daher _nicht_ identisch! + +Hier die Mail, die ich erhalten habe: + +[$head] +> +[$body] + + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/ballot b/templates/ballot new file mode 100644 index 0000000..5e3242a --- /dev/null +++ b/templates/ballot @@ -0,0 +1,34 @@ +votename-first := value votename | first-words 55 +votename-more := value votename | drop-words 55 | create-lines 55 + +pos := value pos | fill-right 2 + +group-first := value group | first-words 50 +group-more := value group | drop-words 50 | create-lines 50 + +bdsginfo := value bdsginfo | create-lines 72 +bdsgtext-first := value bdsgtext | first-words 50 +bdsgtext-more := value bdsgtext | drop-words 50 | create-lines 50 + +== TEMPLATE ================================================================= + +=-=-=-=-=-=-=-=- Alles vor dieser Zeile bitte loeschen =-=-=-=-=-=-=-=- + +WAHLSCHEIN fuer [$votename-first] +[@votename-more| [$line]\n] + + +[$nametext] + +Wenn du keinen Real-Namen angibst, wird deine Stimme fuer +ungueltig erklaert werden. + + +Nr \[Deine Stimme\] Gruppe/Abstimmungsgegenstand +======================================================================== +[@groups|#[$pos] \[ \] [$group-first] +[@group-more| [$line]\n]|\n] +[?bdsg|[@bdsginfo|[$line]|\n]\n\n#a \[ \] [$bdsgtext-first] +[@bdsgtext-more| [$line]|\n]] + +=-=-=-=-=-=-=-=- Alles nach dieser Zeile bitte loeschen =-=-=-=-=-=-=-=- diff --git a/templates/ballot-personal b/templates/ballot-personal new file mode 100644 index 0000000..13aef2a --- /dev/null +++ b/templates/ballot-personal @@ -0,0 +1,63 @@ +votename-first := value votename | first-words 55 +votename-more := value votename | drop-words 55 | create-lines 55 + +pos := value pos | fill-right 2 + +group-first := value group | first-words 50 +group-more := value group | drop-words 50 | create-lines 50 + +bdsginfo := value bdsginfo | create-lines 72 +bdsgtext-first := value bdsgtext | first-words 50 +bdsgtext-more := value bdsgtext | drop-words 50 | create-lines 50 + +votetaker := value mailfrom | fill-left 65 + +== TEMPLATE ================================================================= + +[?alreadysent| +!! Dieser CfV wurde dir bereits einmal zugeschickt, sollte der erste +!! Wahlschein nicht angekommen oder die Anforderung nicht auf deinen +!! Wunsch geschehen sein, so wende dich bitte an den Wahlleiter. +] + + + +************************************ +*** hier den CfV-Text einfuegen! *** +************************************ + + +=-=-=-=-=-=-=-=- Alles vor dieser Zeile bitte loeschen =-=-=-=-=-=-=-=- + +WAHLSCHEIN fuer [$votename-first] +[@votename-more| [$line]\n] + + +[$ballotidtext] [$ballotid] +[$addresstext] [$address] + +Dies ist deine persoenliche und _geheime_ Wahlschein-Kennung! +Halte deine Wahlscheinkennung zwecks Missbrauchs- und Manipulations- +vermeidung bitte moeglichst geheim, da jeder, der sie kennt, in +deinem Namen abstimmen kann! + +[$nametext] + +Wenn du keinen Real-Namen angibst, wird deine Stimme fuer +ungueltig erklaert werden. + + +Nr \[Deine Stimme\] Gruppe/Abstimmungsgegenstand +======================================================================== +[@groups|#[$pos] \[ \] [$group-first] +[@group-more| [$line]\n]|\n] +[?bdsg|[@bdsginfo|[$line]|\n]\n\n#a \[ \] [$bdsgtext-first] +[@bdsgtext-more| [$line]|\n]] + +=-=-=-=-=-=-=-=- Alles nach dieser Zeile bitte loeschen =-=-=-=-=-=-=-=- + + +Danke fuer Deine Stimmabgabe. + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/ballot-request b/templates/ballot-request new file mode 100644 index 0000000..b1b0c69 --- /dev/null +++ b/templates/ballot-request @@ -0,0 +1,10 @@ +=-=-=-=-=-=-=-=- Alles vor dieser Zeile bitte loeschen =-=-=-=-=-=-=-=- + +Zur Wahlscheinanforderung einfach formlos *per Mail* auf dieses +Posting antworten. Falls nach einigen Tagen noch kein Wahlschein +eingetroffen ist, kannst du unter folgender Adresse mit dem +Wahlleiter Kontakt aufnehmen: + +[$mailaddress] + +=-=-=-=-=-=-=-=- Alles nach dieser Zeile bitte loeschen =-=-=-=-=-=-=-=- diff --git a/templates/bdsg-error b/templates/bdsg-error new file mode 100644 index 0000000..bd44741 --- /dev/null +++ b/templates/bdsg-error @@ -0,0 +1,29 @@ +votetaker := value mailfrom | fill-left 65 + +== TEMPLATE ================================================================= +Zur Wertung deiner Stimme ist die Zustimmung zur Speicherung, +Verarbeitung und Veroeffentlichung der Stimmabgabe erforderlich. +Diese Zustimmung konnte nicht automatisch erkannt werden. +deine Stimme wurde daher geloescht und wird nicht gewertet. + +Dieses kann folgende Gruende haben: + +* In deinem Wahlschein fehlt der Abschnitt, der ueber die Art + der Verarbeitung deiner Stimmdaten informiert, oder er wurde + als nicht vollstaendig erkannt. Um bezueglich des deutschen + Bundesdatenschutzgesetzes keine Risiken einzugehen, muss ich + darauf bestehen, dass dieser Absatz unveraendert mitgeschickt + und akzeptiert wird. + +* Du hast nicht der Speicherung/Verarbeitung/Veroeffentlichung + zugestimmt. Im dazugehoerigen Feld muss "JA" eingetragen + werden. + +Bitte stimme erneut ab, lasse dabei den Hinweistext im +Wahlschein intakt und akzeptiere die Klausel, indem du im +vorgesehen Feld "JA" eintraegst. Sollte das Problem aufgrund eines +Uebermittlungsfehlers bestehen bleiben, melde Dich bitte unter +der unten angegebenen Adresse bei mir. + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/bouncelist b/templates/bouncelist new file mode 100644 index 0000000..6f570bd --- /dev/null +++ b/templates/bouncelist @@ -0,0 +1,10 @@ +multi-line := value name | justify-before mail 72 +bouncetext := value bouncetext | create-lines 72 + +== TEMPLATE ================================================================= + +Als ungueltig erkannte Stimmen: +------------------------------------------------------------------------ +[@bounces|[$multi-line] +[@bouncetext|[$line]]|\n] + diff --git a/templates/cancelled b/templates/cancelled new file mode 100644 index 0000000..07cb9b2 --- /dev/null +++ b/templates/cancelled @@ -0,0 +1,12 @@ +votetaker := value mailfrom | fill-left 65 + +== TEMPLATE ================================================================= +Diese automatische Nachricht wurde dir nach Erhalt Deiner ANNULLIERUNG +zugesandt. Jede vorherige Stimmabgabe in dieser Abstimmung ist dadurch +annulliert und wird weder gezaehlt noch veroeffentlicht. + +Wenn dies ein Fehler war und du doch waehlen moechtest, musst du erneut +abstimmen. + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/invalid-account b/templates/invalid-account new file mode 100644 index 0000000..909dc01 --- /dev/null +++ b/templates/invalid-account @@ -0,0 +1,27 @@ +votetaker := value mailfrom | fill-left 65 +head := value head | quote "> " +body := value body | quote "> " + +== TEMPLATE ================================================================= +Deine Stimme kann nicht akzeptiert werden, da du eine kritische +Absenderadresse verwendest. Nichtpersoenliche Accounts wie root@, +operator@, postmaster@, guest@ usw. sind im Allgemeinen nicht zu +Abstimmungen zugelassen, insbesondere, wenn kein Realname angegeben +wird. Stimmen von Anon-Servern werden ebenfalls nicht akzeptiert. +Bitte stimme erneut von einem persoenlichen Account aus ab. + +Wenn das ein Fehler ist und dein wahrer Name/Account faelschlicherweise +fuer einen dieser unpersoenlichen oder Anon-Accounts gehalten wurde, so +schicke bitte eine eMail an meine persoenliche Adresse. Sorry fuer das +umstaendliche Verfahren, aber nur so ist die zuverlaessige automatische +Stimmenzaehlung moeglich. + +Hier die Mail, die ich erhielt: + +[$head] +> +[$body] + + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/invalid-name b/templates/invalid-name new file mode 100644 index 0000000..ce7a28d --- /dev/null +++ b/templates/invalid-name @@ -0,0 +1,19 @@ +votetaker := value mailfrom | fill-left 65 +head := value head | quote "> " +body := value body | quote "> " + +== TEMPLATE ================================================================= +Dieser Call for Votes (CfV) erfordert die Angabe eines Real-Namens ent- +weder im "From:"-Feld des Headers oder im dafuer vorgesehehen Feld des +Wahlscheins. Bitte wiederhole Deine Stimmabgabe mit Angabe Deines wirk- +lichen Namens. + +Hier die Mail, die ich erhalten habe: + +[$head] +> +[$body] + + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/mailheader b/templates/mailheader new file mode 100644 index 0000000..93746ba --- /dev/null +++ b/templates/mailheader @@ -0,0 +1,16 @@ +date := generate-date-header +msgid := generate-msgid + +== TEMPLATE ================================================================= +From: [$from] +Subject: [$subject] +To: [$address] +[?reply-to|Reply-To: [$reply-to]] +[?reference|In-Reply-To: [$reference]] +[?reference|References: [$reference]] +Message-ID: [$msgid] +Date: [$date] +X-Automated-Message: generated by [$usevote-version] +MIME-Version: 1.0 +Content-Type: text/plain; charset\=iso-8859-1 +Content-Transfer-Encoding: 8bit diff --git a/templates/multiple-votes b/templates/multiple-votes new file mode 100644 index 0000000..7da6433 --- /dev/null +++ b/templates/multiple-votes @@ -0,0 +1,19 @@ +votetaker := value mailfrom | fill-left 65 +body := value body | quote "> " + +== TEMPLATE ================================================================= +Dein Wahlschein enthielt widerspruechliche Stimmen (z.B. sowohl JA- als +auch NEIN-Stimme fuer dieselbe Gruppe). + +Das passiert meistens, wenn du mehrere Zeilen schickst, die als Stimme +interpretiert werden, z. B. wenn du vergisst, eine Beispielzeile im CfV +zu loeschen. Bitte lies den CfV noch einmal und wiederhole deine Stimm- +abgabe. + +Hier die Mail, die ich erhalten habe: + +[$body] + + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/no-ballot b/templates/no-ballot new file mode 100644 index 0000000..4a2b71e --- /dev/null +++ b/templates/no-ballot @@ -0,0 +1,16 @@ +votetaker := value mailfrom | fill-left 65 +body := value body | quote "> " + +== TEMPLATE ================================================================= +In deiner Mail wurde kein gueltiger Wahlschein gefunden. Bitte lies die +Hinweise im Call for Votes (CfV) und stimme nochmal ab. +Dieser Fehler tritt meistens dann auf, wenn du Zeilen loeschst, die du +laut CfV nicht haettest loeschen sollen. + +Hier die Mail, die ich erhalten habe: + +[$body] + + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/no-ballotid b/templates/no-ballotid new file mode 100644 index 0000000..2628944 --- /dev/null +++ b/templates/no-ballotid @@ -0,0 +1,19 @@ +votetaker := value mailfrom | fill-left 65 +head := value head | quote "> " +body := value body | quote "> " + +== TEMPLATE ================================================================= +In deinem Wahlschein konnte keine Wahlscheinkennung gefunden werden, +diese wurde dir zusammen mit dem Wahlschein zugeschickt. Solltest +du noch keine eigenen Wahlscheinkennung haben, so kannst du mit +einer Mail an die im CfV genannte Adresse eine anfordern. + +Hier die Mail, die ich erhalten habe: + +[$head] +> +[$body] + + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/no-votes b/templates/no-votes new file mode 100644 index 0000000..1a6de00 --- /dev/null +++ b/templates/no-votes @@ -0,0 +1,20 @@ +votetaker := value mailfrom | fill-left 65 +body := value body | quote "> " + +== TEMPLATE ================================================================= +Offenbar hast du keinerlei Stimmen abgegeben. Wenn das ein Irrtum war, +lies bitte nochmal die Hinweise im CfV und wiederhole Deine Stimmabgabe. + +Dieser Fehler tritt oft auf, wenn du dich vertippst oder dich nicht an +das im Wahlschein vorgegebene Format haeltst. + +Wenn du dich enthalten willst, dann setze bitte ENTHALTUNG an die +Stelle von JA oder NEIN. + +Hier die Mail, die ich erhalten habe: + +[$body] + + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/result-multi b/templates/result-multi new file mode 100644 index 0000000..cc3659b --- /dev/null +++ b/templates/result-multi @@ -0,0 +1,31 @@ +votename-first := value votename | first-words 60 +votename-more := value votename | drop-words 60 | create-lines 60 +numinvalid := value numinvalid | fill-left 4 +numabstain-formatted := value numabstain | fill-left 4 +yes := value yes | fill-left 4 +no := value no | fill-left 4 +group-first := value group | first-words 40 +group-more := value group | drop-words 40 | create-lines 40 + +== TEMPLATE ============================================================ + +Ergebnisse [$votename-first] +[@votename-more| [$line]\n] +([$numvalid] gueltige Stimmen) + + Ja Nein : 2/3? >=60? : ang.? : Gruppe +---- ---- : ---- ----- : ----- : --------------------------------------- +[@count|[$yes] [$no] : [?cond1| Ja |Nein] [?cond2| Ja |Nein] : [?result| Ja |Nein] : [$group-first] +[@group-more| : : : [$line]\n]\n] +[?numabstain|[$numabstain-formatted] Enthaltungen] +[?numinvalid|[$numinvalid] ungueltige Stimme(n)] + +Gegen dieses Ergebnis kann innerhalb einer Woche nach seiner +Veroeffentlichung Einspruch erhoben werden. Der Einspruch ist per +E-Mail bei der Moderation von de.admin.news.announce (Adressen +siehe Signatur) einzulegen. +Wenn es keine ernsthaften Einsprueche gibt oder diese abgelehnt +werden, wird die Moderation von de.admin.news.announce das +Ergebnis danach umsetzen. + + diff --git a/templates/result-proportional b/templates/result-proportional new file mode 100644 index 0000000..53685d1 --- /dev/null +++ b/templates/result-proportional @@ -0,0 +1,31 @@ +votename-first := value votename | first-words 60 +votename-more := value votename | drop-words 60 | create-lines 60 +proportion := value proportion | sprintf '%6.3f' +numinvalid := value numinvalid | fill-left 4 +numabstain-formatted := value numabstain | fill-left 4 +yes := value yes | fill-left 4 +no := value no | fill-left 4 +group-first := value group | first-words 40 +group-more := value group | drop-words 40 | create-lines 40 + +== TEMPLATE ============================================================ + +Ergebnisse [$votename-first] +[@votename-more| [$line]\n] +([$numvalid] gueltige Stimmen) + + Ja Nein : J>=N? Ja/Nein : ang.? : Gruppe +---- ---- : ----- ------- : ----- : --------------------------------------- +[@count|[$yes] [$no] : [?cond1| Ja |Nein] [$proportion] : : [$group-first] +[@group-more| : : : [$line]\n]\n] +[?numinvalid|[$numinvalid] ungueltige Stimme(n)] + +Gegen dieses Ergebnis kann innerhalb einer Woche nach seiner +Veroeffentlichung Einspruch erhoben werden. Der Einspruch ist per +E-Mail bei der Moderation von de.admin.news.announce (Adressen +siehe Signatur) einzulegen. +Wenn es keine ernsthaften Einsprueche gibt oder diese abgelehnt +werden, wird die Moderation von de.admin.news.announce das +Ergebnis danach umsetzen. + + diff --git a/templates/result-single b/templates/result-single new file mode 100644 index 0000000..1be10d2 --- /dev/null +++ b/templates/result-single @@ -0,0 +1,28 @@ +votename-first := value votename | first-words 60 +votename-more := value votename | drop-words 60 | create-lines 60 + +votename-text-first := value votename | first-words 30 +votename-text-more := value votename | drop-words 30 | create-lines 72 + +== TEMPLATE ============================================================ + +Ergebnisse [$votename-first] +[@votename-more| [$line]\n] +([$numvalid] gueltige Stimmen) + +Es gab [$yes] Ja-Stimmen und [$no] Nein-Stimmen[?numabstain| bei [$numabstain] Enthaltungen]. +[?numinvalid|[$numinvalid] Stimme(n) wurden als ungueltig gewertet.] + +Es wurde [?cond1|die|keine] 2/3-Mehrheit erreicht und es gingen [?cond2|mehr|weniger] als +60 Ja-Stimmen ein. Damit ist die [$votename-text-first] +[@votename-text-more|[$line] |\n][?cond1|[?cond2|angenommen|abgelehnt]|abgelehnt]. + +Gegen dieses Ergebnis kann innerhalb einer Woche nach seiner +Veroeffentlichung Einspruch erhoben werden. Der Einspruch ist per +E-Mail bei der Moderation von de.admin.news.announce (Adressen +siehe Signatur) einzulegen. +Wenn es keine ernsthaften Einsprueche gibt oder diese abgelehnt +werden, wird die Moderation von de.admin.news.announce das +Ergebnis danach umsetzen. + + diff --git a/templates/rule-violated b/templates/rule-violated new file mode 100644 index 0000000..ef95b33 --- /dev/null +++ b/templates/rule-violated @@ -0,0 +1,20 @@ +votetaker := value mailfrom | fill-left 65 +body := value body | quote "> " + +== TEMPLATE ================================================================= +Diese Abstimmung unterliegt bestimmten Bedingungen, fuer welche Gruppen +wie abgestimmt werden kann. Diese Bedingungen wurden im Call for Votes +erklaert. Deine Stimme verletzt eine oder mehrere der Regeln. Bitte lies +den CfV noch einmal durch und wiederhole Deine Stimmabgabe. + +Die folgenden Regeln wurden nicht eingehalten: + +[$rules] + +Hier die Mail, die ich erhalten habe: + +[$body] + + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/templates/voterlist b/templates/voterlist new file mode 100644 index 0000000..4187ce6 --- /dev/null +++ b/templates/voterlist @@ -0,0 +1,12 @@ +multi-line := value name | justify-before mail 72 + +== TEMPLATE ============================================================ + +Folgende Personen haben sich bislang an der Abstimmung beteiligt: +======================================================================== +[@multi|[$multi-line]|\n] + + +Als ungueltig erkannte Stimmen: +======================================================================== +[@invalid|[$multi-line]\n[$reason]|\n] diff --git a/templates/votes-multi b/templates/votes-multi new file mode 100644 index 0000000..6029098 --- /dev/null +++ b/templates/votes-multi @@ -0,0 +1,36 @@ +grafix := value group | multi-graph 72 pos groupcount +grafix-line := multi-line 72 groupcount +multi-line := value name | append vote | justify-before mail 72 +invalid-line := value name | justify-before mail 72 +numinvalid := value numinvalid | fill-left 4 +yes := value yes | fill-left 4 +no := value no | fill-left 4 +condition1 := value cond1 | fill-both 4 +condition2 := value cond2 | fill-both 5 +result := value result | fill-both 5 + +== TEMPLATE ============================================================ + +======================================================================== + +Wichtiger Hinweis: + +Die unten aufgefuehrten Personen haben der Speicherung, Verarbeitung +und Veroeffentlichung ihrer Adressen und Stimmdaten nur im Rahmen dieses +Verfahrens zugestimmt. Eine Verwendung darueber hinaus wurde nicht +erlaubt. Damit ist insbesondere die Nutzung oder Uebermittlung der Daten +fuer Werbezwecke oder fuer die Markt- oder Meinungsforschung verboten. + +======================================================================== + + +Liste der gezaehlten Stimmen: + +[@groups|[$grafix]|\n] +[$grafix-line] +[@multi|[$multi-line]|\n] + + +Ungueltige Stimmen: +------------------------------------------------------------------------ +[@invalid|[$invalid-line]\n[$reason]|\n] diff --git a/templates/votes-single b/templates/votes-single new file mode 100644 index 0000000..64e1d37 --- /dev/null +++ b/templates/votes-single @@ -0,0 +1,33 @@ +mail-name := value mail | justify name 72 +invalid-line := value name | justify-before mail 72 + +== TEMPLATE ============================================================ + +======================================================================== + +Wichtiger Hinweis: + +Die unten aufgefuehrten Personen haben der Speicherung, Verarbeitung +und Veroeffentlichung ihrer Adressen und Stimmdaten nur im Rahmen dieses +Verfahrens zugestimmt. Eine Verwendung darueber hinaus wurde nicht +erlaubt. Damit ist insbesondere die Nutzung oder Uebermittlung der Daten +fuer Werbezwecke oder fuer die Markt- oder Meinungsforschung verboten. + +======================================================================== + + +Ja gestimmt: +------------------------------------------------------------------------ +[@yes|[$mail-name]\n] + +Nein gestimmt: +------------------------------------------------------------------------ +[@no|[$mail-name]\n] + +[?abstain|Enthaltung: +------------------------------------------------------------------------ +[@abstain|[$mail-name]\n]] + +Ungueltige Stimmen: +------------------------------------------------------------------------ +[@invalid|[$invalid-line]\n[$reason]|\n] diff --git a/templates/wrong-ballotid b/templates/wrong-ballotid new file mode 100644 index 0000000..0eb49f7 --- /dev/null +++ b/templates/wrong-ballotid @@ -0,0 +1,23 @@ +votetaker := value mailfrom | fill-left 65 +head := value head | quote "> " +body := value body | quote "> " + +== TEMPLATE ================================================================= +Die Wahlscheinkennung, die du in deinem Wahlschein benutzt hast, +entspricht nicht der Kennung, die beim Wahlleiter fuer deine EMail- +Adresse registriert ist, bitte benutzte die Wahlscheinkennung +aus deinem persoenlichen Wahlschein. + +Solltest du wirklich eine andere Wahlscheinkennung zugeschickt +bekommen haben (oder du hast gar keinen Wahlschein angefordert), +so setzte dich bitte mit mir in Verbindung. + +Hier die Mail, die ich erhalten habe: + +[$head] +> +[$body] + + +[$votetaker] +\[mit [$usevote_version]\] diff --git a/tmp/ergebnis-1191790177 b/tmp/ergebnis-1191790177 new file mode 100644 index 0000000..e69de29 diff --git a/tmp/stimmen-1191790177 b/tmp/stimmen-1191790177 new file mode 100644 index 0000000..e69de29 diff --git a/uidlcache b/uidlcache new file mode 100644 index 0000000..e69de29 diff --git a/usevote.cfg b/usevote.cfg new file mode 100644 index 0000000..cf801f5 --- /dev/null +++ b/usevote.cfg @@ -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 + +# Absender fuer den Envelope (Return-Path) der Bestaetigungsmails +# bei Verwendung von SMTP (bitte einfach nur die Adresse eintragen, +# ohne Klammern und Zusaetze). Bei smtp=0 muss das in "mailcmd" +# eingestellt werden, z.B. "-fadresse" für Sendmail +envelopefrom = gvv@foo.bar + +# Nur bei persoenlichen Wahlscheinen: Datei mit Anforderungsmails +requestfile = anforderung + +# Datenschutzklausel generieren und auf deren Existenz pruefen? +# Konfiguration siehe unten (bdsgtext) +bdsg = 1 + +# Reply-To beachten? +# Nicht empfohlen, da jemand fuer andere Personen abstimmen und +# die Bestaetigungen zu sich umlenken koennte. +replyto = 0 + +# Stimmen einzeln bestaetigen? Empfohlen! +voteack = 1 + +# Bcc-Adresse fuer alle Mails (Backup fuer alle Faelle) +# +#mailcc = + +# Alles in einem Schritt durchfuehren (gesonderter Aufruf von +# "uvvote.pl clean" entfaellt)? Fuehrt zu geringeren +# Eingreifmoeglichkeiten! (Mails werden automatisch verschickt) +onestep = 0 + +# Fuer das Ergebnis (xx Ja-Stimmen, xx Nein-Stimmen, xx Enthaltungen) +# auch bei Eingruppenabstimmung des Mehrgruppenformat waehlen? +multigroup = 1 + +# Bedingungen fuer einen Erfolg der Abstimmung (genau zwei erforderlich). +# Es muss sich jeweils um gueltigen Perl-Code handeln, Rueckgabewert +# wird boolesch ausgewertet (true/false). Als Variablen sind $yes +# und $no zugelassen. Falls oben "proportional = 1" gesetzt wurde, +# wird nur Bedingung 1 ausgewertet und kann z.B. auf "$yes>$no" gesetzt +# werden. +condition1 = $yes>=2*$no +condition2 = $yes>=60 + +# Ergebnisdatei, in der alle Einzelergebniss zusammengeschrieben werden +# (wird bei jedem Programmlauf neu erstellt!) +resultfile = ergebnis.alle + +# Datei fuer Scheinkennungen +idfile = scheinkennungen + +# POP3 benutzen? (falls nicht, wird eine lokale Mailbox eingelesen) +pop3 = 1 + +# Mailbox, in der die zu verarbeitenden Mails liegen (falls pop3=0) +votefile = votes + +# POP3-Einstellungen fuer Abruf der eingehenden Wahlscheine: +# Server, Port, Benutzername, Passwort +pop3server = 127.0.0.1 +pop3port = 110 +pop3user = test +pop3pass = test +# Mail nach dem Abrufen vom Server loeschen? +pop3delete = 0 +# Dateiname zum Speichern der bereits abgerufenen Mail-IDs (UIDL) +pop3uidlcache = uidlcache + +# POP3-Einstellungen fuer Abruf von Wahlschein-Anforderungen bei Abstimmungen +# mit personalisierten Wahlscheinen (Punkt 6a der Wahlregeln in de.*) +# Diese zweite Mailbox ist notwendig, um Wahlschein-Anforderungen und die +# eigentliche Abstimmung voneinander zu trennen (nicht noetig, wenn +# personal = 0 gesetzt ist) +pop3server_req = 127.0.0.1 +pop3port_req = 110 +pop3user_req = test +pop3pass_req = test +# Mail nach dem Abrufen vom Server loeschen? +pop3delete_req = 0 +# Dateiname zum Speichern der bereits abgerufenen Mail-IDs (UIDL) +pop3uidlcache_req = uidlcache_req + +# POP3-Einstellungen fuer uvbounce.pl (Verarbeitung von Bounces +# und Generierung einer Liste mit ungueltigen Stimmen). Alle +# zurueckgekommenen Mails an Waehler sollten in dieser Mailbox landen +pop3server_bounce = 127.0.0.1 +pop3port_bounce = 110 +pop3user_bounce = test2 +pop3pass_bounce = test2 +# Mail nach dem Abrufen vom Server loeschen? +pop3delete_bounce = 0 +# Dateiname zum Speichern der bereits abgerufenen Mail-IDs (UIDL) +pop3uidlcache_bounce = uidlcache_bounce + +######################################################################## +# Alles ab hier braucht i.d.R. nur einmal festgelegt werden. +# Es ist moeglich, diese Einstellungen in eine globale Konfigurations- +# datei auszulagern. Das Einbinden erfolgt mit der Zeile +# +# include /pfad/zur/globalen_datei +# +# Falls Einstellungen aus der globalen Konfigurationsdatei hier +# fuer einzelne Abstimmungen ueberschrieben werden sollen, muessen +# diese *hinter* dem Include-Befehl stehen! +# (der letzte Wert ueberschreibt vorhergehende Definitionen) +######################################################################## + +# SMTP benutzen? (falls nicht, wird der weiter unten einstellbare +# MTA direkt aufgerufen; unter Windows kann nur SMTP benutzt werden!) +smtp = 1 + +# SMTP-Server (falls smtp = 1) +smtpserver = localhost +smtpport = 25 + +# SMTP-Authentifizierung benutzen? (RFC 2554) +# Das entsprechende Perlmodul (Net::SMTP) kann derzeit nur AUTH PLAIN, +# funktioniert also moeglicherweise nicht mit jedem Server +#smtpauth = 0 +#smtpuser = +#smtppass = + +# Falls als HELO etwas anderes als der Hostname verwendet werden soll: +#smtphelo = + +# Falls ein anderer Fully Qualified Domain Name als der Hostname fuer +# die Message-ID verwendet werden soll: +#fqdn = + +# Verzeichnis fuer fertig verarbeitete Mails und Ergebnisse +archivedir = fertig + +# Temporaeres Verzeichnis +tmpdir = tmp + +# Pfad zu den Templates (kommaseparierte Liste mit Verzeichnissen) +templatedir = templates + +# Konvertierungsfunktionen für die Templates +# (kommaseparierte Liste mit Funktions-Modulen) +formats = UVformats.pm + +# Dateiname der Steuerungsdatei fuer den Mailversandt +controlfile = tmp/ack.control + +# Dateiname des Shellscripts zum Versenden der Bestaetigungsmails (falls smtp=0) +domailfile = tmp/domail + +# MTA-Aufruf zum Verschicken der Bestaetigungsmails +# nuetzlich ist die Sendmail-Option -f zum Setzen des Absenders +#mailcmd = sendmail -oi -oem -femail@adresse + +# Weiteres Kommando, welches nach jeder Mail aufgerufen werden soll (falls smtp=0). +# Sinnvoll ist ein "sleep x", wobei x bei langsamen Systemen hoeher +# gewaehlt werden sollte, um die Belastung gering zu halten. +sleepcmd = sleep 1 + +# Shellbefehl zum Loeschen des Bildschirms +# Unix: i.d.R. "clear" +# Windows: "cls" +# Falls das Betriebssystem bzw. die Shell keinen solchen Befehl zur Verfuegung +# stellt, sollte ein Kommando verwendet werden, welches eine Trennlinie +# oder aehnliches auf dem Bildschirm ausgibt, z.B. mit "echo" +clearcmd = clear + +# Shellbefehl zum seitenweisen Darstellen von Mails auf dem Bildschirm +# Empfohlene Einstellung: "less", da more Probleme mit der Umleitung von +# STDERR in eine Datei Probleme hat. Unter Windows ist "less" nicht +# vorinstallirt, kann man sich aber herunterladen und einfach in das +# Windows-Verzeichnis kopieren (URL siehe README-Datei) +pager = less + +# Datei mit diversen Meldungen und Textfragmenten (Resourcendatei) +messagefile = messages.cfg + +# Datei mit Wahlregeln +rulefile = usevote.rul + +# Datei mit verdaechtigen Mailadressen +badaddrfile = mailpatterns.cfg + +# Datei fuer Fehlermeldungen beim Programmlauf +errorfile = errors.log + +# Lockdatei (Verhinderung von mehrfachen Programmstarts) +lockfile = usevote.lock + +# Einleitungszeile fuer naechste Mail (RegExp) +mailstart = "^From " + +# Trennlinien vor und nach dem Wahlschein +begin_divider = Alles vor dieser Zeile bitte loeschen +end_divider = Alles nach dieser Zeile bitte loeschen + +# Text fuer die Namens-Angabe im Wahlschein. Achtung, muss im +# Wahlschein genauso stehen! +nametext = Dein Realname, falls nicht im FROM-Header: + +# Text fuer Namens-Angabe in Bestaetigungsmails +nametext2 = Waehlername: + +# Text fuer die Adress-Angabe im Wahlschein +addresstext = Waehleradresse: + +# Text für die Angabe der Wahlscheinkennung (siehe Option "personal") +ballotidtext = Wahlscheinkennung: + +# Text fuer Datenschutzklausel (siehe Option "bdsg"), erscheint als Abstimmungspunkt +bdsgtext = Datenschutzklausel - Zustimmung: Ich bin mit der Verarbeitung meiner Daten wie oben beschrieben einverstanden + +# Datei mit Erklaerungstext fuer BDSG-Klausel +bdsgfile = bdsgtext.cfg + +# Rechter Rand fuer einige Bildschirmausgaben (Terminalbreite) +rightmargin = 72 + +# Regular Expression fuer Erkennung eines gueltigen Realnamens +name_re = [-a-zA-ZäöüáàâéèêíìîóòôúùûÄÖÜÁÁÂÉÈÊÍÌÎÓÒÔÚÙÛß]{2,} +.*[a-zA-ZäöüáàâéèêíìîóòôúùûÄÖÜÁÁÂÉÈÊÍÌÎÓÒÔÚÙÛß]{2,} + +# RegExp fuer JA-Stimmen (case-insensitive) +# Standardmaessig wird J, JA, FUER und DAFUER erkannt +ja_stimme = (J\s*A|J|(D\s*A\s*)?F\s*U\s*E\s*R) + +# RegExp fuer NEIN-Stimmen (case-insensitive) +# Standardmaessig wird N, NEIN, GEGEN und DAGEGEN erkannt +nein_stimme = (N\s*E\s*I\s*N|N|(D\s*A\s*)?G\s*E\s*G\s*E\s*N) + +# RegExp fuer ENTHALTUNG (case-insensitive) +enth_stimme = (E|E\s*N\s*T\s*H\s*A\s*L\s*T\s*U\s*N\s*G) + +# RegExp fuer ANNULLIERUNG (case-insensitive) +# Achtung, sollte auch in den Templates im Bestaetigungstext angepasst werden +ann_stimme = A\s*N\s*N\s*U\s*L\s*L\s*I\s*E\s*R\s*U\s*N\s*G + +# Template files (these files are in the template directory defined above) +tpl_mailheader = "mailheader" # generally used mail header +tpl_bouncelist = "bouncelist" # used by uvbounce.pl +tpl_result_multi = "result-multi" # used by uvcount.pl -r -m +tpl_result_single = "result-single" # used by uvcount.pl -r -o +tpl_result_prop = "result-proportional" # used by uvcount.pl -r (proportional = 1) +tpl_votes_multi = "votes-multi" # used by uvcount.pl -v (multiple groups) +tpl_votes_single = "votes-single" # used by uvcount.pl -v (single group only) +tpl_voterlist = "voterlist" # used by uvcount.pl -l (2nd CfV) +tpl_ballot = "ballot" # used by uvballot.pl (personal = 0) +tpl_ballot_request = "ballot-request" # used by uvballot.pl (personal = 1) +tpl_ballot_personal = "ballot-personal" # used by uvcfv.pl (personal = 1) +tpl_addr_reg = "address-not-registered" # used by uvvote.pl (personal = 1) +tpl_no_ballotid = "no-ballotid" # used by uvvote.pl (personal = 1) +tpl_wrong_ballotid = "wrong-ballotid" # used by uvvote.pl (personal = 1) +tpl_bdsg_error = "bdsg-error" # used by uvvote.pl (bdsg = 1) +tpl_ack_mail = "ack-mail" # used by uvvote.pl (voteack = 1) +tpl_cancelled = "cancelled" # used by uvvote.pl +tpl_invalid_account = "invalid-account" # used by uvvote.pl +tpl_invalid_name = "invalid-name" # used by uvvote.pl +tpl_multiple_votes = "multiple-votes" # used by uvvote.pl +tpl_no_ballot = "no-ballot" # used by uvvote.pl +tpl_no_votes = "no-votes" # used by uvvote.pl +tpl_rule_violated = "rule-violated" # used by uvvote.pl (c.f. usevote.rul) diff --git a/usevote.rul b/usevote.rul new file mode 100644 index 0000000..4d9a6c6 --- /dev/null +++ b/usevote.rul @@ -0,0 +1,68 @@ +# UseVote (c) 1993,94 Ron Dippold, alle Rechte vorbehalten +# uebersetzt von Frederik Ramm +# +# Mit dieser Datei koennen spezielle Regeln fuer eine gueltige Stimmabgabe +# bei Mehrgruppenabstimmungen erstellt werden. +# Beispiel: Eine Gruppe soll aufgeteilt werden, und Du willst erzwingen, +# dass jemand, der fuer eine der neuen Untergruppen stimmt, auch fuer +# die .misc-Gruppe stimmen muss. Man kann eine Menge komplexe Sachen hier- +# mit machen, wenn man Programmierer ist :-) +# +# Das allgemeine Format sieht so aus: +# (a) alles, was mit # anfaengt, ist ein Kommentar. +# (b) "echte" Regeln sehen etwa so aus (natuerlich ohne #): +# if .jjjjj then J..... +# +# Eine Regel beginnt immer mit "if", und danach folgen eine Anzahl Symbole; +# diese Anzahl muss gleich der Anzahl der Gruppen sein, ueber die abgestimmt +# wird. +# Oben geht es also um eine Abstimmung ueber sechs Gruppen. +# Die Symbole zwischen 'if' und 'then' geben an, welche Bedingungen erfuellt +# sein muessen, damit das System auch die Bedingungen hinter then prueft. +# +# Nach 'then' folgen nochmal so viele Symbole, die angeben, welche Bedin- +# gungen erfuellt sein muessen, falls die Bedingungen zwischen 'if' und +# 'then' erfuellt waren. Jeder Wahlschein, auf den das nicht zutrifft, ist +# ungueltig. +# +# Folgende Symbole sind erlaubt: +# J eine JA-Stimme +# N eine NEIN-Stimme +# E eine Enthaltung +# S eine JA- oder NEIN-Stimme +# H eine Enthaltung oder JA-Stimme +# I eine Enthaltung oder NEIN-Stimme +# . egal (Ja, nein oder Enthaltung) +# j eine oder mehrere der markierten Gruppen hat JA-Stimme +# n "" "" "" "" "" "" "" NEIN-Stimme +# e "" "" "" "" "" "" "" Enthaltung +# s "" "" "" "" "" "" "" Ja- oder Nein-Stimme +# h "" "" "" "" "" "" "" Enthaltung oder Ja-Stimme +# i "" "" "" "" "" "" "" Enthaltung oder Nein-Stimme +# +# Alles klar? Jede Stimme wird mit den Symbolen verglichen, und wenn alle +# Kriterien passen, ist der Ausdruck wahr. Wenn der erste Ausdruck (if x) +# wahr ist, muss auch der zeite (then y) wahr sein, sonst ist der Wahlschein +# ungueltig. Ein Beispiel: +# if .jjjjj then J..... +# Das heisst: Wenn der Waehler fuer *irgendeine* ausser der ersten Gruppe mit +# JA stimmt, dann *muss* er JA fuer die erste Gruppe stimmen, oder das ganze +# ist ungueltig. +# Das koennte z.B. eine Gruppenaufteilung sein, wo fuer die erste (die .misc)- +# Gruppe gestimmt werden *muss*, wenn fuer eine der anderen gestimmt wird. +# +# Hier noch ein Beispiel: +# if S... then .ss. +# if .S.. then ..E. +# if ..S. then .E.. +# Diese Regeln sagen: Wer fuer die erste Gruppe abstimmt, der muss auch fuer +# die zweite und dritte Gruppe abstimmen - egal wie. Ausserdem muss er (Re- +# geln 2 und 3) sich bei 3 enthalten, wenn er bei 2 eine Stimme abgibt und +# umgekehrt. Die vierte Gruppe wird hier gar nicht betroffen. +# +# Also: es gibt einfache Regeln, aber es sind auch sehr komplizierte Kom- +# binationen denkar. Das Programm macht alles mit... die Frage ist, ob +# es die Waehler tun :-) +# +# Fuege Deine Regeln hier an. Mit "uvvote.pl -t" kannst Du sie testen. + diff --git a/uvballot.pl b/uvballot.pl new file mode 100644 index 0000000..4085a7f --- /dev/null +++ b/uvballot.pl @@ -0,0 +1,116 @@ +#!/usr/bin/perl -w + +############################################################################### +# UseVoteGer 4.09 Wahlscheingenerierung +# (c) 2001-2005 Marc Langer +# +# 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 < +# +# 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 < +# +# 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 < +# +# 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() { + 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 < +# +# 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 ; + 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('', ); + close(THISRESULT); + } + close(RESULT); + + print "\n"; + +} + + +############################################################################## +# Print help text (options and syntax) on -h or --help # +############################################################################## + +sub help { + print <