#!/usr/bin/perl -w
our $usage1 = <<'END';
-usage: ../fyvzl [OPTIONS DATABASE-FILE ACTION ARG
+usage: ../fyvzl [<options>] <database-file> <action> <arg>...
options
- -lLENGTH (for create)
- -mMAXPERUSER
- -dDOM
- -qQUALDOM
+ -m<maxperuser>
+ -d<dom>
+ -q<qualdom>
-C (show comments in output)
-h (display help)
+options for alphanum generation
+ -l<minrandlength> (for create/choose alphanum, minimum randlength)
END
our $usage2 = <<'END';
actions
- create [REDIRECT] [#COMMENT] (default for REDIRECT is your username)
- update ADDR [REDIRECT] [#COMMENT]
- show ADDR
+ create [<genopts>] [<redirect>] [#<comment>] (default redirect is username)
+ choose [<genopts>] [<redirect>] [#<comment>] (interactively allocate)
+ update <addr> [<redirect>] [#<comment>]
+ show <addr>
list
list-actions
-empty string for REDIRECT means reject
+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
+ list-user <user>
+ insert-exact <addr> <user> <redirect> <comment>
+ donate <addr> <user>
+ enable-user|disable-user <user>
+default generation method is alphanum
END
+our %usage_genopts = (
+'alphanum' => <<END,
+ -l<randlength> (number of letters+digits)
+END
+);
#/
use strict;
use DBI;
use POSIX;
-our $randlength = 6;
our $maxperuser = 10000;
our $qualdom;
our $dbh;
our $user;
our $priv;
our $showcomment;
+our $genmethod = 'alphanum';
+
+# for alphanum
+# options
+our $minrandlength = 6;
+our $maxrandlength = 100;
+# genopts
+our $randlength;
sub nextarg () {
die "too few arguments\n" unless @ARGV;
return $v;
}
-sub nextarg_addr () {
- my $addr = nextarg;
+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;
}
return !!$row;
}
-sub prow ($) {
- my ($row) = @_;
- my $u = $row->{'user'};
+sub puser ($) {
+ my ($u) = @_;
our $last_u;
if (!defined $last_u or $last_u ne $u) {
print "# user $u ".(isdisabled($u) ? 'disabled' : 'enabled')."\n";
$last_u = $u;
}
+}
+
+sub prow ($) {
+ my ($row) = @_;
+ my $u = $row->{'user'};
+ puser($u);
my $pa = $row->{'localpart'};
$pa .= '@'.$dom if defined $dom;
if (length $row->{'redirect'}) {
return $row;
}
-sub action_create {
- my $newrow = rhsargs({'redirect'=>$user, 'comment'=>''});
- open R, "/dev/urandom" or die $!;
- binmode R;
+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 gen_local_part_alphanum {
+ my $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));
+ }
+ return $s;
+}
+
+sub generate_local_part () {
+ my $s;
+ for (;;) {
+ { no strict qw(refs); $s = &{"gen_local_part_$genmethod"}; }
+# 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;
- my $q = $dbh->prepare("SELECT localpart FROM addrs WHERE localpart=?");
- 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";
- $q->execute($s);
- my $row = $q->fetchrow_arrayref();
- last if !$row;
- $dbh->abort();
+ open R, "/dev/urandom" or die $!;
+ binmode R;
+}
+
+sub genopt_alphanum {
+ local ($_) = @_;
+ if (m/^-l(\d+)$/) {
+ $randlength = 0+$1;
+ die "length out of range $minrandlength..$maxrandlength\n"
+ unless ($minrandlength<=$randlength &&
+ $randlength<=$maxrandlength);
+ } else {
+ die "unknown alphanumeric generation option\n";
+ }
+}
+
+sub gendefaults_alphanum {
+ $randlength ||= $minrandlength;
+}
+
+sub genopts {
+ while (@ARGV && $ARGV[0] =~ m/^-/) {
+ my $arg = shift @ARGV;
+ last if $arg =~ m/^--?$/;
+ { no strict qw(refs); &{"genopt_$genmethod"}($arg); }
}
+ { no strict qw(refs); &{"gendefaults_$genmethod"}(); }
+}
+
+sub action_create {
+ genopts;
+ my $newrow = rhsargs({'redirect'=>$user, 'comment'=>''});
+ prepare_create();
$newrow->{'user'} = $user;
- $newrow->{'localpart'} = $s;
+ $newrow->{'localpart'} = generate_local_part();
insertrow($newrow);
$dbh->commit();
prow($newrow);
}
+sub action_choose {
+ genopts;
+ my $template = rhsargs({'redirect'=>$user, 'comment'=>''});
+ $template->{'user'} = $user;
+ prepare_create();
+ puser($user);
+ my %s;
+ while (keys %s < 10) {
+ my $s = generate_local_part();
+ next if $s{$s};
+ print $s or die $!;
+ print "\@$dom" or die $! if $dom;
+ print "\n" or die $!;
+ $s{$s} = 1;
+ }
+ print "# ready - enter addrs or local-parts to create,".
+ " then \`.' on a line by itself\n"
+ or die $!;
+
+ while (<STDIN>) {
+ chomp;
+ last if m/^\.$/;
+ my $s;
+ if (eval {
+ $s = addr2localpart $_;
+ $s{$s} or die "not an (as-yet-unused) suggestion\n";
+ delete $s{$s};
+ die "just taken in the meantime (bad luck!)\n"
+ if local_part_inuse $s;
+ 1;
+ }) {
+ my $newrow = { %$template, 'localpart' => $s };
+ $dbh->commit();
+ prow($newrow);
+ } else {
+ $dbh->rollback();
+ print "! error: $@" or die $!;
+ }
+ }
+}
+
sub selectrow ($) {
my ($localpart) = @_;
our $row_q ||= $dbh->prepare("SELECT * FROM addrs WHERE localpart=?");
sub action_list_actions {
print $usage2 or die $!;
+ print "genopts\n" or die $!;
+ print $usage_genopts{$genmethod} or die $!;
}
while (@ARGV) {
for (;;) {
last unless m/^-./;
if (s/^-l(\d+)$//) {
- $randlength = $1;
+ $minrandlength = $1;
} elsif (s/^-m(\d+)$//) {
$maxperuser = $1;
} elsif (s/^-d(\S+)$//) {
$showcomment = 1;
} elsif (s/^-h/-/) {
print $usage1.$usage2.$usage3 or die $!;
+ foreach my $meth (qw(alphanum)) {
+ print "genopts for $meth generation method\n" or die $!;
+ print $usage_genopts{$meth} or die $!;
+ }
exit 0;
} else {
die "unknown option \`$_'\n";
$user = ((getpwuid $<)[0]) or die;
}
-$usage2 .= "ADDR may be a local part, implicitly qualified with \@$dom\n"
- if defined $qualdom;
-$usage2 .= "REDIRECT is implicitly qualified with \@$qualdom if it has no \@\n"
+$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","","",