| 1 | #!/usr/bin/perl -w |
| 2 | |
| 3 | ############################################################################### |
| 4 | # UseVoteGer 4.12 Personalisierte Wahlscheine |
| 5 | # (c) 2001-2014 Marc Langer <uv@marclanger.de> |
| 6 | # |
| 7 | # This script package is free software; you can redistribute it and/or |
| 8 | # modify it under the terms of the GNU Public License as published by the |
| 9 | # Free Software Foundation. |
| 10 | # |
| 11 | # Use this script to read mails and send back a CfV with unique ballot id |
| 12 | # |
| 13 | # Many thanks to: |
| 14 | # - Ron Dippold (Usevote 3.0, 1993/94) |
| 15 | # - Frederik Ramm (German translation, 1994) |
| 16 | # - Wolfgang Behrens (UseVoteGer 3.1, based on Frederik's translation, 1998/99) |
| 17 | # - Cornell Binder for some good advice and code fragments |
| 18 | # |
| 19 | # This is a complete rewrite of UseVoteGer 3.1 in Perl (former versions were |
| 20 | # written in C). Not all functions of Usevote/UseVoteGer 3.x are implemented! |
| 21 | ############################################################################### |
| 22 | |
| 23 | use strict; |
| 24 | use Getopt::Long; |
| 25 | use Digest::MD5 qw(md5_hex); |
| 26 | use Text::Wrap qw(wrap $columns); |
| 27 | use FindBin qw($Bin); |
| 28 | use lib $Bin; |
| 29 | use UVconfig; |
| 30 | use UVmenu; |
| 31 | use UVmessage; |
| 32 | use UVreadmail; |
| 33 | use UVsendmail; |
| 34 | use UVtemplate; |
| 35 | |
| 36 | my %opt_ctl = (); |
| 37 | |
| 38 | print "\n$usevote_version Personalisierte Wahlscheine - (c) 2001-2005 Marc Langer\n\n"; |
| 39 | |
| 40 | # unknown parameters remain in @ARGV (for "help") |
| 41 | Getopt::Long::Configure(qw(pass_through bundling)); |
| 42 | |
| 43 | # Put known parameters in %opt_ctl |
| 44 | GetOptions(\%opt_ctl, qw(test t config-file=s c=s)); |
| 45 | |
| 46 | # test mode? (default: no) |
| 47 | my $test_only = $opt_ctl{test} || $opt_ctl{t} || 0; |
| 48 | |
| 49 | # # Additional parameters or invalid options? Show help and exit. |
| 50 | help() if (@ARGV); |
| 51 | |
| 52 | # Get name auf config file (default: usevote.cfg) and read it |
| 53 | my $cfgfile = $opt_ctl{'config-file'} || $opt_ctl{c} || "usevote.cfg"; |
| 54 | UVconfig::read_config($cfgfile); |
| 55 | |
| 56 | # Set columns for Text::Wrap |
| 57 | $columns = $config{rightmargin}; |
| 58 | |
| 59 | # read list of suspicious mail addresses from file |
| 60 | my @bad_addr = UVconfig::read_badaddr(); |
| 61 | |
| 62 | # exit if option "personal=1" in config file not set |
| 63 | unless ($config{personal}) { |
| 64 | die wrap ('', '', UVmessage::get("ERR_NOTPERSONAL", (CFGFILE => $cfgfile))) . "\n\n"; |
| 65 | } |
| 66 | |
| 67 | # option -t used? |
| 68 | if ($test_only) { |
| 69 | print_ballot(); |
| 70 | exit 0; |
| 71 | } |
| 72 | |
| 73 | # check for lock file |
| 74 | if (-e $config{lockfile}) { |
| 75 | my $lockfile = $config{lockfile}; |
| 76 | |
| 77 | # don't delete lockfile in END block ;-) |
| 78 | $config{lockfile} = ''; |
| 79 | |
| 80 | # exit |
| 81 | die UVmessage::get("ERR_LOCK", (FILE=>$lockfile)) . "\n\n"; |
| 82 | } |
| 83 | |
| 84 | # safe exit (delete lockfile) |
| 85 | $SIG{QUIT} = 'sighandler'; |
| 86 | $SIG{INT} = 'sighandler'; |
| 87 | $SIG{KILL} = 'sighandler'; |
| 88 | $SIG{TERM} = 'sighandler'; |
| 89 | $SIG{HUP} = 'sighandler'; |
| 90 | |
| 91 | # create lock file |
| 92 | open (LOCKFILE, ">$config{lockfile}"); |
| 93 | close (LOCKFILE); |
| 94 | |
| 95 | # check for tmp directory and domail file |
| 96 | unless (-d $config{tmpdir}) { |
| 97 | mkdir ($config{tmpdir}, 0700) |
| 98 | or die UVmessage::get("ERR_MKDIR", (DIR => $config{tmpdir})) . "\n$!\n\n"; |
| 99 | } |
| 100 | |
| 101 | # generate filename for mail archive |
| 102 | # normally unixtime is sufficient, if it is not unique append a number |
| 103 | my $file = my $base = "anforderung-" . time(); |
| 104 | my $count = 0; |
| 105 | while ($count<1000 && (-e "$config{archivedir}/$file" || -e "$config{tmpdir}/$file")) { |
| 106 | $file = "$base-" . ++$count; |
| 107 | } |
| 108 | die UVmessage::get("ERR_FILE_CREATION") . "\n\n" if ($count == 1000); |
| 109 | |
| 110 | unless ($config{pop3}) { |
| 111 | rename ($config{requestfile}, "$config{tmpdir}/$file") |
| 112 | or die UVmessage::get("ERR_RENAME_MAILFILE") . "$!\n\n"; |
| 113 | } |
| 114 | |
| 115 | # wait, so that current mail deliveries can finalize |
| 116 | sleep 2; |
| 117 | |
| 118 | # initiliaze random number generator |
| 119 | srand; |
| 120 | |
| 121 | # read votes and process them |
| 122 | # for each mail pass a reference to the sub to be called |
| 123 | # The third parameter "1" shows that it is called from uvcfv.pl |
| 124 | $count = UVreadmail::process("$config{tmpdir}/$file", \&process_request, 1); |
| 125 | print "\n", UVmessage::get("CFV_NUMBER", (COUNT => $count)), "\n\n"; |
| 126 | UVsendmail::send(); |
| 127 | |
| 128 | print UVmessage::get("INFO_TIDY_UP") . "\n\n"; |
| 129 | rename("$config{tmpdir}/$file", "$config{archivedir}/$file"); |
| 130 | chmod (0400, "$config{archivedir}/$file"); |
| 131 | |
| 132 | exit 0; |
| 133 | |
| 134 | |
| 135 | ############################################################################## |
| 136 | # Evaluate a ballot request # |
| 137 | # Called from UVreadmail::process() for every mail # |
| 138 | # Parameters: Voter address and name, date header of vote mail (strings), # |
| 139 | # complete header and body (references to Strings) # |
| 140 | ############################################################################## |
| 141 | |
| 142 | sub process_request { |
| 143 | my ($voter_addr, $voter_name, $h_date, $entity, $body) = @_; |
| 144 | |
| 145 | my @header = split(/\n/, $entity->stringify_header); |
| 146 | my $head = $entity->head; |
| 147 | my $msgid = $head->get('Message-ID'); |
| 148 | chomp($msgid) if ($msgid); |
| 149 | |
| 150 | # found address? |
| 151 | if ($voter_addr) { |
| 152 | # check for suspicious addresses |
| 153 | foreach my $element (@bad_addr) { |
| 154 | if ($voter_addr =~ /^$element/) { |
| 155 | my (@votes, @set, $ballot_id, $voting); # irrelevant, but necessary for UVmenu::menu() |
| 156 | my @errors = ('SuspiciousAccountBallot'); |
| 157 | my $res = UVmenu::menu(\@votes, \@header, $body, \$voter_addr, \$voter_name, \$ballot_id, \$voting, \@set, \@errors); |
| 158 | |
| 159 | # "Ignore": don't deliver a ballot |
| 160 | return 0 if ($res eq 'i'); |
| 161 | if (@errors) { |
| 162 | # send error mail if address hasn't been accepted |
| 163 | my $template = UVtemplate->new(); |
| 164 | $template->setKey('head' => $entity->stringify_header); |
| 165 | $template->setKey('body' => $$body); |
| 166 | my $msg = $template->processTemplate($config{tpl_invalid_account}); |
| 167 | UVsendmail::mail($voter_addr, "Fehler", $msg, $msgid); |
| 168 | return 0; |
| 169 | } |
| 170 | last; |
| 171 | } |
| 172 | } |
| 173 | } else { |
| 174 | |
| 175 | # no address found in mail (non-RFC compliant?) |
| 176 | my (@votes, @set, $ballot_id); # irrelevant, but necessary for UVmenu::menu() |
| 177 | my @errors = ('InvalidAddressBallot'); |
| 178 | my $res = UVmenu::menu(\@votes, \@header, $body, \$voter_addr, \$voter_name, \$ballot_id, \@set, \@errors); |
| 179 | |
| 180 | # "ignore" or address not ok: no ballot can be sent |
| 181 | return 0 if (@errors || $res eq 'i'); |
| 182 | } |
| 183 | |
| 184 | my $subject = UVmessage::get("CFV_SUBJECT"); |
| 185 | my $template = UVtemplate->new(); |
| 186 | my $ballot_id = ""; |
| 187 | |
| 188 | #if ($ballot_id ne $ids{$voter_addr}) { |
| 189 | if ($ids{$voter_addr}) { |
| 190 | $ballot_id = $ids{$voter_addr}; |
| 191 | $template->setKey('alreadysent' => 1) if ($ballot_id = $ids{$voter_addr}); |
| 192 | } else { |
| 193 | # generate new ballot id from the MD5 sum of header, body and a random value |
| 194 | $ballot_id = md5_hex($entity->stringify_header . $body . rand 65535); |
| 195 | $ids{$voter_addr} = $ballot_id; |
| 196 | |
| 197 | # write ballot id to file |
| 198 | open(IDFILE, ">>$config{idfile}") |
| 199 | or die UVmessage::get("CFV_ERRWRITE", (FILE => $config{idfile})) . "\n\n"; |
| 200 | print IDFILE "$voter_addr $ballot_id\n"; |
| 201 | close(IDFILE) or die UVmessage::get("CFV_ERRCLOSE") . "\n\n"; |
| 202 | } |
| 203 | |
| 204 | $template->setKey('ballotid' => $ballot_id); |
| 205 | $template->setKey('address' => $voter_addr); |
| 206 | $template->setKey('bdsginfo' => $config{bdsginfo}); |
| 207 | |
| 208 | for (my $n=0; $n<@groups; $n++) { |
| 209 | $template->addListItem('groups', pos=>$n+1, group=>$groups[$n]); |
| 210 | } |
| 211 | |
| 212 | my $msg = $template->processTemplate($config{'tpl_ballot_personal'}); |
| 213 | |
| 214 | # $config{voteaccount} is the Reply-To address: |
| 215 | UVsendmail::mail($voter_addr, $subject, $msg, $msgid, $config{voteaccount}); |
| 216 | |
| 217 | } |
| 218 | |
| 219 | |
| 220 | ############################################################################## |
| 221 | # Print dummy personalized ballot in STDOUT for checking purposes # |
| 222 | # Called if command line argument -t is present # |
| 223 | ############################################################################## |
| 224 | |
| 225 | sub print_ballot { |
| 226 | my $template = UVtemplate->new(); |
| 227 | |
| 228 | # generate new ballot id |
| 229 | my $ballot_id = md5_hex(rand 65535); |
| 230 | |
| 231 | $template->setKey('ballotid' => $ballot_id); |
| 232 | $template->setKey('address' => 'dummy@foo.invalid'); |
| 233 | $template->setKey('bdsginfo' => $config{bdsginfo}); |
| 234 | |
| 235 | for (my $n=0; $n<@groups; $n++) { |
| 236 | $template->addListItem('groups', pos=>$n+1, group=>$groups[$n]); |
| 237 | } |
| 238 | |
| 239 | my $msg = $template->processTemplate($config{'tpl_ballot_personal'}); |
| 240 | |
| 241 | print $msg, "\n"; |
| 242 | } |
| 243 | |
| 244 | |
| 245 | ############################################################################## |
| 246 | # Handle Signals and delete lock files when exiting # |
| 247 | ############################################################################## |
| 248 | |
| 249 | END { |
| 250 | # delete lockfile |
| 251 | unlink $config{lockfile} if ($config{lockfile}); |
| 252 | } |
| 253 | |
| 254 | |
| 255 | sub sighandler { |
| 256 | my ($sig) = @_; |
| 257 | die "\n\nSIG$sig: deleting lockfile and exiting\n\n"; |
| 258 | } |
| 259 | |
| 260 | |
| 261 | ############################################################################## |
| 262 | # Print help text (options and syntax) on -h or --help # |
| 263 | ############################################################################## |
| 264 | |
| 265 | sub help { |
| 266 | print <<EOF; |
| 267 | Usage: uvcfv.pl [-c config_file] [-t] |
| 268 | uvcfv.pl -h |
| 269 | |
| 270 | Liest Mailboxen ein und beantwortet alle Mails mit personalisierten CfVs. |
| 271 | |
| 272 | -c config_file liest die Konfiguration aus config_file |
| 273 | (usevote.cfg falls nicht angegeben) |
| 274 | |
| 275 | -t, --test gibt einen Dummy-Wahlschein fuer Pruefzwecke aus |
| 276 | |
| 277 | -h, --help zeigt diesen Hilfetext an |
| 278 | |
| 279 | EOF |
| 280 | |
| 281 | exit 0; |
| 282 | } |