Update version number and copyright.
[usenet/usevote.git] / uvcfv.pl
1 #!/usr/bin/perl -w
2
3 ###############################################################################
4 # UseVoteGer 4.11 Personalisierte Wahlscheine
5 # (c) 2001-2012 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 }
This page took 0.017491 seconds and 3 git commands to generate.