chiark / gitweb /
break out gen_local_part_alphanum
[d.git] / service
diff --git a/service b/service
index 7e8eed9ee2ef221f79df272e935515bff257b218..3e1197d141b6b21dc4b768e4aaf7b05465ea3e85 100755 (executable)
--- a/service
+++ b/service
@@ -12,6 +12,7 @@ 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
   list
@@ -25,6 +26,7 @@ privileged actions
   donate ADDR USER
   enable-user|disable-user USER
 END
+#/
 
 use strict;
 
@@ -39,6 +41,7 @@ our $dom;
 our $user;
 our $priv;
 our $showcomment;
+our $genmethod = 'alphanum';
 
 sub nextarg () {
     die "too few arguments\n" unless @ARGV;
@@ -47,13 +50,17 @@ sub nextarg () {
     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;
 }
@@ -67,14 +74,19 @@ sub isdisabled ($) {
     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'}) {
@@ -140,38 +152,96 @@ sub rhsargs ($) {
     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 action_create {
+    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 {
+    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=?");
@@ -320,8 +390,9 @@ if (defined $ENV{'USERV_USER'}) {
     $user = ((getpwuid $<)[0]) or die;
 }
 
-$usage2 .= "ADDR may be a local part, implicitly qualified with \@$dom\n"
-    if defined $qualdom;
+$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;