Article 3332 of comp.lang.perl:
Xref: feenix.metronet.com comp.org.usenix:611 comp.lang.perl:3332 comp.org.ieee:422
Newsgroups: comp.org.usenix,comp.lang.perl,comp.org.ieee,comp.org.uniforum
Path: feenix.metronet.com!news.utdallas.edu!tamsun.tamu.edu!cs.utexas.edu!sdd.hp.com!col.hp.com!csn!teal.csn.org!jsh
From:
[email protected] (Jeffrey Haemer)
Subject: Electronic Balloting
Message-ID: <
[email protected]>
Sender:
[email protected] (The Daily Planet)
Nntp-Posting-Host: teal.csn.org
Organization: Colorado SuperNet, Inc.
Date: Fri, 11 Jun 1993 02:13:30 GMT
Lines: 637
Folks,
A few weeks back, we posted a note asking for your advice and aid
in prototyping software for electronic standards balloting. We
got it. Thanks. Now we want some more.
Here's a shar with a first cut at some balloting software. Unshar
it, read the README, and the rest should be self-explanatory.
We're looking for advice, bugs, bug-fixes, enhancements, and users.
The shar includes our original post, explaining our goal in more detail.
Jeffrey S. Haemer, USENIX Standards Liaiason <
[email protected]>
Pat Wilson, SAGE Board Member <
[email protected]>
========
#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of archive 1 (of 1)."
# Contents: Environ.pl Mail.pl README ballot balloting.mm vote
# Wrapped by
[email protected] on Fri Jun 4 14:11:16 1993
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'Environ.pl' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'Environ.pl'\"
else
echo shar: Extracting \"'Environ.pl'\" \(1773 characters\)
sed "s/^X//" >'Environ.pl' <<'END_OF_FILE'
X# $Id: Environ.pl,v 3.0 1993/06/04 19:53:34 ballot Exp ballot $
X
Xpackage Environ;
X# print the environment
Xsub 'printenv {
X foreach (sort keys %ENV) {
X print "$_=$ENV{$_}\n";
X }
X}
X
X# set environment variables
X# override and add to the values in ENV
X# with values specified in a resource file.
X# The resource file is specified by RESOURCE
Xsub 'setenv {
X local(@path) = split(m%/%, $0);
X local($cmd) = pop(@path);
X %ENV = &'get_arr($cmd, %ENV) unless ($'setenv'guard++)
X}
X
X# fill an array from a specified file
X# Use the first argument as the name of an environment variable
X# that points at the keyfile.
X# If that fails, try a file by the name of arg1
X# Use the second argument as an array of default values
X#
X# See setenv() as an example.
X#
X# BUGS:
X# I don't get why I can't combine the lines marked with "?".
X#
Xsub 'get_arr {
X local($keyfile) = shift(@_);
X local (%arr) = @_;
X open(KEYS, $ENV{$keyfile} = $ENV{$keyfile} ? $ENV{$keyfile} : ("$keyfile.res") ) || return %arr;
X while (<KEYS>) {
X chop;
X ($name, $other) = split(' ', $_, 2); # ?
X $arr{$name} = $other; # ?
X }
X close(KEYS) || die "can't close $keyfile: $!, aborting\n";
X return %arr;
X}
X
X# Test routine. Invoke it in a driver with
X# require "Environ.pl";
X# &Environ'test;
Xsub test {
X &'setenv;
X %arr = &'get_arr("foo", %ENV);
X die "get_arr failed to get ENV\n" unless $arr{HOME} = $ENV{HOME};
X $arr{HOME} = NULL;
X
X open(IN, "foo.res");
X print IN "HOME\tBOGUS\n";
X close(IN);
X %arr = &'get_arr("foo", %ENV);
X die "getarr failed to read foo.res\n" unless $arr{HOME} = "BOGUS";
X $arr{HOME} = NULL;
X
X $ENV{"bar"} = "foo.res";
X %arr = &'get_arr("bar", %ENV);
X die "getarr failed to read ENV{bar}\n" unless $arr{HOME} = "BOGUS";
X $arr{HOME} = NULL;
X
X unlink("foo.res");
X print "Package tests okay.\n";
X}
X
X1;
END_OF_FILE
if test 1773 -ne `wc -c <'Environ.pl'`; then
echo shar: \"'Environ.pl'\" unpacked with wrong size!
fi
chmod +x 'Environ.pl'
# end of 'Environ.pl'
fi
if test -f 'Mail.pl' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'Mail.pl'\"
else
echo shar: Extracting \"'Mail.pl'\" \(2375 characters\)
sed "s/^X//" >'Mail.pl' <<'END_OF_FILE'
X# $Id: Mail.pl,v 3.0 1993/06/04 19:53:34 ballot Exp ballot $
X
Xrequire "Environ.pl";
X
Xpackage Mail;
X
X@MAILER = ("/usr/bin/mailx", "/bin/mail", "/usr/bin/mail", "/usr/ucb/Mail");
X
X# get key lines
X# Return each key value
X# in the current item in an associative array.
X# keylines are specified by regular expressions
X# in a file named by the second argument,
X# default expressions are supplied in the third.
Xsub 'getkeys {
X
X local($_, $keyfile, %defaults) = @_;
X
X %keyline = &'get_arr($keyfile, %defaults) unless ($getkeys'guard++);
X local(%info);
X local($*) = 1;
X for $k (keys %keyline) {
X $info{$k} = $1 if (/$keyline{$k}/);
X }
X return %info;
X}
X
X# get all the mail messages
X# Toss the entire mail file into an array, one element per message.
X# Return the array
Xsub 'getmsgs {
X open(INBOX, $_[0]) || die "can't open $_[0]: $!, aborting\n";
X local($/) = '';
X @msg = ();
X $msg = '';
X while(<INBOX>) {
X if (/^From /) {
X push(@msg, $msg) if $msg;
X $msg = '';
X }
X $msg .= $_;
X }
X push (@msg, $msg) if $msg;
X return @msg;
X}
X
X# mail a message
X# just a call to the native mail program
X#
X# BUGS: I worry a bit about the diversity of mail programs
Xsub 'mailx {
X die "bad call to mailx\n" unless @_ > 2; # try assert
X local($sub, $to, @msg) = @_;
X ($ENV{MAILER}) = grep(-x, @MAILER) unless $ENV{MAILER};
X $cmd = "| $ENV{MAILER} -s '$sub' $to";
X open(ACKMAIL, $cmd) || die "can't open $cmd: $!, aborting\n";
X print ACKMAIL @msg;
X close(ACKMAIL) || die "can't close $cmd: $!, aborting\n";
X}
X
X# Test routine. Invoke it in a driver with
X# require "Mail.pl";
X# &Mail'test;
X
X# default values. %KEYLINES is probably the wrong set
X@MAILDIR = ("/var/spool/mail", "/var/mail", "/usr/spool/mail");
X%KEYLINE = (
X 'Checksum', 'Checksum:\s*(\d+\s*\d+)',
X 'From', '^From:\s*(.*)',
X 'Author', 'Author:\s*(.*)',
X 'ID', 'ID:\s*(\d+)',
X 'Subject', '^Subject:\s*(.*)',
X 'Vote', 'Vote:\s*(.+)',
X );
X
Xsub test {
X setpwent;
X ($name) = getpwuid($>); # I dunno, why not?
X endpwent;
X &'mailx("Zzazz", $name, "This is the first line\nThis is the second\n");
X sleep 10;
X ($maildir) = grep(-e, @MAILDIR);
X @msgs = &'getmsgs("$maildir/$name");
X $newmsg = pop(@msgs);
X %info = &'getkeys($newmsg, "keyline", %KEYLINE);
X
X die "Bad subject line $info{Subject}\n" if "Zzazz" != $info{Subject};
X die "Bad recipient line $info{To}\n" if "Zzazz" != $info{To};
X print "Package tests okay.\n";
X}
X
X1;
END_OF_FILE
if test 2375 -ne `wc -c <'Mail.pl'`; then
echo shar: \"'Mail.pl'\" unpacked with wrong size!
fi
# end of 'Mail.pl'
fi
if test -f 'README' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(4209 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
X# $Id: README,v 3.0 1993/06/04 19:53:34 ballot Exp ballot $
X
XThis is a cut at balloting software.
X
X
X INSTALLATION
X
XThe system receiving the votes should have a special id to process
Xthe votes, and votes should be sent to that id (e.g.
X<
[email protected]>).
X
XYou _must_ create an initial, two-column file of valid voters,
X"ids.res". Each voter needs an id, and must know that id to vote.
X(The format of the file is "ID Votername".) For IEEE standards
Xballots, for example, you might want to make the list be a list of
XIEEE members and their IEEE membership numbers.
X
XThe system has reasonable defaults, but it's fairly configurable;
Xit lets you use external files to specify what kinds of votes can
Xbe cast, where the mailboxes are, and all sorts of other things.
XUnfortunately, you currently have to read the code to understand
Xhow to configure it.
X
XCurrently, the scripts point at /usr/bin/perl. If your system has
Xperl someplace else, you'll get non-informative error messages.
X
X
X WHAT IT DOES
X
XVoters run "vote" to cast ballots. The recipient of the votes runs
X"ballot" to count ballots.
X
X"vote" insures that the ballot has all the right fields,
Xthen tags a checksum on the end.
X
X"ballot" runs through the incoming mailbox and sorts the votes into
Xbins. In doing so, it validates the balloter and the checksum,
Xand sends the sender an acknowledgment of the receipt of the vote.
X
X
X WHAT IT DOESN'T DO: KNOWN PROBLEMS/PROJECTS
X
XThere should be a good configuration and installation script.
X
XCurrently, the scripts point at /usr/bin/perl. If your system has
Xperl someplace else, you'll get non-informative error messages.
XThis could probably be fixed with a good configuration and
Xinstallation script.
X
XOther software that needs to be written are a utility for tallying
Xvotes and a utility for processing invalid votes. We have contributed
Xsoftware that parses valid votes once they're binned, but it's not yet
Xready to post.
X
XThe "ids.res" file (and its processing) could be more sophisticated.
XIn particular, if it contained an email address, we could have a
Xdaemon periodically run through the file and send mail noodging
Xthose who haven't yet voted.
X
XKnown problems awaiting future solution are indicated in comments
Xthe code. Those problems should probably be listed here instead,
Xor at least duplicated here.
X
XCurrently, the first vote from a voter that shows up in the mailbox
Xis the one that counts. This has two shortcomings. First, if
Xthe messages arrive out-of-order, this isn't the right choice.
XSecond, we might want to allow people to change their minds, in
Xwhich case we should probably use the _last_ message that arrives.
XPerhaps this should be a configuration or command-line option.
X
XThere should be a man page that doesn't make you read the code to
Xfigure out how to configure things.
X
X"ballot" doesn't make any effort to lock the input mailbox. That's
Xbecause I don't really know how to do this. It also doesn't do
Xvery fancy security, for the same reason. (Currently, "vote" appends
Xa simple checksum to the message which is double-checked by "ballot",
Xand then returned to the sender so that the sender can be certain that
Xthe checksum received was the one sent.)
X
XBecause different groups may want different security levels or
Xalgorithms, perhaps the security/checksumming should be done by a
Xseparate executable that "vote" and "ballot" call as a subprocess.
X
X
X PHILOSOPHICAL OBSERVATION
X
XIt is arguably ironic that this balloting software, initially
Xdesigned to help ballot standards documents, is in a non-standardized
Xlanguage. I suspect that the fastest way to whip this suite into
Xshape is to ask that everyone who chooses to comment on this amusing
Xirony accompany the comment with an improvement to the software.
X(Figure, it's either that or we create a group to standardize perl ... :-)
X
X
X FILES
X
XEnvPkg.pl A package to get and set environment variables and resources
XMailPkg.pl A package that handles the mail messages
XREADME This file
Xballot Sorts mail file into bins of each kind of vote
Xids.res A two-column list of folks yet to vote: ID Name
Xvote script to generate a vote
Xvoted.res Folks who have already voted, same format as "ids"
END_OF_FILE
if test 4209 -ne `wc -c <'README'`; then
echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'ballot' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'ballot'\"
else
echo shar: Extracting \"'ballot'\" \(4819 characters\)
sed "s/^X//" >'ballot' <<'END_OF_FILE'
X#!/usr/bin/perl
X# $Id: ballot,v 3.0 1993/06/04 19:53:34 ballot Exp ballot $
X
Xrequire "Mail.pl";
X
X@MAILDIRS = ("/var/spool/mail", "/var/mail", "/usr/spool/mail");
X%CHOICES = (
X YES, "yes",
X NO, "no",
X ABSTAIN, "abstain",
X);
X
X%KEYLINE = (
X 'Checksum', 'Checksum:\s*(\d+\s*\d+)',
X 'From', '^From:\s*(.*)',
X 'Author', 'Author:\s*(.*)',
X 'ID', 'ID:\s*(\d+)',
X 'Subject', '^Subject:\s*(.*)',
X 'Vote', 'Vote:\s*(.+)',
X );
X
X# acknowledge a message
X# parse out the sender,
X# return an ack with enough information to let the recipient know
X# what we actually got.
Xsub ack {
X local($sub) = "Vote received";
X local($to) = $Keyline{From};
X local(@ack) = (
X "Received vote: $Keyline{Vote}\n",
X "Voter: $Keyline{Author}\n",
X "ID: $Keyline{ID}\n",
X "Checksum: $Keyline{Checksum}\n",
X "Msg subject: $Keyline{Subject}\n"
X );
X
X # optional "To" line formats
X if ($to =~ /<(\S*)>/) {
X $to = $1;
X } elsif ($to =~ /(\S*)\s*\(.*\)/) {
X $to = $1;
X } else {
X return 0;
X }
X &mailx($sub, $to, @ack);
X 1;
X}
X
X# 'bin' an individual message
X# Make sure the vote's legit and acknowledge it,
X# then toss the message into the bin
X# indicated in the message's "Vote:" field.
Xsub bin {
X local($msg) = @_[0];
X %Keyline = &'getkeys($msg, "keyline", %KEYLINE);
X local($vote) = $Keyline{Vote};
X foreach (keys %Choices) {
X $vote = $_, last if $vote =~ /$Choices{$_}/i
X }
X $vote = BAD unless (&valid($msg) && $vote && &ack());
X $tally{$vote}++;
X print $vote $msg;
X}
X
X# check out the checksum
X# Right now, expects messages to end with the two lines
X# Checksum: nnnn nnnn
X#
X# where the numbers come from running cksum
X# on the rest of the message body
X#
X# BUGS: Okay, okay, this isn't any good.
X# Still, little point wasting time here
X# until I know what the right thing to do is.
Xsub cksum {
X local($msg) = @_[0];
X return 1;
X # $*=1;
X @text = split(/\n\n/, $msg); # split out the header
X shift(@text); # and dump it
X @text = split(/^Checksum: /, join(' ', @text)); # now the get the checksum
X $checksum = pop(@text);
X $checksum = unpack("%32C*", join(' ', @text));
X}
X
X# clean up
X# print out the tally;
X# rewrite the "ids" (yet-to-vote) and "voted" files;
X# finally close open files
X# removing any that got created but never used
Xsub clean_up {
X &tally(%tally);
X open(IDS, "> $ENV{'ids'}") || die "can't open $ENV{ids} for write: $!, aborting\n";
X open(VOTED, ">> $ENV{'voted'}") || die "can't open $ENV{voted} for append: $!, aborting\n";
X foreach (keys %Ids) {
X print { $Voted{$_} ? VOTED : IDS } "$_\t$Ids{$_}\n";
X }
X close(IDS) || die "can't close $ENV{ids}: $!, aborting\n";
X close(VOTED) || die "can't close $ENV{voted}: $!, aborting\n";
X close(BAD) || die "can't close BAD: $!, aborting";
X foreach (BAD, $ENV{"ids"}, $ENV{"voted"}) {
X unlink $_ if -z $_;
X }
X
X unlink("BAD") if -z "BAD";
X foreach (keys %Choices) {
X close($_) || die "can't close $_: $!, aborting";
X unlink $_ if -z $_;
X }
X}
X
X# all the initialization
X# open up the input mailbox and the output mailboxes.
X# note that votes are cumulative
X#
X# BUGS: needs to lock the input mailbox,
X# then move it out of the way so ballots aren't counted twice
Xsub initialize {
X &setenv;
X die "No valid voters\n" unless %Ids = &get_arr("ids");
X local(%voted) = &get_arr("voted");
X foreach (keys %voted) {
X $Voted{$_}++;
X }
X
X ($Maildir) = grep(-e, @MAILDIRS);
X ($Name) = getpwuid($>); # I dunno, why not?
X $ENV{BALLOT_BOX} = $ENV{BALLOT_BOX} || "$Maildir/$Name";
X $ENV{"voted"} = $ENV{"voted"} || "voted.res";
X die "No votes\n" if ( -z $ENV{BALLOT_BOX} || ! -e $ENV{BALLOT_BOX} );
X %Choices = &get_arr("choices", %CHOICES);
X foreach (keys %Choices) {
X open($_, ">> $_") || die "can't open $_ for append: $!, aborting\n";
X }
X open(BAD, ">> BAD") || die "can't open BAD for append: $!, aborting\n";
X}
X
X# tally the votes
X# just print out the tallies from this batch of mail
X#
X# BUGS: This is just a debugging tool.
X# Tallying should be done by a separate process
X# and go directly to the tally bins.
Xsub tally {
X local(%tally) = @_;
X foreach (sort keys %tally) {
X print "$_=$tally{$_}\n";
X }
X}
X
X# validate the message
X# Read the file that matches IDs to voter names,
X# then check that the message "ID:" field has the right "Name:" field.
X# and that the checksum matches the message.
X# Don't allow ballot-box stuffing -- only vote once.
X#
X# BUGS: May need other checks.
X#
X# Assumes access to a global %Keyline array,
X# which is probably the wrong thing to do.
X# This should all be modularized and stuff.
Xsub valid {
X local($msg) = @_[0];
X ++$Voted{$Keyline{ID}} if
X ($Keyline{Author} eq $Ids{$Keyline{ID}})
X && &cksum($msg)
X && !$Voted{$Keyline{ID}};
X}
X
X# read mail messages from mailbox
X# and toss them one-by-one into an appropriate output mailbox
X
X&initialize;
X@msg = &getmsgs($ENV{BALLOT_BOX});
Xwhile($msg = shift(@msg)) {
X &bin($msg);
X}
X&clean_up;
END_OF_FILE
if test 4819 -ne `wc -c <'ballot'`; then
echo shar: \"'ballot'\" unpacked with wrong size!
fi
chmod +x 'ballot'
# end of 'ballot'
fi
if test -f 'balloting.mm' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'balloting.mm'\"
else
echo shar: Extracting \"'balloting.mm'\" \(1852 characters\)
sed "s/^X//" >'balloting.mm' <<'END_OF_FILE'
X.\" $Id: balloting.mm,v 1.1 1993/06/04 20:10:15 ballot Exp ballot $
X.\" Uses the mm macro ".PH", but that's all
X.PH "''''"
X.ce
XElectronic Balloting Software
X
XWhat if you could FTP POSIX draft standards and ballot on them from
Xthe comfort of your own keyboard? No more messy stamps or ink-stained
Xfingers! No need to wait on the Postal "Service" for the latest
Xdrafts!
X
XWe see a future in which draft standards will be available for
Xanonymous FTP, and balloting can be done by email. For a variety
Xof reasons -- some sensible, some merely historical -- achieving
Xeither of these advances requires overcoming both political and
Xtechnical barriers. We'd like to remove the technical barriers.
XAndrew Hume has already demonstrated a prototype solution for
Xelectronic draft distribution. We'd like to see a prototype for
Xelectronic balloting, and we'd like your help to make this happen.
X
XIt's envisioned that any electronic balloting procedure will need
X(a) authentication at least as good as Snail Mail provides and (b)
Xvote-counting software to make it as painless as possible.
X
XQuick-and-dirty authentication can be as simple as (1) mail the
Xballot-request software, which then (2) generates an encryption
Xkey and (3) sends it, via postal mail (wouldn't want them to feel
X_too_ left out!) to the requester, who then (4) uses the out-of-band
Xkey to encrypt the ballot, which is then (5) decrypted by the
Xballoting software and (6) counted appropriately -- or at least
Xthat's the plan.
X
XWhat's wrong with the plan? How can it be made better? How
Xinteresting can the vote-counting software get? Should it be
Xwritten in perl?
X
XPlease contribute your thoughts, code, etc., and help POSIX balloting
Xstep into the 1990s.
X
X
X.nf
X- Jeffrey S. Haemer, USENIX Standards Liaison <
[email protected]>
X- Pat Wilson, SAGE Board Member <
[email protected]>
X.fi
END_OF_FILE
if test 1852 -ne `wc -c <'balloting.mm'`; then
echo shar: \"'balloting.mm'\" unpacked with wrong size!
fi
# end of 'balloting.mm'
fi
if test -f 'vote' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'vote'\"
else
echo shar: Extracting \"'vote'\" \(836 characters\)
sed "s/^X//" >'vote' <<'END_OF_FILE'
X#!/usr/bin/perl
X# $Id: vote,v 3.0 1993/06/04 19:53:34 ballot Exp ballot $
X
X# send in a ballot
X# Tags a checksum on the end.
X#
X# BUGS:
X# The program should probably save the checksum,
X# or even the completed ballot, somewhere
X# so the user can verify that the acknowledgement
X# matches what was sent.
X# Right now, I use mail's "set record=" feature for this.
X
Xrequire "Mail.pl";
X
X@KEYWORDS = (Author, ID, Vote);
X
Xdie "Usage $0 ballot address\n" if (@ARGV != 2);
Xopen(BALLOT, $ARGV[0]);
Xwhile (<BALLOT>) {
X push (@msg, $_);
X foreach $k (@KEYWORDS) {
X $keywords{$k}++ if ($_ =~ /^$k/);
X }
X}
X
Xforeach $k (@KEYWORDS) {
X unless ($keywords{$k}) {
X print "$k? ";
X $_ = <>;
X redo if $_ =~ /^\s+$/;
X push(@msg, "$k: $_");
X next;
X }
X}
Xpush (@msg, "Checksum: " . unpack('%32C*', join('', @msg)) . "\n");
X
X&mailx("Vote", $ARGV[1], @msg);
END_OF_FILE
if test 836 -ne `wc -c <'vote'`; then
echo shar: \"'vote'\" unpacked with wrong size!
fi
chmod +x 'vote'
# end of 'vote'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
if test ! -f ark${I}isdone ; then
MISSING="${MISSING} ${I}"
fi
done
if test "${MISSING}" = "" ; then
echo You have the archive.
rm -f ark[1-9]isdone
else
echo You still need to unpack the following archives:
echo " " ${MISSING}
fi
## End of shell archive.
exit 0