chiark / gitweb /
Merge branch 'master' of /u/webstump/live-urcm
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 27 Feb 2010 18:42:50 +0000 (18:42 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 27 Feb 2010 18:42:50 +0000 (18:42 +0000)
15 files changed:
.gitignore
ModerationCommon.pm [new file with mode: 0644]
cgi/sballot [new symlink]
common.pl [new file with mode: 0644]
crontab.part [deleted file]
crontab.part.INO [new file with mode: 0644]
install-aggregates [new file with mode: 0755]
install-key [new file with mode: 0755]
install-substitutions [new file with mode: 0755]
sballot/cgi [new file with mode: 0755]
sballot/new-issue [new file with mode: 0755]
setup [new file with mode: 0755]
stump/etc/added-footer.IN1 [new file with mode: 0644]
stump/etc/added-headers.INO [new file with mode: 0644]
stump/etc/hostname.REPLACEMENT [deleted file]

index a3b3b0688d0964aa00b96ac4e071df81382b5c73..83b6fafefb3772526ba2ee8e0a1ed4ea275cceaf 100644 (file)
 /xlog/log/*/event.log
 /xlog/log/*/event.log.[0-9]
 /xlog/log/*/event.log.[0-9].gz
-/master/moderators
+/crontab.part
+/stump/etc/added-headers
+/stump/etc/added-footer
+/stump/etc/added-footer.new
+/stump/etc/approval.key.txt
+/sballot/issues
+
+/settings
+/moderators
diff --git a/ModerationCommon.pm b/ModerationCommon.pm
new file mode 100644 (file)
index 0000000..41f865f
--- /dev/null
@@ -0,0 +1,53 @@
+
+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;
diff --git a/cgi/sballot b/cgi/sballot
new file mode 120000 (symlink)
index 0000000..1acc459
--- /dev/null
@@ -0,0 +1 @@
+../sballot/cgi
\ No newline at end of file
diff --git a/common.pl b/common.pl
new file mode 100644 (file)
index 0000000..e149b91
--- /dev/null
+++ b/common.pl
@@ -0,0 +1,11 @@
+our $hashlen=32;
+
+sub hash ($) {
+    my $r= `echo $_[0] | sha256sum`; $? and die $?;
+    $r =~ s/ *\-$//;
+    chomp $r;
+    return $r;
+}
+
+
+1;
diff --git a/crontab.part b/crontab.part
deleted file mode 100644 (file)
index 602ab06..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-#
-# install with
-#  ssh webstump@chiark crontab live/crontab
-#
-#m h  d m dow
-50 7  12 * *   savelog live/xlog/log/uk.rec.cycling.moderated/event.log
-50 7  12 * *   savelog live/errs
diff --git a/crontab.part.INO b/crontab.part.INO
new file mode 100644 (file)
index 0000000..4704cd1
--- /dev/null
@@ -0,0 +1,7 @@
+#
+# install with
+#  ssh webstump@chiark live/crontab-install
+#
+#m h  d m dow
+50 7  12 * *   savelog live-%ABBREV%/xlog/log/%GROUP%/event.log
+50 7  12 * *   savelog live-%ABBREV%/errs
diff --git a/install-aggregates b/install-aggregates
new file mode 100755 (executable)
index 0000000..10e2e6d
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/bash
+set -e
+
+basedir=`dirname "$0"`
+case "$basedir" in
+.|..)          basedir="$basedir/.."           ;;
+*)             basedir=`dirname "$basedir"`    ;;
+esac
+cd $basedir
+
+for f in       crontab forward-suffix forward-slimy
+do
+       d=$f
+       g=$f.combined
+       >$g
+
+       case $f in
+       forward-*)      d=.$d; echo '# Exim filter' >>$g ;;
+       esac
+
+       echo '# autogenerated - do not edit' >>$g
+       if test -f $f.part; then cat $f.part >>$g; fi
+       cat live-*/$f.part >>$g
+
+       mv -f $g $d
+       case $f in
+       crontab)        crontab crontab ;;
+       esac
+done
diff --git a/install-key b/install-key
new file mode 100755 (executable)
index 0000000..feee4cc
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+set -e
+
+fail () { echo >&2 "fatal: $*"; exit 1; }
+
+. settings
+
+armorout=stump/etc/approval.key.txt
+if test -f $armorout; then exit 0; fi
+
+if [ x"`whoami`" != xwebstump ]; then fail "wrong user"; fi
+
+gpg --batch --gen-key - <<END
+%echo Generating key...
+Key-Type: RSA
+Key-Length: 2048
+Key-Usage: sign
+Name-Real: $GROUP approval key
+Name-Email: $MODEMAIL
+%commit
+%echo Key generated.
+END
+
+gpg --export --armor "$GROUP approval key <$MODEMAIL>" >$armorout
diff --git a/install-substitutions b/install-substitutions
new file mode 100755 (executable)
index 0000000..b0349fa
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/bash
+set -e
+. settings
+export GROUP
+export ABBREV
+export INFOHEADER
+find -name '*.IN[O1]' -exec perl -e '
+       use IO::Handle;
+       use POSIX;
+       my $f= shift;
+       my $d= $f;
+       $d =~ s/\.IN([O1])$//;
+       my $overwrite= ($1 eq 'O');
+       my $g= "$d.new";
+       open F, "< $f" or die "$f $!";
+       open G, "> $g" or die "$g $!";
+       while (<F>) {
+               s/\%(\w+)\%/ 
+                       exists($ENV{$1}) ? $ENV{$1} : die "$1 ?"
+                /ge;
+               print G or die $!;
+       }
+       F->error and die $!;
+       close F or die $!;
+       close G or die $!;
+       if ($overwrite) {
+               rename $g, $d or die "$g $d $!";
+       } else {
+               if (link $g, $d) {
+                       unlink $g or die $!;
+               } else {
+                       die "$g $d $!" unless $!==&EEXIST;
+               }
+       }
+' '{}' \;
diff --git a/sballot/cgi b/sballot/cgi
new file mode 100755 (executable)
index 0000000..d328b9b
--- /dev/null
@@ -0,0 +1,166 @@
+#!/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;
diff --git a/sballot/new-issue b/sballot/new-issue
new file mode 100755 (executable)
index 0000000..d3720b6
--- /dev/null
@@ -0,0 +1,157 @@
+#!/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();
+}
diff --git a/setup b/setup
new file mode 100755 (executable)
index 0000000..980a5a5
--- /dev/null
+++ b/setup
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e
+# To set up a new newsgroup, run this script.
+#
+# Some things are idempotent, others are once-only.
+
+./install-key
+./install-substitutions
+./install-aggregates
diff --git a/stump/etc/added-footer.IN1 b/stump/etc/added-footer.IN1
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/stump/etc/added-headers.INO b/stump/etc/added-headers.INO
new file mode 100644 (file)
index 0000000..afe9a1c
--- /dev/null
@@ -0,0 +1 @@
+X-Moderation: %INFOHEADER%
diff --git a/stump/etc/hostname.REPLACEMENT b/stump/etc/hostname.REPLACEMENT
deleted file mode 100755 (executable)
index 820d4a2..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/sh
-#
-# This is a shell script that replaces "hostname" on bsd-derived systems
-# where inews conflicts with system-provided "hostname" program.
-#
-# This is a drop-in replacement for hostname.
-#
-# Edit this file and set HOSTNAME and FULL_HOSTNAME to the right values,
-# according to your machine's system name.
-#
-# Read instructions in "modenv" file (close to RNEWS) where I explain when
-# you should use this script, and how.
-#
-# Example: 
-# HOSTNAME=galaxy
-# FULL_HOSTNAME=galaxy.galstar.com
-
-HOSTNAME=manifold
-FULL_HOSTNAME=manifold.algebra.com
-
-if [ "x$1" = "-f" ] ; then
-  echo $HOSTNAME
-else
-  echo $FULL_HOSTNAME
-fi