#!/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)
+options for wordlist generation
+ -Wf<wordlist> (switches generation method to wordlist)
+ -WF<min-word-list-len> } (for wordlist generation
+ -Wl<min-num-words> } method only)
+ -WL<min-max-mean-word-len> }
+ -Wd<permitted-delimiter-chars> } (first char is default; comma = none)
END
our $usage2 = <<'END';
actions
- create [REDIRECT] [#COMMENT] (default for REDIRECT is your username)
- choose [REDIRECT] [#COMMENT] (interactively see and allocate suggestions)
- 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
+'wordlist' => <<END,
+ -l<num-words> (number of words in output)
+ -d<delim-char> (delimiter character, "," means none)
+ -F<max-dict-size> (pick from up to <dictsize> different words, 0 means all)
+ -m<max-addr-len> (restrict total length of generated addrs, 0 = unlimited)
+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;
+
+# for wordlist
+# options
+our $wordlist;
+our $minwordlistlen = 1000;
+our $minmaxmeanwordlen = 6.2;
+our $minnumwords = 3;
+our $maxnumwords = 10;
+our $worddelims = '.-_,';
+# genopts
+our $numwords;
+our $worddelim;
+our $wordlistlen = 3000;
+our $maxdomainlen = 40;
sub nextarg () {
die "too few arguments\n" unless @ARGV;
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 (;;) {
- $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));
- }
+ { no strict qw(refs); $s = &{"gen_local_part_$genmethod"}; }
# print STDERR "$s\n";
last if !local_part_inuse($s);
}
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 gen_local_part_wordlist {
+ my @cmd = (qw(random-word), "-f$wordlist","-n$numwords");
+ push @cmd, "-F$wordlistlen" if $wordlistlen < 1e9;
+ for (;;) {
+ open P, "-|", @cmd or die $!;
+ my $s = <P>;
+ $!=0; $?=0; close P or die "$? $!";
+ chomp $s or die;
+ $s =~ s/ /$worddelim/g;
+ my $efflen = length $s;
+ $efflen += 1 + length($dom) if defined $dom;
+ return $s if $efflen <= $maxdomainlen;
+ }
+}
+
+sub genopt_wordlist {
+ local ($_) = @_;
+ if (m/^-l(\d+)$/) {
+ $numwords = $1;
+ die "length out of range $minnumwords..$maxnumwords\n"
+ unless ($minnumwords<=$numwords &&
+ $numwords<=$maxnumwords);
+ } elsif (m/^-d(.)$/) {
+ $worddelim = $1;
+ die "word delimiter must be one of \`$worddelims'\n"
+ unless grep { $worddelim eq $_ } split //, $worddelims;
+ } elsif (m/^-F(\d+)$/) {
+ $wordlistlen = $1 ? 0+$1 : 1e9;
+ die "requested dictionary size too small\n"
+ unless $wordlistlen >= $minwordlistlen;
+ } elsif (m/^-m(\d+)$/) {
+ $maxdomainlen = $1 ? 0+$1 : 1e9;
+ } else {
+ die "unknown wordlist generation option\n";
+ }
+}
+
+sub gendefaults_wordlist {
+ $numwords ||= $minnumwords;
+ $worddelim = substr($worddelims,0,1) unless defined $worddelim;
+ $worddelim = '' if $worddelim eq ',';
+ my $expectedmindomlen =
+ (defined $dom ? (1 + length $dom) : 0) # @domain.name
+ + $minmaxmeanwordlen * $numwords # some words
+ + (length $worddelim) * ($numwords-1); # delimiters
+ die "assuming lowest reasonable mean word length $minmaxmeanwordlen".
+ " addrs would be $expectedmindomlen long but".
+ " your maximum length specified $maxdomainlen\n"
+ if $expectedmindomlen > $maxdomainlen;
+}
+
+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;
}
sub action_choose {
+ genopts;
my $template = rhsargs({'redirect'=>$user, 'comment'=>''});
$template->{'user'} = $user;
prepare_create();
print "\n" or die $!;
$s{$s} = 1;
}
- print "# enter addresses or local parts to create,".
+ print "# ready - enter addrs or local-parts to create,".
" then \`.' on a line by itself\n"
or die $!;
prow($newrow);
} else {
$dbh->rollback();
- print "!error: $@" or die $!;
+ print "! error: $@" or die $!;
}
}
}
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+)$//) {
$dom = $1;
} elsif (s/^-q(\S+)$//) {
$qualdom = $1;
+ } elsif (s/^-Wf(\S+)$//) {
+ $wordlist = $1;
+ $genmethod = 'wordlist';
+ } elsif (s/^-WF(\d+)$//) {
+ $minwordlistlen = $1;
+ } elsif (s/^-Wl(\d+)$//) {
+ $minnumwords = $1;
+ } elsif (s/^-WL([0-9.]+)$//) {
+ $minmaxmeanwordlen = $1;
} elsif (s/^-C/-/) {
$showcomment = 1;
} elsif (s/^-h/-/) {
print $usage1.$usage2.$usage3 or die $!;
+ foreach my $meth (qw(alphanum wordlist)) {
+ print "genopts for $meth generation method\n" or die $!;
+ print $usage_genopts{$meth} or die $!;
+ }
exit 0;
} else {
die "unknown option \`$_'\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"
+ ? "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","","",