Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Secure Internet Voting with Perl


Listing 5. The vote program.

  0  #!/usr/bin/perl -Tw
  1  # -*- perl -*-

  2  use strict;
  3  use CGI qw(:standard *table *Tr *dl);
  4  use DBI;

  5  use constant REGISTRATION_CODE_LENGTH => 100;
  6  use vars qw(@CANDIDATES @PARTIES @OFFICES @CANDIDATE_NAME);
  7  $ENV{PATH} = '/bin';

  8  # connect to database
  9  my $DB = DBI->connect('dbi:mysql:CEA') or die "Can't connect: $DBI::errstr";

 10  ########################
 11  # create the page
 12  ########################
 13  print header,
 14    start_html(-title   => 'Indecision 2000',
 15               -bgcolor => 'white'
 16              ),
 17    h1({-align=>'CENTER'},
 18       img({-src=>'/icons/star.gif'}) x 3,
 19       'Indecision 2000',
 20       img({-src=>'/icons/star.gif'}) x 3);

 21  # load global variables
 22  get_globals();

 23  # If the VOTE button is pressed, validate and enter the ballot
 24  if (param('VOTE') && (my $registration = validate())) {
 25      enter_ballot($registration);
 26  }

 27  # Otherwise print the ballot
 28  else {
 29      generate_ballot();
 30  }

 31  # end of page
 32  print end_html;
 33  $DB->disconnect;

 34  exit 0;

 35  ###############################################################
 36  # get_globals() loads the @CANDIDATES, @CANDIDATE_NAME,
 37  # @PARTIES and @OFFICES globals from information in the
 38  # database.
 39  ###############################################################
 40  sub get_globals {

 41      # fetch the matrix of candidates, parties and offices
 42      my $query =<<END;
 43  SELECT candidate_id,first_name,last_name,party_name,
 44         office_name,candidate.party_id,candidate.office_id
 45    FROM candidate,party,office 
 46    WHERE candidate.office_id=office.office_id
 47    AND candidate.party_id=party.party_id
 48  END
 49  ;

 50      my $sth = $DB->prepare($query) or die "Can't prepare: ",$DB->errstr;
 51      $sth->execute;

 52      while (my($candidate_id,$first,$last,$party,$office,$party_id,$office_id) 
 53             = $sth->fetchrow_array) {
 54          $CANDIDATES[$party_id-1][$office_id-1] = $candidate_id;
 55          $CANDIDATE_NAME[$candidate_id] = "$first $last";
 56          $PARTIES[$party_id-1] = $party;
 57          $OFFICES[$office_id-1] = $office;
 58      }
 59      $sth->finish;
 60  }

 61  ###############################################################
 62  # generate_ballot(), voting_matrix(), validation_number() and vote()
 63  # create various parts of the page see by the voter
 64  ###############################################################
 65  sub generate_ballot {
 66      print start_multipart_form;
 67      voting_matrix();
 68      registration_number();
 69      vote();
 70      print end_form;
 71  }

 72  # This generates the table containing the ballot.
 73  sub voting_matrix {
 74      print img({-src=>'/icons/HandPointing.gif',-align=>'LEFT'}),
 75        h2('Step 1: Fill in your E-Ballot');

 76      print
 77        start_table({-cellspacing => 0,-border=>1}),
 78        Tr(th(''),th(\@OFFICES));

 79      for (my $party=0; $party < @PARTIES; $party++) {
 80          print start_Tr,th($PARTIES[$party]);

 81          for (my $office=0; $office < @OFFICES; $office++) {

 82              my $candidate = $CANDIDATES[$party][$office];
 83              print td({-bgcolor=>$office %2 ? 'white' : '#E0E0E0'},
 84                        $candidate ? radio_group(-name    => $office,
 85                                                 -value   => $candidate,
 86                                                 -labels  => {$candidate=>$CANDIDATE_NAME[$candidate]},
 87                                                 -default => '-',
 88                                              )
 89                                  : ' '
 90                      );
 91          }

 92          print end_Tr;
 93      }

 94      # Handle write-ins.
 95      print Tr(th(' '),
 96               td([map {radio_group(-name  => $_, -value => 'Write in:').
 97                         textfield(-name  => "writein $_",
 98                                  -value => '',
 99                                -override => defined param($_) && param($_)=~/^\d+$/
100                     )} (0..$#OFFICES)]
101                 )
102              ),
103        end_table;
104  }



105  # generate the field for entering voter registration number
106  sub registration_number {
107      print hr,
108        img({-src => '/icons/HandPointing.gif', -align => 'LEFT'}),
109        h2('Step 2: Enter your Registered Voter Code'),
110        blockquote(
111                   b('EITHER:'), 'Cut and paste the code here:',
112                   textarea(-name => 'registration_id', -rows =>4 , -cols => 70, -wrap => 'physical'), br,
113                   b('OR:'), 'Select voter registration file for upload here:', br,
114                   filefield(-name => 'registration_file')
115                 );
116  }

117  # generate the VOTE button
118  sub vote {
119      print hr,
120        img({-src => '/icons/HandPointing.gif', -align => 'LEFT'}),
121      h2('Step 3:','Cast your Ballot'),
122      blockquote(b(submit('VOTE')));
123  }


124  ###############################################################
125  # validate() validates the ballot to discourage fraud
126  ###############################################################
127  sub validate {
128      # first check that the voter registration field is filled out
129      return error('The voter registration ID field is missing.')
130        unless param('registration_id') || param('registration_file');

131      # check that the voter has voted for at least one office
132      return error('The ballot has not been filled out.')
133        unless grep {param($_) ne 'Write in:' || param("writein $_")} 0..@OFFICES-1;

134      # check that no office has more than one vote
135      for (0..@OFFICES-1) {
136          my @votes = param($_);
137          return error("You have voted for $OFFICES[$_] more than once.") if @votes > 1;
138      }

139      # recover the registration ID
140      my $registration_id;
141      if (my $fh = param('registration_file')) {
142          while (<$fh>) {
143              chomp;
144              next unless /--REGISTRATION-START--/../--REGISTRATION-END--/;
145              next unless /^\d+$/;
146              $registration_id .= $_;
147          }
148      }
149      $registration_id ||= param('registration_id');
150      $registration_id =~ s/\D//g;  # get rid of all non-digits
151      return error('Your registration code is the incorrect length.')
152        unless length $registration_id == REGISTRATION_CODE_LENGTH;

153      # check that this is a registered voter
154      my $sth = $DB->prepare('SELECT registration_used FROM registration WHERE registration_id=?')
155        or die "prepare registration: ",$DB->errstr;
156      my $rows = $sth->execute($registration_id);
157      return error("The registration code provided is not on the list of eligible voters.")
158        unless $rows > 0;

159      # check that registration ID has not already been used
160      my ($used) = $sth->fetchrow_array;
161      return error("That voter registration code has already been used.")
162        unless $used == 0;
163      $sth->finish;

164      return $registration_id;
165  }

166  ###############################################################
167  # enter_ballot() updates the database
168  ###############################################################
169  sub enter_ballot {
170      my $registration = shift;

171      # lock this registration number so that it can't be used again
172      $DB->do("UPDATE registration SET registration_used=1
173               WHERE registration_id='$registration'
174               AND registration_used=0")>0
175        or die "Can't update registration: ",$DB->errstr;

176      # generate a ballot ID
177      my $id = random_digits(100);

178      # prepare the SQL for regular and write-in votes
179      my $regular_vote = $DB->prepare("INSERT INTO tally VALUES('$id',?,?,NULL)")
180        or die "Can't prepare: ",$DB->errstr;

181      my $writein_vote = $DB->prepare("INSERT into writein VALUES('$id',?,?,NULL)")
182        or die "Can't prepare: ",$DB->errstr;

183      # begin user confirmation
184      print h2('Save this Information for your Records');
185      print start_dl;
186      for my $office (0..$#OFFICES) {
187          my $selection      = param($office);
188          my $writein        = param("writein $office");
189          my $candidate_name = $writein || $CANDIDATE_NAME[$selection] || '-none-';

190          # update database with the candidate's vote
191          if ($writein) {
192              $writein_vote->execute($office+1,$writein)   or die "can't update tally: ", $DB->errstr;
193          } elsif ($selection) {
194              $regular_vote->execute($office+1,$selection) or die "can't update tally: ", $DB->errstr;
195          }

196          # update confirmation page
197          print dt(b($OFFICES[$office])),dd($candidate_name);
198      }
199      print end_dl;
200      $writein_vote->finish;
201      $regular_vote->finish;

202      # show user his confirmation number
203      $id =~ s/(.{50})/$1\n/;
204      print h3('Ballot Confirmation Number'),pre($id);
205  }

206  ###############################################################
207  # utilities
208  ###############################################################

209  # generate some random digits for the ID
210  sub random_digits {
211      my $digits_desired = shift;
212      open(RAND, '/dev/urandom') or die "Can't open random number device: $!";
213      my $data;
214      read(RAND,$data,$digits_desired) or die "Can't read random bytes: $!";
215      my @digits = map {$_ % 10} unpack('C*',$data);
216      return join '', @digits[0..$digits_desired-1];
217  }

218  # all-purpose error message
219  sub error {
220      print p(font({-size=>'+2',-color=>'red'}, @_, br,
221                   'Please correct and try again.'));
222      return;
223  }

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.