75909de3ed
To fix bug 629334. On the way, some old .cvsignore files were deleted. Also, Makefiles and headers were adapted to show the new URL. Calendars have not been updated.
246 lines
6.4 KiB
Python
246 lines
6.4 KiB
Python
#! /usr/bin/python
|
|
|
|
import re
|
|
import sys
|
|
import string
|
|
import md5
|
|
|
|
class Ballot:
|
|
def __init__ (self):
|
|
self.email = 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 = [ \
|
|
("JONATHAN BLANDFORD", 1), \
|
|
("CHEMA CELORIO", 2), \
|
|
("RHETT CREIGHTON", 3), \
|
|
("MIGUEL DE ICAZA", 4), \
|
|
("GLYNN FOSTER", 5), \
|
|
("NAT FRIEDMAN", 6), \
|
|
("JIM GETTYS", 7), \
|
|
("JODY GOLDBERG", 8), \
|
|
("TELSA GWYNNE", 9), \
|
|
("BILL HANEMAN", 10), \
|
|
("JAMES HENSTRIDGE", 11), \
|
|
("GEORGE LEBL", 12), \
|
|
("CHRIS LYTTLE", 13), \
|
|
("IAN MCKELLAR", 14), \
|
|
("MICHAEL MEEKS", 15), \
|
|
("FEDERICO MENA-QUINTERO", 16), \
|
|
("TIM NEY", 17), \
|
|
("BASTIEN NOCERA", 18), \
|
|
("HAVOC PENNINGTON", 19), \
|
|
("CHRIS PHELPS", 20), \
|
|
("ARIEL RIOS", 21), \
|
|
("RICHARD STALLMAN", 22), \
|
|
("ANDY TAI", 23), \
|
|
("DANIEL VEILLARD", 24), \
|
|
("JEFF WAUGH", 25) ]
|
|
|
|
for c in candidate_tuples:
|
|
cand = Candidate (c[0], c[1])
|
|
candidates[cand.id] = cand
|
|
|
|
from_line_re = re.compile ("^From: *(.*)")
|
|
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_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
|
|
m = md5.new (key)
|
|
digest = m.digest ()
|
|
# convert to hex, python 2.0 has hexdigest() but this one I'm using
|
|
# apparently does not
|
|
token = ""
|
|
for num in digest:
|
|
token = token + ("%02x" % (ord(num),))
|
|
if token == b.token:
|
|
if dup_tokens.has_key (token):
|
|
print "Auth token occurs twice, someone voted more than once"
|
|
return 0
|
|
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) > 11:
|
|
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
|
|
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)
|
|
|
|
|
|
|
|
|
|
|