3 usage: ../fyvzl [<options>] <database-file> <action> <arg>...
8 -C (show comments in output)
10 options for alphanum generation
11 -l<minrandlength> (for create/choose alphanum, minimum randlength)
12 options for wordlist generation
13 -Wf<wordlist> (switches generation method to wordlist)
14 -WF<min-word-list-len> } (for wordlist generation
15 -Wl<min-num-words> } method only)
16 -WL<min-max-mean-word-len> }
17 -Wd<permitted-delimiter-chars> } (first char is default; comma = none)
19 our $usage2 = <<'END';
21 create [<genopts>] [<redirect>] [#<comment>] (default redirect is username)
22 choose [<genopts>] [<redirect>] [#<comment>] (interactively allocate)
23 update <addr> [<redirect>] [#<comment>]
25 list [-a] (-a also lists aliases set to reject)
27 empty string for redirect means reject
28 remember to quote comments (to protect # from your shell)
30 our $usage3 = <<'END';
33 insert-exact <addr> <user> <redirect> <comment>
36 enable-user|disable-user <user>
37 default generation method is alphanum
39 our %usage_genopts = (
41 -l<randlength> (number of letters+digits)
44 -l<num-words> (number of words in output)
45 -d<delim-char> (delimiter character, "," means none)
46 -F<max-dict-size> (pick from up to <dictsize> different words, 0 means all)
47 -m<max-addr-len> (restrict total length of generated addrs, 0 = unlimited)
57 our $maxperuser = 10000;
64 our $genmethod = 'alphanum';
68 our $minrandlength = 6;
69 our $maxrandlength = 100;
76 our $minwordlistlen = 1000;
77 our $minmaxmeanwordlen = 6.2;
79 our $maxnumwords = 10;
80 our $worddelims = '.-_,';
84 our $wordlistlen = 3000;
85 our $maxdomainlen = 40;
88 die "too few arguments\n" unless @ARGV;
90 die "option in wrong place on command line\n" if $v =~ m/^-/;
94 sub addr2localpart ($) {
96 return $addr if $addr !~ m/\@/;
97 die "address not in correct domain (\@$dom)\n" unless $' eq $dom; #';
101 sub nextarg_addr () {
102 return addr2localpart nextarg;
106 die "too many arguments\n" if @ARGV;
112 $dis_q ||= $dbh->prepare("SELECT * FROM disabled_users WHERE user=?");
114 my $row = $dis_q->fetchrow_arrayref();
121 if (!defined $last_u or $last_u ne $u) {
122 print "# user $u ".(isdisabled($u) ? 'disabled' : 'enabled')."\n";
129 my $u = $row->{'user'};
131 my $pa = $row->{'localpart'};
132 $pa .= '@'.$dom if defined $dom;
133 if (length $row->{'redirect'}) {
134 print "$pa: $row->{'redirect'}" or die $!;
136 print "# reject $pa" or die $!;
138 if ($showcomment || !$priv) {
139 print " #$row->{'comment'}" or die $!;
141 print "\n" or die $!;
148 read(R, $ch, 1) == 1 or die $!;
151 next unless $o-$v+$lim < 256;
152 # print STDERR "goodrand($lim)=$v\n";
159 if (defined $$ref && $$ref =~ m/[^\041-\177]/) {
160 die "bad characters in redirection target\n";
162 if (defined $$ref && length $$ref && $$ref !~ m/\@/) {
163 die "unqualified redirection target\n" unless defined $qualdom;
164 $$ref .= '@'.$qualdom;
171 "INSERT INTO addrs (".
172 (join ",", sort keys %$row).
174 (join ",", map { "?" } sort keys %$row).
176 $dbh->do($stmt, {}, map { $row->{$_} } sort keys %$row);
184 my $f = (s/^\#// ? 'comment' : 'redirect');
185 die "$f supplied twice\n" if exists $row->{$f};
188 foreach my $f (keys %$defrow) {
189 next if defined $row->{$f};
190 $row->{$f} = $defrow->{$f};
192 qualify $row->{'redirect'};
196 sub local_part_inuse ($) {
198 our $checkexist_q ||=
199 $dbh->prepare("SELECT localpart FROM addrs WHERE localpart=?");
200 $checkexist_q->execute($s);
201 my $row = $checkexist_q->fetchrow_arrayref();
205 sub gen_local_part_alphanum {
206 my $s = chr(ord('a')+goodrand(26));
207 while (length $s < $randlength) {
208 my $v = goodrand(36);
216 sub generate_local_part () {
219 { no strict qw(refs); $s = &{"gen_local_part_$genmethod"}; }
220 # print STDERR "$s\n";
221 last if !local_part_inuse($s);
226 sub prepare_create () {
227 my $countq = $dbh->prepare("SELECT count(*) FROM addrs WHERE user=?");
228 $countq->execute($user);
229 my ($count) = $countq->fetchrow_array();
230 die unless defined $count;
231 die "too many aliases for this user\n" if $count >= $maxperuser;
232 open R, "/dev/urandom" or die $!;
236 sub genopt_alphanum {
240 die "length out of range $minrandlength..$maxrandlength\n"
241 unless ($minrandlength<=$randlength &&
242 $randlength<=$maxrandlength);
244 die "unknown alphanumeric generation option\n";
248 sub gendefaults_alphanum {
249 $randlength ||= $minrandlength;
252 sub gen_local_part_wordlist {
253 my @cmd = (qw(random-word), "-f$wordlist","-n$numwords");
254 push @cmd, "-F$wordlistlen" if $wordlistlen < 1e9;
256 open P, "-|", @cmd or die $!;
258 $!=0; $?=0; close P or die "$? $!";
260 $s =~ s/ /$worddelim/g;
261 my $efflen = length $s;
262 $efflen += 1 + length($dom) if defined $dom;
263 return $s if $efflen <= $maxdomainlen;
267 sub genopt_wordlist {
271 die "length out of range $minnumwords..$maxnumwords\n"
272 unless ($minnumwords<=$numwords &&
273 $numwords<=$maxnumwords);
274 } elsif (m/^-d(.)$/) {
276 die "word delimiter must be one of \`$worddelims'\n"
277 unless grep { $worddelim eq $_ } split //, $worddelims;
278 } elsif (m/^-F(\d+)$/) {
279 $wordlistlen = $1 ? 0+$1 : 1e9;
280 die "requested dictionary size too small\n"
281 unless $wordlistlen >= $minwordlistlen;
282 } elsif (m/^-m(\d+)$/) {
283 $maxdomainlen = $1 ? 0+$1 : 1e9;
285 die "unknown wordlist generation option\n";
289 sub gendefaults_wordlist {
290 $numwords ||= $minnumwords;
291 $worddelim = substr($worddelims,0,1) unless defined $worddelim;
292 $worddelim = '' if $worddelim eq ',';
293 my $expectedmindomlen =
294 (defined $dom ? (1 + length $dom) : 0) # @domain.name
295 + $minmaxmeanwordlen * $numwords # some words
296 + (length $worddelim) * ($numwords-1); # delimiters
297 die "assuming lowest reasonable mean word length $minmaxmeanwordlen".
298 " addrs would be $expectedmindomlen long but".
299 " maximum length is set to $maxdomainlen (use different -m?)\n"
300 if $expectedmindomlen > $maxdomainlen;
304 while (@ARGV && $ARGV[0] =~ m/^-/) {
305 my $arg = shift @ARGV;
306 last if $arg =~ m/^--?$/;
307 { no strict qw(refs); &{"genopt_$genmethod"}($arg); }
309 { no strict qw(refs); &{"gendefaults_$genmethod"}(); }
314 my $newrow = rhsargs({'redirect'=>$user, 'comment'=>''});
316 $newrow->{'user'} = $user;
317 $newrow->{'localpart'} = generate_local_part();
326 my $template = rhsargs({'redirect'=>$user, 'comment'=>''});
327 $template->{'user'} = $user;
331 while (keys %s < 10) {
332 my $s = generate_local_part();
335 print "\@$dom" or die $! if $dom;
336 print "\n" or die $!;
339 print "# ready - enter addrs or local-parts to create,".
340 " then \`.' on a line by itself\n"
348 $s = addr2localpart $_;
349 $s{$s} or die "not an (as-yet-unused) suggestion\n";
351 die "just taken in the meantime (bad luck!)\n"
352 if local_part_inuse $s;
355 my $newrow = { %$template, 'localpart' => $s };
361 print "! error: $@" or die $!;
367 my ($localpart) = @_;
368 our $row_q ||= $dbh->prepare("SELECT * FROM addrs WHERE localpart=?");
369 $row_q->execute($localpart);
370 return $row_q->fetchrow_hashref();
374 my ($localpart) = @_;
375 my $q = $dbh->prepare("SELECT * FROM addrs WHERE localpart=?");
376 my $row = selectrow $localpart;
377 die "unknown localpart\n" unless defined $row;
378 die "not owned by you\n" unless $priv || $row->{user} eq $user;
383 my $localpart = nextarg_addr;
384 my $updrow = rhsargs({});
386 begin_row($localpart);
387 foreach my $f (qw(redirect comment)) {
388 my $v = $updrow->{$f};
389 next unless defined $v;
390 $dbh->do("UPDATE addrs SET $f=? WHERE localpart=?",
393 my $row = selectrow $localpart;
399 my $localpart = nextarg_addr;
401 my $row = begin_row($localpart);
407 while (my $row = $q->fetchrow_hashref()) {
416 last unless $ARGV[0] =~ m/^-/;
422 die "unknown option to list \`$_'\n";
426 my $q = "SELECT * FROM addrs WHERE user=?";
427 $q .= " AND NOT redirect = ''" unless $listall;
428 $q .= " ORDER BY localpart";
429 $q = $dbh->prepare($q);
434 sub action_list_user {
441 sub action_list_all {
444 my $q = $dbh->prepare("SELECT * FROM addrs".
445 " ORDER BY user, localpart");
450 sub action_list_users {
453 my $qo = $dbh->prepare("SELECT user, count(*) AS count FROM addrs".
454 " GROUP BY user ORDER BY user");
455 my $qd = $dbh->prepare("SELECT 1 FROM disabled_users WHERE user = ?");
456 my $qe = $dbh->prepare("SELECT count(*) FROM addrs".
457 " WHERE user = ? AND NOT redirect=''");
459 printf "# total inuse ena user\n" or die $!;
460 while (my $row = $qo->fetchrow_hashref) {
461 my $tu = $row->{user};
464 my ($disabled) = $qd->fetchrow_array();
465 my ($inuse) = $qe->fetchrow_array();
466 printf "%6d %6d %s %s\n",
468 $inuse, $disabled ? '-' : 'y',
474 sub action_insert_exact {
477 $row->{'localpart'} = nextarg_addr;
478 $row->{'user'} = $user = nextarg;
479 $row->{'redirect'} = nextarg;
480 $row->{'comment'} = nextarg;
488 my $localpart = nextarg_addr;
489 my $newuser = nextarg;
491 begin_row($localpart);
492 $dbh->do('UPDATE addrs SET user=? WHERE localpart=?',
493 {}, $newuser, $localpart);
497 sub action_enable_user {
501 $dbh->do('DELETE FROM disabled_users WHERE user=?',{},$user);
505 sub action_disable_user {
509 $dbh->do('INSERT INTO disabled_users (user) VALUES (?)',{},$user);
513 sub action_list_actions {
514 print $usage2 or die $!;
515 print "genopts\n" or die $!;
516 print $usage_genopts{$genmethod} or die $!;
520 last unless $ARGV[0] =~ m/^-/;
527 } elsif (s/^-m(\d+)$//) {
529 } elsif (s/^-d(\S+)$//) {
531 } elsif (s/^-q(\S+)$//) {
533 } elsif (s/^-Wf(\S+)$//) {
535 $genmethod = 'wordlist';
536 } elsif (s/^-WF(\d+)$//) {
537 $minwordlistlen = $1;
538 } elsif (s/^-Wl(\d+)$//) {
540 } elsif (s/^-WL([0-9.]+)$//) {
541 $minmaxmeanwordlen = $1;
545 print $usage1.$usage2.$usage3 or die $!;
546 foreach my $meth (qw(alphanum wordlist)) {
547 print "genopts for $meth generation method\n" or die $!;
548 print $usage_genopts{$meth} or die $!;
552 die "unknown option \`$_'\n";
557 my $dbfile = nextarg();
559 if (defined $ENV{'USERV_USER'}) {
561 $user = $ENV{'USERV_USER'};
564 $user = ((getpwuid $<)[0]) or die;
567 $usage2 .= defined $dom
568 ? "addr may be a local part, implicitly qualified with \@$dom\n"
569 : "addr must be a local part (only)\n";
570 $usage2 .= "redirect is implicitly qualified with \@$qualdom".
574 $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","",
575 { PrintError => 0, AutoCommit => 0, RaiseError => 1 })
578 my $action = nextarg();
580 { no strict qw(refs); &{"action_$action"}(); }