-#!/usr/bin/perl -w
-our $usage1 = <<'END';
-usage: ../fyvzl [OPTIONS DATABASE-FILE ACTION ARG
-options
- -lLENGTH (for create)
- -mMAXPERUSER
- -dDOM
- -qQUALDOM
- -C (show comments in output)
- -h (display help)
-END
-our $usage2 = <<'END';
-actions
- create [REDIRECT] [#COMMENT] (default for REDIRECT is your username)
- update ADDR [REDIRECT] [#COMMENT]
- show ADDR
- list
- list-actions
-empty string for REDIRECT means reject
-END
-our $usage3 = <<'END';
-privileged actions
- list-user USER
- insert-exact ADDR USER REDIRECT COMMENT
- donate ADDR USER
- enable-user|disable-user USER
-END
-#/
-
-use strict;
-
-use DBI;
-use POSIX;
-
-our $randlength = 6;
-our $maxperuser = 10000;
-our $qualdom;
-our $dbh;
-our $dom;
-our $user;
-our $priv;
-our $showcomment;
-
-sub nextarg () {
- die "too few arguments\n" unless @ARGV;
- my $v = shift @ARGV;
- die "option too late on command line\n" if $v =~ m/^-/;
- return $v;
-}
-
-sub addr2localpart ($) {
- my ($addr) = @_;
- return $addr if $addr !~ m/\@/;
- die "address not in correct domain (\@$dom)\n" unless $' eq $dom; #';
- return $`; #`;
-}
-
-sub nextarg_addr () {
- return addr2localpart nextarg;
-}
-
-sub nomoreargs () {
- die "too many arguments\n" if @ARGV;
-}
-
-sub isdisabled ($) {
- my ($u) = @_;
- our $dis_q;
- $dis_q ||= $dbh->prepare("SELECT * FROM disabled_users WHERE user=?");
- $dis_q->execute($u);
- my $row = $dis_q->fetchrow_arrayref();
- return !!$row;
-}
-
-sub prow ($) {
- my ($row) = @_;
- my $u = $row->{'user'};
- our $last_u;
- if (!defined $last_u or $last_u ne $u) {
- print "# user $u ".(isdisabled($u) ? 'disabled' : 'enabled')."\n";
- $last_u = $u;
- }
- my $pa = $row->{'localpart'};
- $pa .= '@'.$dom if defined $dom;
- if (length $row->{'redirect'}) {
- print "$pa: $row->{'redirect'}" or die $!;
- } else {
- print "# reject $pa" or die $!;
- }
- if ($showcomment || !$priv) {
- print " #$row->{'comment'}" or die $!;
- }
- print "\n" or die $!;
-}
-
-sub goodrand ($) {
- my ($lim) = @_;
- for (;;) {
- my $ch;
- read(R, $ch, 1) == 1 or die $!;
- my $o = ord $ch;
- my $v = $o % $lim;
- next unless $o-$v+$lim < 256;
-# print STDERR "goodrand($lim)=$v\n";
- return $v;
- }
-}
-
-sub qualify (\$) {
- my ($ref) = @_;
- if (defined $$ref && $$ref =~ m/[^\041-\177]/) {
- die "bad characters in redirection target\n";
- }
- if (defined $$ref && length $$ref && $$ref !~ m/\@/) {
- die "unqualified redirection target\n" unless defined $qualdom;
- $$ref .= '@'.$qualdom;
- }
-}
-
-sub insertrow ($) {
- my ($row) = @_;
- my $stmt =
- "INSERT INTO addrs (".
- (join ",", sort keys %$row).
- ") VALUES (".
- (join ",", map { "?" } sort keys %$row).
- ") ";
- $dbh->do($stmt, {}, map { $row->{$_} } sort keys %$row);
-}
-
-sub rhsargs ($) {
- my ($defrow) = @_;
- my $row = { };
- while (@ARGV) {
- $_ = shift @ARGV;
- my $f = (s/^\#// ? 'comment' : 'redirect');
- die "$f supplied twice\n" if exists $row->{$f};
- $row->{$f} = $_;
- }
- foreach my $f (keys %$defrow) {
- next if defined $row->{$f};
- $row->{$f} = $defrow->{$f};
- }
- qualify $row->{'redirect'};
- return $row;
-}
-
-sub local_part_inuse ($) {
- my ($s) = @_;
- our $checkexist_q ||=
- $dbh->prepare("SELECT localpart FROM addrs WHERE localpart=?");
- $checkexist_q->execute($s);
- my $row = $checkexist_q->fetchrow_arrayref();
- return !!$row;
-}
-
-sub generate_local_part () {
- my $s;
- for (;;) {
- $s = chr(ord('a')+goodrand(26));
- while (length $s < $randlength) {
- my $v = goodrand(36);
- $s .= chr($v < 26
- ? ord('a')+($v)
- : ord('0')+($v-26));
- }
-# print STDERR "$s\n";
- last if !local_part_inuse($s);
- }
- return $s;
-}
-
-sub prepare_create () {
- my $countq = $dbh->prepare("SELECT count(*) FROM addrs WHERE user=?");
- $countq->execute($user);
- my ($count) = $countq->fetchrow_array();
- die unless defined $count;
- die "too many aliases for this user\n" if $count >= $maxperuser;
- open R, "/dev/urandom" or die $!;
- binmode R;
-}
-
-sub action_create {
- my $newrow = rhsargs({'redirect'=>$user, 'comment'=>''});
- prepare_create();
- $newrow->{'user'} = $user;
- $newrow->{'localpart'} = generate_local_part();
- insertrow($newrow);
- $dbh->commit();
- prow($newrow);
-}
-
-sub selectrow ($) {
- my ($localpart) = @_;
- our $row_q ||= $dbh->prepare("SELECT * FROM addrs WHERE localpart=?");
- $row_q->execute($localpart);
- return $row_q->fetchrow_hashref();
-}
-
-sub begin_row ($) {
- my ($localpart) = @_;
- my $q = $dbh->prepare("SELECT * FROM addrs WHERE localpart=?");
- my $row = selectrow $localpart;
- die "unknown localpart\n" unless defined $row;
- die "not owned by you\n" unless $priv || $row->{user} eq $user;
- return $row;
-}
-
-sub action_update {
- my $localpart = nextarg_addr;
- my $updrow = rhsargs({});
- nomoreargs;
- begin_row($localpart);
- foreach my $f (qw(redirect comment)) {
- my $v = $updrow->{$f};
- next unless defined $v;
- $dbh->do("UPDATE addrs SET $f=? WHERE localpart=?",
- {}, $v, $localpart);
- }
- my $row = selectrow $localpart;
- $dbh->commit;
- prow($row);
-}
-
-sub action_show {
- my $localpart = nextarg_addr;
- nomoreargs;
- my $row = begin_row($localpart);
- prow($row);
-}
-
-sub listq ($) {
- my ($q) = @_;
- while (my $row = $q->fetchrow_hashref()) {
- prow($row);
- }
-}
-
-sub action_list {
- nomoreargs;
- my $q = $dbh->prepare("SELECT * FROM addrs WHERE user=?".
- " ORDER BY localpart");
- $q->execute($user);
- listq($q);
-}
-
-sub action_list_user {
- die unless $priv;
- $user = nextarg;
- nomoreargs;
- action_list;
-}
-
-sub action_list_all {
- die unless $priv;
- nomoreargs;
- my $q = $dbh->prepare("SELECT * FROM addrs".
- " ORDER BY user, localpart");
- $q->execute();
- listq($q)
-}
-
-sub action_insert_exact {
- die unless $priv;
- my $row = { };
- $row->{'localpart'} = nextarg_addr;
- $row->{'user'} = $user = nextarg;
- $row->{'redirect'} = nextarg;
- $row->{'comment'} = nextarg;
- nomoreargs;
- insertrow($row);
- $dbh->commit;
-}
-
-sub action_donate {
- die unless $priv;
- my $localpart = nextarg_addr;
- my $newuser = nextarg;
- nomoreargs;
- begin_row($localpart);
- $dbh->do('UPDATE addrs SET user=? WHERE localpart=?',
- {}, $newuser, $localpart);
- $dbh->commit;
-}
-
-sub action_enable_user {
- die unless $priv;
- $user = nextarg;
- nomoreargs;
- $dbh->do('DELETE FROM disabled_users WHERE user=?',{},$user);
- $dbh->commit;
-}
-
-sub action_disable_user {
- die unless $priv;
- $user = nextarg;
- nomoreargs;
- $dbh->do('INSERT INTO disabled_users VALUES user (?)',{},$user);
- $dbh->commit;
-}
-
-sub action_list_actions {
- print $usage2 or die $!;
-}
-
-while (@ARGV) {
- last unless $ARGV[0] =~ m/^-/;
- $_ = shift @ARGV;
- last if m/^--?$/;
- for (;;) {
- last unless m/^-./;
- if (s/^-l(\d+)$//) {
- $randlength = $1;
- } elsif (s/^-m(\d+)$//) {
- $maxperuser = $1;
- } elsif (s/^-d(\S+)$//) {
- $dom = $1;
- } elsif (s/^-q(\S+)$//) {
- $qualdom = $1;
- } elsif (s/^-C/-/) {
- $showcomment = 1;
- } elsif (s/^-h/-/) {
- print $usage1.$usage2.$usage3 or die $!;
- exit 0;
- } else {
- die "unknown option \`$_'\n";
- }
- }
-}
-
-my $dbfile = nextarg();
-
-if (defined $ENV{'USERV_USER'}) {
- $priv=0;
- $user = $ENV{'USERV_USER'};
-} else {
- $priv=1;
- $user = ((getpwuid $<)[0]) or die;
-}
-
-$usage2 .= defined $dom
- ? "ADDR may be a local part, implicitly qualified with \@$dom\n"
- : "ADDR must be a local part (only)\n";
-$usage2 .= "REDIRECT is implicitly qualified with \@$qualdom if it has no \@\n"
- if defined $qualdom;
-
-$dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","",
- { PrintError => 0, AutoCommit => 0, RaiseError => 1 })
- or die "$dbfile $!";
-
-my $action = nextarg();
-$action =~ y/-/_/;
-{ no strict qw(refs); &{"action_$action"}(); }
+#!/bin/sh
+set -e
+cd /usr/local/lib/evade-mail
+dom="$USERV_U_domain"
+exec "./service-$dom" -qchiark.greenend.org.uk \
+ -d"$dom" /var/lib/evade-mail/"$dom".sqlite3 \
+ "$@"