3 # F*EX CGI for administration
 
   5 # Original author: Andre Hafner <andrehafner@gmx.net>
 
   8 BEGIN { ($ENV{PERLINIT}||'') =~ /(.+)/s and eval $1 }
 
  16 (our $FEXLIB) = $ENV{FEXLIB} =~ /(.+)/ or die "no \$FEXLIB\n";
 
  18 # import from fex.pp and fex.ph
 
  19 our ($FEXHOME,$spooldir,$logdir,$docdir,$akeydir,$durl,$mdomain,$bs,$hostname);
 
  20 our ($keep_default,$keep_max,$recipient_quota,$sender_quota,$autodelete);
 
  21 our ($admin,$admin_pw,$admin_hosts);
 
  23 our $error = 'FAC error';
 
  25 # load common code, local config : $HOME/lib/fex.ph
 
  26 require "$FEXLIB/fex.pp";
 
  29 my $ra = $ENV{REMOTE_ADDR}||0;
 
  31 if (not @admin_hosts or not ipin($ra,@admin_hosts)) {
 
  32   html_error($error,"Administration from your host ($ra) is not allowed.");
 
  35 html_error($error,"\$admin not configured in $FEXLIB/fex.ph\n") unless $admin;
 
  37 chdir $spooldir or http_die("$spooldir - $!");
 
  38 chomp($admin_pw = slurp("$admin/@")||'');
 
  39 html_error($error,"no F*EX account for admin $admin\n") unless $admin_pw;
 
  41 # redirect to https if configured
 
  42 (undef,$port) = split(':',$ENV{HTTP_HOST}||'');
 
  43 $port ||= $ENV{PROTO} eq 'https' ? 443 : 80;
 
  44 if ($port == 80 and open my $x,'/etc/xinetd.d/fexs') {
 
  46     if (/^\s*disable\s*=\s*no/) {
 
  48         "HTTP/1.1 301 Moved Permanently",
 
  49         "Location: https://$hostname$ENV{REQUEST_URI}",
 
  62 $action = $PARAM{"action"}||'';
 
  70 my $http_client = $ENV{HTTP_USER_AGENT} || '';
 
  72 # files to save with backup function
 
  73 my @backup_files = qw(
 
  82 if ($action eq "backup") {
 
  87 http_header('200 OK');
 
  89 $_ = html_header("F*EX Admin Control for $hostname");
 
  90 s:</h1>: (<a href="?action=logout">logout</a>)</h1>:;
 
  94   "<li><a href=\"?action=create\">Create new user</a>\n".
 
  95   "<li><a href=\"?action=change-auth\">Change user auth-ID</a>\n".
 
  96   "<li><a href=\"?action=edit\">Edit user restrictions file</a>\n".
 
  97   "<li><a href=\"?action=delete\">Delete existing user</a>\n".
 
  98   "<li><a href=\"?action=quota\">Manage disk quota</a>\n";
 
 101   "<li><a href=\"?action=fup.log\">Get fup.log</a>\n".
 
 102   "<li><a href=\"?action=fop.log\">Get fop.log</a>\n".
 
 103   "<li><a href=\"?action=error.log\">Get error.log</a>\n";
 
 105 if (-f "$logdir/fexsrv.log") {
 
 107     "<li><a href=\"?action=watch\">Watch logfile</a>\n".
 
 108     "<li><a href=\"?action=fexsrv.log\">Get fexsrv.log</a>\n".
 
 113   "<li><a href=\"?action=backup\">Download backup<br>(config only)</a>\n".
 
 114   "<li><a href=\"?action=restore\">Restore backup</a>\n";
 
 117   "<li><a href=\"?action=list\">List spooled files</a>\n".
 
 118   "<li><a href=\"?action=showquota\">Show quotas (sender/recipient)</a>\n".
 
 119   "<li><a href=\"?action=showconfig\">Show server config</a>\n".
 
 120   "<li><a href=\"?action=userconfig\">Show user config</a>\n";
 
 123   "<li><a href=\"?action=editconfig\">Edit config</a>\n".
 
 124   "<li><a href=\"?action=editindex\">Edit index.html</a>\n";
 
 128   '  <th>manage user</th>'
 
 130   '  <th>log files</th>'
 
 134   '    <td><ul>$nav_user</ul>'
 
 135   '    <td><ul>$nav_show</ul>'
 
 136   '    <td><ul>$nav_log</ul>'
 
 137   '    <td><ul>$nav_edit</ul>'
 
 138   '    <td><ul>$nav_backup</ul>'
 
 144 my @user_items = &userList;
 
 146 if    ($action eq "create")      { &createUserForm }
 
 147 elsif ($action eq "change-auth") { &changeAuthForm }
 
 148 elsif ($action eq "edit")        { &editRestrictionsForm }
 
 149 elsif ($action eq "delete")      { &deleteUserForm }
 
 150 elsif ($action eq "quota")       { &changeQuotaForm }
 
 151 elsif ($action eq "list")        { &listFiles }
 
 152 elsif ($action eq "showquota")   { &showQuota }
 
 153 elsif ($action eq "showconfig")  { &showConfig }
 
 154 elsif ($action eq "userconfig")  { &userConfigForm }
 
 155 elsif ($action eq "watch")       { &watchLog }
 
 156 elsif ($action eq "fexsrv.log")  { &getlog("fexsrv.log") }
 
 157 elsif ($action eq "fup.log")     { &getlog("fup.log") }
 
 158 elsif ($action eq "fop.log")     { &getlog("fop.log") }
 
 159 elsif ($action eq "error.log")   { &getlog("error.log") }
 
 160 elsif ($action eq "editconfig")  { &editFile("$FEXLIB/fex.ph") }
 
 161 elsif ($action eq "editindex")   { &editFile("$docdir/index.html") }
 
 162 elsif ($action eq "backup")      { &backup }
 
 163 elsif ($action eq "restore")     { &restoreForm }
 
 165 if (defined $PARAM{"createUser"}) {
 
 166   createUser($PARAM{"createUser"}, $PARAM{"authID"});
 
 167 } elsif (defined $PARAM{"changeAuthUser"}) {
 
 168   if ($PARAM{"changeAuthUser"} =~ /^#.*/) {
 
 171     changeUser($PARAM{"changeAuthUser"}, $PARAM{"authID"});
 
 173 } elsif (defined $PARAM{"showUserConfig"}) {
 
 174   if ($PARAM{"showUserConfig"} =~ /^#.*/) {
 
 177     showUserConfig($PARAM{"showUserConfig"});
 
 179 } elsif (defined $PARAM{"deleteUser"}) {
 
 180   if ($PARAM{"deleteUser"} =~ /^#.*/) {
 
 183     deleteUser($PARAM{"deleteUser"});
 
 185 } elsif (defined $PARAM{"userQuota"}) {
 
 186   if ($PARAM{"userQuota"} =~ /^#.*/) {
 
 189     if (defined $PARAM{"default quota"}) {
 
 190       $user = normalize_user($PARAM{"userQuota"});
 
 191       unlink "$user/\@QUOTA";
 
 192       print "$user has now default quota:<p>\n";
 
 193       print "recipient quota: $recipient_quota MB<br>\n";
 
 194       print "sender quota: $sender_quota MB<br>\n";
 
 199         $PARAM{"recipientQuota"},
 
 200         $PARAM{"senderQuota"}
 
 204 } elsif (defined $PARAM{"editUser"}) {
 
 205   if ($PARAM{"editUser"} =~ /^#.*/) {
 
 206     &editRestrictionsForm;
 
 208     $user = normalize_user($PARAM{"editUser"});
 
 209     if (defined $PARAM{"delete file"}) {
 
 210       unlink "$user/\@ALLOWED_RECIPIENTS";
 
 211       print "upload restrictions for $user have been deleted\n";
 
 217 } elsif ($PARAM{"contentBox"} and $PARAM{"ar"}) {
 
 218   saveFile($PARAM{"contentBox"},$PARAM{"ar"});
 
 219 } elsif ($PARAM{"upload_archive"}) {
 
 220   restore($PARAM{"upload_archive"}{data});
 
 226 # declaration of formular functions
 
 229 # formular for creating new users
 
 230 # required arguments: -
 
 232   print h3("Create new user");
 
 234     '<form action="/$fac" method="post" enctype="multipart/form-data">'
 
 237     '<td>user</td><td><input type="text" name="createUser" size="80"></td>'
 
 240     '<td>auth-ID:</td><td><input type="text" name="authID" size="16"></td>'
 
 243     '<input type="submit" name="create user" value="create user">'
 
 249 # formular for changing auth-id of an user
 
 250 # required arguments: -
 
 252   my @option = map { "<option value=\"$_\">$_</option>\n" } @user_items;
 
 254   print h3("change auth-ID");
 
 256     '<form action="/$fac" method="post" enctype="multipart/form-data">'
 
 259     '<td>user:</td><td><select name="changeAuthUser">@option</select></td>'
 
 262     '<td>new auth-ID:</td><td><input type="text" name="authID" size="16"></td>'
 
 265     '<input type="submit" name="change" value="change">'
 
 271 # formular choosing user, whose config files shall be shown
 
 272 # required arguments: -
 
 274   my @option = map { "<option value=\"$_\">$_</option>\n" } @user_items;
 
 276   print h3("Show user config files");
 
 278     '<form action="/$fac" method="post enctype="multipart/form-data">'
 
 281     '<td>user:</td><td><select name="showUserConfig">@option</select></td>'
 
 284     '<input type="submit" name="show config files" value="show config files">'
 
 290 # formular for choosing user, whose restriction file shall be edited
 
 291 # required arguments: -
 
 292 sub editRestrictionsForm {
 
 293   my @option = map { "<option value=\"$_\">$_</option>\n" } @user_items;
 
 295   print h3("Edit user restriction file");
 
 297     '<form action="/$fac" method="post enctype="multipart/form-data">'
 
 300     '<td>user:</td><td><select name="editUser">@option</select></td>'
 
 303     '<input type="submit" name="edit file" value="edit file">'
 
 304     '<input type="submit" name="delete file" value="delete file">'
 
 310 # formular for choosing user, who shall be removed
 
 311 # required arguments: -
 
 313   my @option = map { "<option value=\"$_\">$_</option>\n" } @user_items;
 
 315   print h3("Delete existing user");
 
 317     '<form action="/$fac" method="post enctype="multipart/form-data">'
 
 320     '<td>user:</td><td><select name="deleteUser">@option</select></td>'
 
 323     '<input type="submit" name="delete user" value="delete user">'
 
 329 # formular for changing an user's quota file
 
 330 # required arguments: -
 
 331 sub changeQuotaForm {
 
 337   if ($user = $PARAM{"user"}) {
 
 339     $user = normalize_user($user);
 
 340     $rquota = $1 if ($PARAM{"rquota"}||'') =~ /^(\d+)$/;
 
 341     $squota = $1 if ($PARAM{"squota"}||'') =~ /^(\d+)$/;
 
 344   foreach (@user_items) {
 
 345     if ($user and $user eq $_) {
 
 346       push @option,"<option value=\"$_\" selected>$_</option>\n";
 
 348       push @option,"<option value=\"$_\">$_</option>\n";
 
 352   print h3("Manage disk quota");
 
 354     '<form action="/$fac" method="post" enctype="multipart/form-data">'
 
 357     '<td>user:</td><td><select name="userQuota">@option</select></td>'
 
 360     '<td>new quota for recipient:</td>'
 
 361     '<td><input type="text" name="recipientQuota" size="12" value=\"$rquota\">'
 
 362     ' MB (optional)</td>'
 
 365     '<td>new quota for sender:</td>'
 
 366     '<td><input type="text" name="senderQuota" size="12" value=\"$squota\">'
 
 367     ' MB (optional)</td>'
 
 370     '<input type="submit" name="change quota" value="change quota">'
 
 371     '<input type="submit" name="default quota" value="default quota">'
 
 377 # formular for choosing backup file to restore
 
 378 # required arguments: -
 
 380   print h2("restore config");
 
 382     'Specify the backup-archive you want to restore:<br>'
 
 383     '<form action="/$fac" method="post" enctype="multipart/form-data">'
 
 384     '<input type="file" name="upload_archive" size="80"><br>'
 
 385     '<input type="submit" name="restore" value="restore">'
 
 393 # declaration user functions
 
 396 # function for creating new users
 
 397 # required arguments: username, auth-id
 
 402   http_die("not enough arguments in createUser") unless $id;
 
 404   $user = normalize_user($user);
 
 406   unless (-d "$user") {
 
 407     mkdir "$user",0755 or http_die("cannot mkdir $user - $!");
 
 413     html_error($error,"There is already an user $user!");
 
 416   open $idf,'>',$idf or http_die("cannot write $idf - $!");
 
 417   print {$idf} $id,"\n";
 
 418   close $idf or http_die("cannot write $idf - $!");
 
 420   printf "%s?from=%s&ID=%s<br>\n",$fup,$user,$id;
 
 421   printf "%s/%s<p>\n",$fup,b64("from=$user&id=$id");
 
 423   notifyUser($user,$id);
 
 424   print "An information e-mail to $user has been sent.\n";
 
 428 # function for changing an user's auth-ID
 
 429 # required arguments: username, auth-id
 
 433   http_die("not enough arguments in changeUser") unless $id;
 
 436   $user = normalize_user($user);
 
 441   open $idf,'>',$idf or http_die("cannot write $idf - $!");
 
 442   print {$idf} $id,"\n";
 
 443   close $idf or http_die("cannot write $idf - $!");
 
 444   printf "%s?from=%s&ID=%s<br>\n",$fup,$user,$id;
 
 445   printf "%s/%s\n",$fup,b64("from=$user&id=$id");
 
 446   print "</code><p>\n";
 
 447   notifyUser($user,$id,"change-auth");
 
 448   print "An information e-mail to $user has been sent.\n";
 
 452 # function for showing an user's config files
 
 453 # required arguments: username
 
 457   http_die("not enough arguments in showUserConfig!") unless $user;
 
 458   $user = normalize_user($user);
 
 460   chdir "$user" or http_die("could not change directory $user - $!");
 
 461   print h2("Config files of <code>$user</code>");
 
 463   foreach my $file (glob('.auto @* @GROUP/*')) {
 
 464     if (-f $file and not -l $file and $file !~ /.*~$/) {
 
 465       print h3($file), "\n";
 
 466       open $file,'<',$file or http_die("cannot open $file - $!");
 
 467       # print "<table border=1><tr><td>\n";
 
 469       # print "</tr></table>\n";
 
 476 # function for editing an user's recipient/sender restrictions
 
 477 # required arguments: username
 
 482   http_die("not enough arguments in editUser") unless $user;
 
 483   $user = normalize_user($user);
 
 484   http_die("no user $user") unless -d $user;
 
 485   my $ar = "$user/\@ALLOWED_RECIPIENTS";
 
 487     open $ar,'>',$ar or http_die("cannot open $ar - $!");
 
 489 # Restrict allowed recipients. Only those listed here are allowed.
 
 490 # Make this file COMPLETLY empty if you want to disable the restriction.
 
 491 # An allowed recipient is an e-mail address, you can use * as wildcard.
 
 492 # Example: *@flupp.org
 
 496   $content = dehtml(slurp($ar));
 
 498     'Edit restrictions file for user $user :<br>'
 
 499     '<form action="/$fac" method="post" enctype="multipart/form-data">'
 
 500     '<textarea name="contentBox"  rows="10" cols="80">'
 
 503     '<input type="hidden" name="ar" value="$ar">'
 
 504     '<input type="submit" name="save changes" value="save changes">'
 
 510 # function for deleting files
 
 511 # required arguments: list of Files
 
 513   http_die("not enough arguments in deleteFiles") unless (my @files = @_);
 
 518         print "file has been deleted: $_<br>\n";
 
 520         print "file could not be deleted: $_ - $!<br>\n";
 
 523       print "file does not exists: $_<br>\n";
 
 529 # function for saving a single file
 
 530 # required arguments: content, location
 
 535   http_die("not enough arguments in saveFile") unless $ar;
 
 537   if ($ar eq 'index.html') {
 
 538     $ar = "$docdir/index.html"
 
 539   } elsif ($ar eq 'fex.ph') {
 
 540     $ar = "$FEXLIB/fex.ph"
 
 541   } elsif ($ar =~ m'^([^/]+/\@ALLOWED_RECIPIENTS)$') {
 
 544     http_die("unknown file $ar")
 
 548   if ($ar =~ /fex.ph$/) {
 
 549     open $new,'>',$new or http_die("cannot open ${ar}_new - $!");
 
 551     close $new or http_die("cannot write $new - $!");;
 
 552     my $status = dehtml(`perl -c $FEXLIB/fex.ph_new 2>&1`);
 
 553     if ($status =~ /syntax OK/ ) {
 
 556       http_die("cannot write $ar~ - $!") if $?;
 
 560         'No valid syntax in configuration file:'
 
 561         '<p><pre>$status</pre><p>'
 
 562         '<a href="javascript:history.back()">back</a>'
 
 567     system 'mv',$ar,"$ar~";
 
 570   open $ar,'>',$ar or http_die("cannot write $ar - $!");
 
 572   close $ar or http_die("cannot write $ar - $!");;
 
 573   print "<code>$ar</code> has been saved\n";
 
 577 # function for deleting existing user
 
 578 # required arguments: username
 
 582   http_die("not enough arguments in deleteUser") unless $user;
 
 584   $user = normalize_user($user);
 
 587   http_die("no such user $user") unless -f $idf;
 
 588   unlink $idf or http_die("cannot remove $idf - $!");
 
 589   unlink "$user/\@ALLOWED_RECIPIENTS";
 
 590   unlink "$user/\@SUBUSER";
 
 591   rmrf("$user/\@GROUP");
 
 592   print "$user deleted\n";
 
 596 # function for saving quota information for one single user
 
 597 # required arguments: username, recipient-quota, sender-quota
 
 599   my ($user,$rq,$sq) = @_;
 
 600   my ($rquota,$squota);
 
 603   $user = normalize_user($user);
 
 604   http_die("$user is not a F*EX user") unless -d $user;
 
 606   $rquota = $squota = '';
 
 607   $qf = "$user/\@QUOTA";
 
 611       $rquota = $1 if /recipient.*?(\d+)/i;
 
 612       $squota = $1 if /sender.*?(\d+)/i;
 
 617   $rquota = $1 if $rq and $rq =~ /(\d+)/;
 
 618   $squota = $1 if $sq and $sq =~ /(\d+)/;
 
 619   open $qf,'>',$qf or http_die("cannot write $qf - $!");
 
 620   print {$qf} "recipient:$rquota\n" if $rquota;
 
 621   print {$qf} "sender:$squota\n"    if $squota;
 
 622   close $qf or http_die("cannot write $qf - $!");
 
 624   $rquota = $recipient_quota unless $rquota;
 
 625   $squota = $sender_quota    unless $squota;
 
 626   print h3("New quotas for $user");
 
 627   print "recipient quota: $rquota MB<br>\n";
 
 628   print "sender quota: $squota MB<br>\n";
 
 632 # function for listing f*exed files
 
 633 # required arguments: -
 
 635   print h3("List current files");
 
 638   foreach $recipient (glob "*@*") {
 
 639     next if -l $recipient;
 
 640     foreach $file (glob "$recipient/*/*") {
 
 641       if (-s "$file/data" and  $dkey = readlink("$file/dkey") and -l ".dkeys/$dkey") {
 
 642         ($to,$from,$file) = split "/",$file;
 
 643         $file = html_quote($file);
 
 644         print "$from → $to : $durl/$dkey/$file\n";
 
 652 # function for watching the fex-logfile
 
 653 # required arguments: -
 
 655   if (-f "$logdir/fexsrv.log") {
 
 656     print h2("polling fexsrv.log"),"\n";
 
 657     open my $log,"$FEXHOME/bin/logwatch|"
 
 658       or http_die("cannot run $FEXHOME/bin/logwatch - $!");
 
 661     print h2("no fexsrv.log");
 
 666 # function for showing logfiles
 
 667 # required arguments: logfile-name
 
 669   my $log = shift or http_die("not enough arguments in getLog");
 
 671   print h2("show $log");
 
 672   if (open $log,"$logdir/$log") {
 
 676     http_die("cannot open $logdir/$log - $!");
 
 681 # function for creating a new backup file
 
 682 # required arguments: -
 
 684   my @d = localtime time;
 
 685   my $date = sprintf('%d%02d%02d',$d[5]+1900,$d[4]+1,$d[3]);
 
 686   my $backup = "backup/config-$date.tar";
 
 687   my $http_client = $ENV{HTTP_USER_AGENT} || '';
 
 691   $home = $1 if $ENV{VHOST} and $ENV{VHOST} =~ /:(.+)/;
 
 693   chdir $home or http_die("$home - $!");
 
 695   unless (-d "backup") {
 
 696     mkdir "backup",0700 or http_die("cannot mkdir backup - $!");
 
 699   system "tar -cf $backup @backup_files 2>/dev/null";
 
 701   $size = -s $backup or http_die("backup file empty");
 
 703   open $backup,'<',$backup or http_die("cannot open $backup - $!");
 
 707     "Content-Length: $size",
 
 708     "Content-Type: application/octet-stream; filename=fex-backup-$date.tar",
 
 709     "Content-Disposition: attachment; filename=\"fex-backup-$date.tar\"",
 
 713   while (read($backup,my $b,$bs)) {
 
 720 # function for restoring an old configuration file
 
 721 # required arguments: uploaded archive
 
 723   my $archive_file = shift or http_die("not enough arguments in restore!");
 
 724   my $restore = "backup.tar";
 
 727   $home = $1 if $ENV{VHOST} and $ENV{VHOST} =~ /:(.+)/;
 
 729   chdir $home or http_die("$home - $!");
 
 732   open $restore,'>',$restore or http_die("cannot open $restore - $!");
 
 733   print {$restore} $archive_file;
 
 734   close $restore or http_die("cannot write $restore - $!");
 
 736     print "file upload successful<br>\n";
 
 737     print "saving actual config in $home/backup/config.tar<br>\n";
 
 739     system "tar -cf backup/config.tar @backup_files";
 
 741     print "starting restore:\n<p>\n";
 
 743     system "tar -xvf $restore";
 
 747     http_die("upload error - no file data received");
 
 751 # function for editing a text-file
 
 752 # required arguments: filepath, filename
 
 757   $file = dehtml(slurp($ar));
 
 761   print h2("edit <code>$ar<code>");
 
 764     '<form action="/$fac" enctype="multipart/form-data" method="post">'
 
 765     '<textarea name="contentBox" rows="26" cols="80">'
 
 768     '<input type="hidden" name="ar" value="$ar">'
 
 769     '<input type="submit" name="save changes" value="save changes">'
 
 775 # function for showing all users' quotas
 
 776 # required arguments: -
 
 779   print h2("Show quotas (domain sorted, values in MB)");
 
 780   print "<table border=\"1\"><tr>";
 
 781   foreach (@user_items) {
 
 782     if (/\#\#\#\s(\S+)/) {
 
 784       print "<th>\@$1</th>";
 
 785       print "<th>sender</th>";
 
 786       print "<th>sender (used)</th>";
 
 787       print "<th>recipient</th>";
 
 788       print "<th>recipient (used)</th>";
 
 792       my $rquota = $recipient_quota;
 
 793       my $squota = $sender_quota;
 
 797       ($squota,$squota_used) = check_sender_quota($user);
 
 798       ($rquota,$rquota_used) = check_recipient_quota($user);
 
 799       my $action = "quota&user=$user&rquota=$rquota&squota=$squota";
 
 802       print "<td><a href=\"?action=$action\">$_</a></td>";
 
 803       print "<td align=\"right\">$squota</td>";
 
 804       print "<td align=\"right\">$squota_used</td>";
 
 805       print "<td align=\"right\">$rquota</td>";
 
 806       print "<td align=\"right\">$rquota_used</td>";
 
 815 # function for showing fex-server configuration
 
 816 # required arguments: -
 
 818   print h3("Show config");
 
 819   print "<table border=\"0\">\n";
 
 820   printf "<tr><td>spooldir:</td><td>%s</td>\n",$spooldir;
 
 821   printf "<tr><td>logdir:</td><td>%s</td>\n",$logdir;
 
 822   printf "<tr><td>docdir:</td><td>%s</td>\n",$docdir;
 
 823   printf "<tr><td>durl:</td><td>%s</td>\n",$durl;
 
 824   printf "<tr><td>mdomain:</td><td>%s</td>\n",$mdomain||'';
 
 825   printf "<tr><td>autodelete:</td><td>%s</td>\n",$autodelete;
 
 826   printf "<tr><td>keep:</td><td>%s</td>\n",$keep_default;
 
 827   printf "<tr><td>keep_max:</td><td>%s</td>\n",$keep_max;
 
 828   printf "<tr><td>recipient_quota:</td><td>%s</td>\n",$recipient_quota;
 
 829   printf "<tr><td>sender_quota:</td><td>%s</td>\n",$sender_quota;
 
 830   printf "<tr><td>admin:</td><td>%s</td>\n",$admin;
 
 835 # require authentification
 
 840   if ($action eq 'logout') {
 
 841     if (($ENV{HTTP_COOKIE}||'') =~ /akey=(\w+)/) {
 
 842       unlink "$akeydir/$1";
 
 845       "HTTP/1.1 301 Moved Permanently",
 
 848       "Set-Cookie: akey=; Max-Age=0; Discard",
 
 854   $rid = slurp("$admin/@") or html_error($error,"no F*EX account for $admin");
 
 862       $akey = md5_hex("$admin:$rid");
 
 867     return if $akey eq md5_hex("$admin:$rid");
 
 870   http_header('200 OK');
 
 871   print html_header("F*EX Admin Control for $hostname");
 
 875       '<font color="red"><h3>'
 
 876       '  wrong akey for <code>$admin</code>'
 
 881   if ($id and $id ne $rid) {
 
 883       '<font color="red"><h3>'
 
 884       '  wrong auth-ID for <code>$admin</code>'
 
 890     '<form action="/$fac" method="post" enctype="multipart/form-data">'
 
 891     '  auth-ID for <code>$admin</code>:'
 
 892     '  <input type="password" name="id" size="16" autocomplete="off">'
 
 899 # function for checking simple HTTP authentication
 
 900 # (not used any more, replaced with require_akey)
 
 902   if ($ENV{HTTP_AUTHORIZATION} and $ENV{HTTP_AUTHORIZATION} =~ /Basic\s+(.+)/)
 
 903   { @http_auth = split(':',decode_b64($1)) }
 
 906     or $http_auth[0] !~ /^(fexmaster|admin|\Q$admin\E)$/
 
 907     or $http_auth[1] ne $admin_pw
 
 910       '401 Authorization Required',
 
 911       "WWW-Authenticate: Basic realm=$admin F*EX admin authentification",
 
 914     # control back to fexsrv for further HTTP handling
 
 920 # function for sending notification mails to an user
 
 921 # required arguments: username, auth-id, message-type
 
 923   my ($user,$id,$type) = @_;
 
 925   my $message = 'A F*EX account has been created for you. Use';
 
 927   http_die("not enough arguments in createUser") unless $id;
 
 928   if ($type and $type eq "change-auth") {
 
 929     $message = 'New auth-ID for your F*EX account has been set. Use'
 
 932   $user = normalize_user($user);
 
 933   open my $mail,'|-',$sendmail,'-f',$admin,$user,$bcc
 
 934     or http_die("cannot start sendmail - $!");
 
 939     'Subject: your F*EX account on $hostname'
 
 944     '$url/fup?from=$user'
 
 947     'See $url/index.html for more information about F*EX.'
 
 949     'Questions? ==> F*EX admin: $admin'
 
 952     or http_die("cannot send notification e-mail (sendmail error $!)");
 
 956 # sort key is the (inverse) domain
 
 957 # required arguments: list of usernames (e-mail addresses)
 
 959 #    http_die("not enough arguments in domainsort") unless (my @d = @_);
 
 965     s/\./,/ while /\..*@/;
 
 967     $_ = join('.',reverse(split /\./));
 
 970   @d = sort { lc $a cmp lc $b } @d;
 
 973     $_ = join('.',reverse(split /\./));
 
 981 # function for creating a sorted list of all users
 
 982 # required arguments: -
 
 988   foreach $u (glob('*@*')) {
 
 990     push @u,$u if -f "$u/@";
 
 993   foreach (domainsort(@u)) {
 
 996         push @list,"### $1 ###";
 
1011   while (<$file>) { print dehtml($_) }
 
1019   return "<h2>$_</h2>\n";
 
1026   return "<h3>$_</h3>\n";
 
1031   print "</body></html>\n";