/stump/etc/added-footer
/stump/etc/added-footer.new
/stump/etc/approval.key.txt
+/sballot/issues
/settings
/moderators
--- /dev/null
+
+package ModerationCommon;
+
+BEGIN {
+ use Exporter ();
+ our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+ $VERSION = 1.00;
+
+ @ISA = qw(Exporter);
+ @EXPORT = qw(hash $hashlen
+ readsettings %setting
+ sendmail_start sendmail_finish);
+ %EXPORT_TAGS = ( ); # eg: TAG => [ qw!name1 name2! ],
+
+ @EXPORT_OK = qw();
+}
+
+our $hashlen=32;
+
+sub hash ($) {
+ my $r= `echo $_[0] | sha256sum`; $? and die $?;
+ $r =~ s/ *\-$//;
+ chomp $r;
+ return $r;
+}
+
+sub sendmail_start () {
+ open ::P, "|sendmail -odb -oee -oi -t" or die $!;
+}
+sub sendmail_finish () {
+ $?=0; $!=0; close ::P or warn "$! $?";
+}
+
+our %setting;
+
+sub readsettingsfile ($) {
+ my ($file) = @_;
+ open SET, "<$file" or die "$file $!";
+ while (<SET>) {
+ next unless m/\S/;
+ next if m/^\#/;
+ m/^([A-Z_]+)\=(.*?)\s*$/ or die;
+ $setting{$1}= $2;
+ }
+}
+
+sub readsettings () {
+ readsettingsfile("../../global-settings");
+ readsettingsfile("../settings");
+}
+
+1;
--- /dev/null
+../sballot/cgi
\ No newline at end of file
--- /dev/null
+our $hashlen=32;
+
+sub hash ($) {
+ my $r= `echo $_[0] | sha256sum`; $? and die $?;
+ $r =~ s/ *\-$//;
+ chomp $r;
+ return $r;
+}
+
+
+1;
--- /dev/null
+#!/usr/bin/perl -w
+
+use strict qw(refs vars);
+
+use CGI qw/:standard/;
+use Cwd qw/realpath/;
+
+BEGIN {
+ my $self= $ENV{'SCRIPT_FILENAME'};
+ $self= $0 unless defined $self;
+ $self= realpath $self;
+ my $sballotdir= $self;
+ $sballotdir =~ s,/[^/]*$,,;
+
+ chdir $sballotdir or die "$sballotdir $!";
+ unshift @INC, "..";
+};
+
+use ModerationCommon;
+
+sub fail ($) {
+ my ($m)= @_;
+ print header(-status=>500), start_html('Secret ballot - error'),
+ h1("error"), strong($m), end_html();
+ exit 0;
+}
+
+my $issueid= param('issue');
+fail('bad issueid') if $issueid =~ m/[^-0-9a-z]/ or $issueid =~ m/^[^0-9a-z]/;
+
+open T, "issues/$issueid/title" or fail("unknown issue $!");
+my $title= <T>; chomp $title or die $!;
+my $regexp= <T>; chomp $regexp or die $!;
+close T or die $!;
+
+my $vote= param('vote');
+my $ident= param('ident');
+my $pw= param('password');
+
+sub read_vfile ($) {
+ my ($vfile)= @_;
+ open M, $vfile or fail("unknown psuedonym $!");
+ my $m= <M>; chomp $m or die $!;
+ close M or die $!;
+
+ $m =~ m/^(\S+) (\S.*)$/ or die;
+ return ($1,$2);
+}
+
+sub issue_and_title () {
+ return (
+ dt('Issue ID'), dd(escapeHTML($issueid)),
+ dt('Title'), dd(escapeHTML($title))
+ );
+}
+
+if (length $vote or length $ident or length $pw) {
+ fail('bad pseudonym') if !defined $ident or $ident =~ m/[^0-9a-z]/;
+
+ fail('bad password') if !defined $pw or $pw =~ m/[^0-9a-z]/;
+ my $pwhash= hash($pw);
+
+ fail('bad vote') if $vote =~ m/[^0-9a-z]/;
+ $vote =~ y/a-z/A-Z/;
+
+ fail("invalid vote - consult administrator's instructions")
+ unless $vote =~ /^(?:$regexp)$/io;
+
+ my $vfile= "issues/$issueid/v.$ident";
+ my ($exp_pwhash, $oldvote) = read_vfile($vfile);
+ $exp_pwhash eq $pwhash or fail("wrong password");
+
+ open N, "> $vfile.new" or die $!;
+ print N "$pwhash $vote\n" or die $!;
+ close N or die $!;
+
+ rename "$vfile.new", $vfile or die "$vfile $!";
+
+ print(header(), start_html('Secret ballot - vote recorded'),
+ h1('Vote recorded'), '<dl>',
+ issue_and_title(),
+ dt('Old vote'), dd($oldvote),
+ dt('New vote'), dd($vote), '</dl>',
+ end_html()) or die $!;
+ exit 0;
+}
+
+if (param('results') or param('email_results')) {
+ my $txt= <<END;
+The moderators' votes (so far) are as follows:
+END
+ foreach my $vfile (sort <issues/$issueid/v.*>) {
+ $vfile =~ m,/v\.([0-9a-f]+)([^/]*)$, or die;
+ next if $2 eq 'new';
+ die "$vfile $2" if length $2;
+ $ident= $1;
+ my ($dummy_pwhash, $vote) = read_vfile($vfile);
+ $txt .= " $ident $vote\n";
+ }
+ $txt .= <<END;
+
+See the email from the administrator for the meanings of the above
+votes. There is no automatic counting; the above is just a list
+of the entries provided by the voting moderators.
+END
+
+ if (param('email_results')) {
+ sendmail_start();
+ print P <<END or die $!;
+To: $setting{ABBREV} moderators <$setting{MODEMAIL}>
+Subject: Secret ballot results for $setting{ABBREV}
+
+One of the moderators for $setting{GROUP}
+has requested that the results of the following ballot be sent out:
+ Issue ID: $issueid
+ Title: $title
+
+$txt
+
+Regards
+moderation system robot
+END
+ print(header(), start_html('Secret ballot - email sent'),
+ h1('Done'),
+ p('The email has been sent and should arrive shortly'),
+ end_html())
+ or die $!;
+ exit 0;
+ }
+ print(header(), start_html('Secret ballot - results'),
+ h1('Results so far'),
+ '<dl>',issue_and_title(),'</dl>',
+ pre(escapeHTML($txt)),
+ end_html())
+ or die $!;
+ exit 0;
+}
+
+print(header(), start_html('Secret ballot - voting page'),
+ h1('Instructions'),
+ p('Wait for the email from the administrator confirming '.
+ 'that this is the actual live ballot before voting. '.
+ "The administrator's email will tell you what to put in".
+ " the vote box."),
+ h1('Voting form'), '<dl>',
+ start_form(-method=>'POST'),
+ hidden('issue',$issueid),
+ issue_and_title(),
+ dt('Pseudonym'), dd(textfield(-name=>'ident', -size=>($hashlen+10))),
+ dt('Password'), dd(textfield(-name=>'password', -size=>($hashlen+10))),
+ dt('Vote'), dd(textfield(-name=>'vote', -size=>40)),
+ '</dl>',
+ submit('Cast your vote'),
+ end_form(),
+ h1('Results'),
+ p('This allows you to view the results (so far)'),
+ start_form(-method=>'GET'), hidden('issue',$issueid),
+ p(submit(-name=>'results',
+ -value=>'Show results')),
+ p(submit(-name=>'email_results',
+ -value=>"Send results to moderators' list")),
+ end_form(),
+ end_html())
+ or die $!;
+
+exit 0;
--- /dev/null
+#!/usr/bin/perl -w
+#
+# usage: .../new-ballot '[YNA]' 'Remove Bad Bozo from the moderation panel'
+# ^ regexp, put into /^(?:$regexp)$/io
+
+use strict qw(refs vars);
+
+BEGIN {
+ my $sballotdir= $0; $sballotdir =~ s,/[^/]*$,,;
+ chdir $sballotdir or die "$sballotdir $!";
+ unshift @INC, '..';
+};
+
+use ModerationCommon;
+use POSIX;
+
+@ARGV==2 or die;
+$ARGV[0] !~ m/^-/ or die;
+my $regexp= shift @ARGV;
+my $title= shift @ARGV;
+
+readsettings();
+
+system "rm -rf issues/new";
+mkdir "issues" or $!==&EEXIST or die $!;
+mkdir "issues/new",0770 or die $!;
+
+stat "issues/new" or die $!;
+my $issueid= time.'-'.((stat _)[1]);
+my $issuedir= "issues/$issueid";
+
+rename "issues/new", $issuedir or die $!;
+
+open T, "> $issuedir/title" or die $!;
+print T $title, "\n" or die $!;
+print T $regexp, "\n" or die $!;
+close T or die $!;
+
+
+open S, "/dev/urandom" or die $!;
+
+sub randhex () {
+ my $nonce;
+ sysread(S, $nonce, $hashlen) == $hashlen or die $!;
+ return unpack "H*", $nonce;
+}
+
+my @mods;
+
+open M, "../moderators" or die $!;
+while (<M>) {
+ next unless m/\S/;
+ next if m/^\#/;
+ m/^([A-Z]+)\s+(\S+)\s*$/ or die;
+ my $m= { Name => $1, Email => $2 };
+ $m->{Nonce}= randhex(); $m->{Ident}= hash($m->{Nonce});
+ $m->{Password}= randhex(); $m->{HashedPw}= hash($m->{Password});
+ push @mods, $m;
+}
+close M or die $!;
+
+
+sendmail_start();
+print P <<END or warn $!;
+To: $setting{ABBREV} moderators <$setting{MODEMAIL}>
+Subject: Secret ballot initiated for $setting{ABBREV}
+
+The administrator of $setting{GROUP}
+has initiated a new secret ballot on the question:
+ Issue ID: $issueid
+ Title: $title
+
+Each moderator will be sent a private email telling them their
+pseudonym and voting details.
+
+There will also be an announcement from the administrator confirming
+that this is the live ballot (as if there are problems with the
+software, it may be necessary to initiate several) and explaining what
+to enter into the "vote" box on the voting page. Please do not vote
+until you've received that confirmation.
+
+The moderators who will be able to vote are the following people:
+END
+foreach my $m (@mods) {
+ my $opqe= $m->{Email};
+ $opqe =~ s/\@/ (at) /;
+ printf P " %-10s %s\n", $m->{Name}, $opqe or die $!;
+}
+
+print P <<END or die $!;
+If you are not listed, or your email address is wrong, or you do not
+receive the private login details email (which should arrive almost
+immediately), please say so as soon as possible.
+
+The pseudonyms which have been assigned are as follows:
+END
+
+@mods= sort { $a->{Ident} cmp $b->{Ident} } @mods;
+foreach my $m (@mods) {
+ my $vfile= "$issuedir/v.$m->{Ident}";
+ open V, "> $vfile" or die "$vfile $!";
+ print V "$m->{HashedPw} not voted\n" or die $!;
+ close V or die $!;
+ print P " ", $m->{Ident}, "\n" or die $!;
+}
+
+print P <<END or die $!;
+(the pseudonyms have been sorted into numerical order)
+
+Thanks for your attention.
+moderation system robot
+END
+
+sendmail_finish();
+
+foreach my $m (@mods) {
+ sendmail_start();
+ print P <<END or warn $!;
+To: $setting{ABBREV} moderator $m->{Name} <$m->{Email}>
+Subject: [$setting{ABBREV}] Secret ballot private info
+
+The administrator of $setting{GROUP}
+has initiated a new secret ballot on the question:
+ Issue ID: $issueid
+ Title: $title
+
+Your login details for voting are:
+ Pseudonym: $m->{Ident}
+ Password: $m->{Password}
+
+These are confidential to you, valid only for this particular vote,
+and cannot be regenerated if they are lost. So please keep this email
+but do not reveal it to anyone. Reveal the password only to the
+voting web page.
+
+There will also be an announcement from the administrator confirming
+that this is the live ballot (as if there are problems with the
+software, it may be necessary to initiate several) and explaining what
+to enter into the "vote" box on the voting page. Please do not vote
+until you've received that confirmation.
+
+You may then cast (and change) your vote here:
+ $setting{CGIBASEURL}/g.$setting{ABBREV}/sballot?issue=$issueid
+
+Thanks for your attention,
+moderation system robot
+
+Addendum for the paranoid:
+Your pseudonym was generated from a nonce, as follows:
+ echo $m->{Nonce} | sha256sum
+Only you know this nonce - it is not stored on the moderation system
+server. You should check that your vote is properly recorded and
+complain if not: vote tallies will list which way each pseudonym
+voted.
+END
+ sendmail_finish();
+}