Mercurial > hg > elinux-vote
changeset 0:ba44dc878db8
Initial version
author | "German Poo-Caaman~o <gpoo@gnome.org>" |
---|---|
date | Thu, 07 Dec 2006 10:33:46 -0300 |
parents | |
children | 910b2909e680 |
files | .hgignore list-addresses.py mail-ballots.pl vote-counter.py |
diffstat | 4 files changed, 398 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Thu Dec 07 10:33:46 2006 -0300 @@ -0,0 +1,8 @@ +# use glob syntax. +syntax: glob +*~ +.*.swp +*.pyc + +syntax: regexp +.*\#.*\#$
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/list-addresses.py Thu Dec 07 10:33:46 2006 -0300 @@ -0,0 +1,42 @@ +#! /usr/bin/python + +import re +import sys +import string + +nospam_re = re.compile ("no_spam\.?") + +def unmunge_email (addr): + unmunged = nospam_re.sub ("", addr) + return unmunged + + +comment_re = re.compile ("^#.*") +entry_re = re.compile (" *(.*?)(<.*?>) *\((.*?)\) *") + +filename = sys.argv[1] + +handle = open (filename) + +lines = handle.readlines () + +count = 0 + +for line in lines: + line = comment_re.sub ("", line) + string.strip (line) + if line == "" or line == "\n": + continue + + match = entry_re.search (line) + if match: + name = string.strip (match.group (1)) + email = unmunge_email (string.strip (match.group (2))) + contribution = string.strip (match.group (3)) + count = count + 1 + print email + else: + print "No match: " + line + +handle.close () +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mail-ballots.pl Thu Dec 07 10:33:46 2006 -0300 @@ -0,0 +1,108 @@ +#!/usr/bin/perl + +# Script to send ballots to all voters. +# +# How to use this script +# ====================== +# +# You probably want to first update the subject of the e-mail that will be +# sent. Look for "Subject:" in this file and update the subject. +# +# You should know the secret string used to build the validation token for the +# current elections/referendum. Let's suppose it's "secretstring". +# +# Let's also suppose that the ballot is in ballot.txt and that you made a list +# of voters in voters.txt (the format of this file should be the same as the +# one used in membership_new.txt). +# +# You should use this script like this: +# $ ./mail-ballots.pl voters.txt ballot.txt "secretstring" +# +# This script needs a MTA to send the e-mails. If you don't have one or if +# you're not sure that your ISP allows you to directly send mails, it's +# probably better and safer to run the script from a gnome.org server. +# +# You may want to look at your mail server logs (and maybe keep them) to +# know if the mail was delivered. There are usually 10-15 errors. In case of +# such errors, you can try to look for the new e-mail addresses of the voters +# to ask them if they want to update their registered e-mail address and +# receive their ballot. + +use Digest::MD5 qw (md5_hex); +use MD5; +use Mail::Internet; + +die "Usage: mail-ballots.pl <recipient list> <ballot template> <secret string>\n" + unless $#ARGV == 2; + +my $SECRET_STRING = $ARGV[2]; + +open BALLOT, "<$ARGV[1]" || die "Cannot open ballot file $ARGV: $!"; +my @ballot = <BALLOT>; +close BALLOT; + +for (my $i = 0; $i <= $#ballot; $i++) { + push @dear_indexes, $i if $ballot[$i] =~ /^Estimado\(a\) <member>,/; + push @identity_indexes, $i if $ballot[$i] =~ /^\s*Member:/; + push @addr_indexes, $i if $ballot[$i] =~ /^\s*Member Address:/; + push @token_indexes, $i if $ballot[$i] =~ /^\s*Validation Token:/; +} + +my $head = Mail::Header->new ( + [ "From: Eleccion de sede Encuentro Linux <voto\@encuentrolinux.cl>", + "Subject: Balota oficial de seleccion de sede Encuentro Linux 2008", + "Reply-To: voto\@encuentrolinux.cl", + "Sender: German Poo Caaman~o <gpoo\@ubiobio.cl>" + ]); + +open RECIPS, "<$ARGV[0]" || die "Cannot open file $ARGV: $!"; + +my $sent = 0; +my $errors = 0; + +while (<RECIPS>) { + chomp; + next if (/^\#/ || /^$/); + + #if (!(/^ *(.*)\/(.*)\@no_spam\.(.*)\/.*\/.* *$/)) { + if (!(/^ *(.*) (<.*)\@no_spam\.(.*>).*$/)) { + print "Error for line: $_\n"; + exit; + next; + } + my $identity = $1; + my $addr = "$2\@$3"; + #my $hash = MD5->hexhash ("$identity $addr", $SECRET_STRING); + #print "$identity $addr-$SECRET_STRING: $hash\n"; + my $hash = MD5->hexhash ("$addr", $SECRET_STRING); + print "$addr-$SECRET_STRING: $hash\n"; + + foreach $index (@dear_indexes) { + $ballot[$index] = "Estimado(a) $identity,\n"; + } + + foreach $index (@identity_indexes) { + $ballot[$index] = "Member: $identity\n"; + } + + foreach $index (@addr_indexes) { + $ballot[$index] = "Member Address: $addr\n"; + } + + foreach $index (@token_indexes) { + $ballot[$index] = "Validation Token: $hash\n"; + } + + $head->replace ("To", $addr); + my $mail = Mail::Internet->new (Header => $head, Body => \@ballot); + unless ($mail->smtpsend (MailFrom => "voto-owner\@encuentrolinux.cl")) { + print "Error: Could not send to $addr ($identity)!\n"; + $errors++; + } else { + $sent++; + } +} + +close RECIPS; + +print "Mailed $sent ballots; $errors could not be mailed.\n";
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vote-counter.py Thu Dec 07 10:33:46 2006 -0300 @@ -0,0 +1,240 @@ +#! /usr/bin/python + +import re +import sys +import string +import md5 + +MAX_CANDIDATES = 1 + +class Ballot: + def __init__ (self): + self.email = 0 + self.member_name = 0 + self.member = 0 + self.token = 0 + self.votes = [] + + def add_vote (self, name, id): + self.votes.append ((name, id)) + +class Candidate: + def __init__ (self, name, id): + self.name = name + self.id = id + self.count = 0 + self.voters = [] + +candidates = {} + +candidate_tuples = [ \ + ("UNIVERSIDAD TECNICA FEDERICO SANTA MARIA", 1), \ + ("UNIVERSIDAD DE CONCEPCION", 2) ] + +for c in candidate_tuples: + cand = Candidate (c[0], c[1]) + candidates[cand.id] = cand + +from_line_re = re.compile ("^From: *(.*)") +member_name_re = re.compile (">? *Member: *(.*)") +member_address_re = re.compile (">? *Member Address: *([^ ]*)") +auth_token_re = re.compile (">? *Validation Token: *(.*)") +vote_re = re.compile (">? *([A-Z ]+) *\(ID# *([0-9]+)\)") + +ballots = [] +current_ballot = 0 + +filename = sys.argv[1] # mail archive file +secret_cookie = sys.argv[2] # secret cookie +voter_list = sys.argv[3] # list of valid voter addresses + +# hash from valid addresses to whether they have sent in a ballot yet +valid_addresses = {} + +voter_handle = open (voter_list) +for voter_addr in voter_handle.readlines (): + valid_addresses[string.strip (voter_addr)] = 0 + +handle = open (filename) +lines = handle.readlines () +for line in lines: + + match = from_line_re.match (line) + if match: + email = string.strip (match.group (1)) + if current_ballot: + ballots.append (current_ballot) + current_ballot = Ballot () + current_ballot.email = email + + continue + + match = member_name_re.match (line) + if match: + member = string.strip (match.group (1)) + if (current_ballot.member_name): + print "Duplicate member name in ballot from '%s' - duplicates ''%s', '%s'" \ + % (current_ballot.email, current_ballot.member_name, member) + else: + current_ballot.member_name = member + + continue + + match = member_address_re.match (line) + if match: + member = string.strip (match.group (1)) + if (current_ballot.member): + print "Duplicate member address in ballot from '%s' - duplicates ''%s', '%s'" \ + % (current_ballot.email, current_ballot.member, member) + else: + current_ballot.member = member + + continue + + match = auth_token_re.match (line) + if match: + token = string.strip (match.group (1)) + if (current_ballot.token): + print "Duplicate auth token in ballot from '%s' - duplicates '%s', '%s'" \ + % (current_ballot.email, current_ballot.token, token) + else: + current_ballot.token = token + + continue + + match = vote_re.match (line) + if match: + name = string.strip (match.group (1)) + id = string.strip (match.group (2)) + + id = int(id) + + if not candidates.has_key (id): + print "Unknown candidate '%s' ID %d in ballot from '%s'" % (name, id, current_ballot.email) + elif not candidates[id].name == name: + print "Candidate name '%s' for ID '%s' doesn't match, expected '%s'" % (name, id, candidates[id].name) + else: + current_ballot.add_vote (name, id) + + continue + +if current_ballot: + ballots.append (current_ballot) + +handle.close () + +def contains_dups (b): + dups = {} + for v in b.votes: + id = v[1] + if dups.has_key (id): + return 1 + dups[id] = 1 + return 0 + +dup_tokens = {} +def md5_is_bad (b): + #key = b.member + secret_cookie + key = "%s %s%s" % (b.member_name, b.member, secret_cookie) + m = md5.new (key) + digest = m.digest () + token = m.hexdigest() + if token == b.token: + if dup_tokens.has_key (token): + print "Auth token occurs twice, someone voted more than once" + return 1 + else: + dup_tokens[token] = 1 + return 0 + else: + print "Bad auth token is %s hashed from '%s'" % (token, key) + return 1 + +def valid_voter (addr): + return valid_addresses.has_key (addr) + +valid_ballots = {} + +i = 0 +for b in ballots: + error = 0 + if not b.member: + error = "missing member address" + elif not b.token: + error = "missing auth token" + elif len (b.votes) > MAX_CANDIDATES: + error = "too many votes (%d votes)" % len (b.votes) + elif len (b.votes) == 0: + error = "didn't list any candidates" + elif contains_dups (b): + error = "contains duplicate votes for the same candidate" + elif md5_is_bad (b): + error = "bad authentication token" + elif not valid_voter (b.member): + error = "ballot from someone not on the list of valid voters" + else: + if valid_ballots.has_key (b.token): + old = valid_ballots[b.token] + print "Overriding previous valid ballot %d from %s with new ballot %d" %\ + (old[1], old[0].email, i) + valid_ballots[b.token] = (b, i) + + if error: + print "Ignoring ballot %d from '%s' due to: %s" % (i, b.email, error) + + i = i + 1 + +def tupcmp (a, b): + return cmp (a[1], b[1]) + +## Print results only after all errors have been printed, so +## we don't lose any errors. +valids = valid_ballots.values () +valids.sort (tupcmp) +for (b, i) in valids: + print "Ballot %d:" % i + + print " From: " + b.email + print " Member: " + b.member_name + print " Member Address: " + b.member + print " Token: " + b.token + print " Voted for %d candidates:" % len (b.votes) + + voted_for = [] + + valid_addresses[b.member] = 1 + + for v in b.votes: + id = v[1] + candidates[id].count = candidates[id].count + 1 + candidates[id].voters.append (b.member) + voted_for.append (candidates[id].name) + + for v in voted_for: + print " " + v + +print "The following members did not vote:" +for addr in valid_addresses.keys (): + if not valid_addresses[addr]: + print addr + +def cmpcand (a, b): + return cmp (a.count, b.count) + +cand_list = candidates.values () +cand_list.sort (cmpcand) + +print "" +print "" +print "ELECTION RESULTS:" + +print " %d of %d members cast a valid ballot" \ + % (len (valids), len (valid_addresses.keys())) + +for c in cand_list: + print " %s (%d votes)" % (c.name, c.count) + + + + +