merged
authorMatthias Förste <foerste@schlittermann.de>
Tue, 19 Jul 2011 09:08:12 +0200
changeset 37 fee44892ca1e
parent 12 e0e126b7f06d (current diff)
parent 36 a459cc790ed0 (diff)
child 38 460f4d75e403
merged
bin/dav-htuseradd
bin/dav-htuserdel
cgi-bin/admin/dav-htuseradd.cgi
dav-htpasswd.conf.ex
--- a/.hgignore	Wed Jul 13 13:41:08 2011 +0200
+++ b/.hgignore	Tue Jul 19 09:08:12 2011 +0200
@@ -1,6 +1,6 @@
 syntax regexp:
 
 ^TODO$
-^dav-htpasswd.conf$
+^ius-dav-htpasswd.conf$
 ^_build
 ^Build$
--- a/Build.PL	Wed Jul 13 13:41:08 2011 +0200
+++ b/Build.PL	Tue Jul 19 09:08:12 2011 +0200
@@ -4,7 +4,7 @@
 
 my $builder = Module::Build->subclass(
     class => 'My::Module::Build',
-    code => <<'___CODE',
+    code  => <<'___CODE',
     use strict;
     use warnings;
     use File::Temp;
@@ -18,32 +18,44 @@
              defined $provides{$_} ? $provides{$_} : 'lib' . lc $d . '-perl'
                => $self->requires->{$_} == 0 ? '' : ' (>= ' . $self->requires->{$_} . ')' 
              } keys %{$self->requires} ),
-         'at' => ''
         );
         my $c = new File::Temp or die "Can't tempfile";
         print $c "Package: libius-dav-htpasswd-perl-deps\n";
         print $c 'Depends: ', join ', ', map { "$_$deps{$_}" } keys %deps;
         system('equivs-build', $c->filename);
     }
+    sub ACTION_install {
+        my $self = shift;
+        my $h = ($self->install_path('cgi-bin') =~ m|^(.*)/cgi-bin$|)[0] ;
+        my $u = my $g = ($h =~ m|/([^/]+)$|)[0];
+        system(qw(chown -R), "$u:$g", $h) == 0
+            or die "Can't 'chown -R $u:$g $h': $!";
+        { no warnings 'qw';
+          system(qw(chmod -R go=,-st), $h) == 0
+            or die "Can't 'chmod -R go=,-st $h";
+        }
+        $self->SUPER::ACTION_install;
+    }
 ___CODE
 );
 
-my $build = $builder->new
-(
-    module_name => 'Ius::Dav::Htpasswd',
-    license  => 'gpl',
+my $build = $builder->new(
+    module_name    => 'Ius::Dav::Htpasswd',
+    license        => 'gpl',
     create_license => 1,
-    requires => {
+    requires       => {
         'Apache::Htpasswd' => 0,
-        'AppConfig' => 0,
-        'File::Path' => 0,
-        'POSIX' => 0,
+        'AppConfig'        => 0,
+        'File::Path'       => 0,
+        'POSIX'            => 0,
         'String::MkPasswd' => 0
     },
-    script_files => [glob 'bin/*'],
-    cgi_files => { map { /\.(bak|orig)$/ ? () : ($_ => $_) } glob 'cgi-bin/admin/*' }
+    script_files => [ glob 'bin/*' ],
+    cgi_files =>
+      { map { /\.(bak|orig)$/ ? () : ( $_ => $_ ) } glob 'cgi-bin/*' }
 );
 $build->add_build_element('cgi');
 $build->install_path(
-    'cgi-bin' => $build->original_prefix($build->installdirs) . '/lib/cgi-bin');
+        'cgi-bin' => $build->original_prefix( $build->installdirs )
+      . '/lib/ius-dav-htpasswd/cgi-bin' );
 $build->create_build_script;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Tue Jul 19 09:08:12 2011 +0200
@@ -0,0 +1,31 @@
+Installation (on debian):
+
+    * install dependencies
+        # perl ./Build.PL && ./Build equivs
+        # dpkg -i libius-dav-htpasswd-perl-deps_*_all.deb
+        # aptitude install
+        # aptitude install apache2-mpm-itk sudo
+
+    * preinst 
+        # mkdir -p $PREFIX/lib/ius-dav-htpasswd
+        # useradd -d $PREFIX/lib/ius-dav-htpasswd -m -r -U -s /bin/true ius-dav-htpasswd
+        # passwd -l ius-dav-htpasswd
+
+   * installation
+    
+        # perl ./Build.PL && ./Build test && ./Build install
+
+        # visudo
+
+            [...]
+
+            ius-dav-htpasswd debian-lenny = (root) NOPASSWD: $PREFIX/bin/ius-dav-htuseradd, $PREFIX/bin/ius-dav-htuserdel
+
+            [...]
+
+        # $EDITOR /etc/apache2/sites-available/ius-dav-htpasswd # see ssl-vhost-apache-example.conf
+        # htpasswd [-c] $PREFIX/etc/ius-dav-htpasswd/htpasswd.admin ius-dav-htpasswd-admin
+        # htpasswd -c $PREFIX/etc/ius-dav-htpasswd/htpasswd.dav ius-dav-htpasswd-master # when using a master user
+
+        # ln -s /usr/local/bin/ius-dav-htuserexpiry /etc/cron.daily/
+
--- a/bin/dav-htuseradd	Wed Jul 13 13:41:08 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-#!/usr/bin/perl
-
-#    Copyright (C) 2011  Matthias Förste
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-#    Matthias Förste <foerste@schlittermann.de>
-
-use strict;
-use warnings;
-
-use Ius::Dav::Htpasswd qw(mkpasswd readconfig useradd usage);
-
-use Getopt::Long;
-use Pod::Usage;
-
-my ($user, $expiry, $pass);
-
-GetOptions(
-    'u|user=s' => \$user,
-    'e|expiry=i' => \$expiry,
-    'h|help' => sub { usage(-exit => 0, -verbose => 1) },
-    'm|man'  => sub {
-        usage(
-            -exit => 0,
-
-            # "system('perldoc -V &>/dev/null')" appears shorter, but may not
-            # do what you expect ( it still returns 0 on debian squeeze with
-            # dash as system shell even if cannot find the command in $PATH)
-            -noperldoc => system('perldoc -V >/dev/null 2>&1'),
-            -verbose   => 2
-        );
-    },
-) and defined $user or usage;
-
-$pass = useradd readconfig, $user, mkpasswd, $expiry;
-print "[$pass]\n";
--- a/bin/dav-htuserdel	Wed Jul 13 13:41:08 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-#!/usr/bin/perl
-
-#    Copyright (C) 2011  Matthias Förste
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-#    Matthias Förste <foerste@schlittermann.de>
-
-use strict;
-use warnings;
-
-use Getopt::Long;
-use Ius::Dav::Htpasswd qw(readconfig userdel usage);
-
-my $user;
-
-GetOptions(
-    'u|user=s' => \$user,
-    "h|help" => sub { usage( -verbose => 0, -exitval => 0 ) },
-    "m|man"  => sub {
-        usage(
-            -verbose   => 2,
-            -exitval   => 0,
-            -noperldoc => ( `perldoc -V 2>/dev/null`, $? != 0 )[-1]
-        );
-    },
-) and defined $user or usage();
-
-exit userdel readconfig, $user;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/ius-dav-htuseradd	Tue Jul 19 09:08:12 2011 +0200
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+
+#    Copyright (C) 2011  Matthias Förste
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#    Matthias Förste <foerste@schlittermann.de>
+
+use strict;
+use warnings;
+
+use Ius::Dav::Htpasswd qw(mkpasswd readconfig useradd usage);
+
+use Getopt::Long;
+use Pod::Usage;
+
+my ( $user, $expiry, $pass );
+
+GetOptions(
+    'u|user=s'   => \$user,
+    'e|expiry=i' => \$expiry,
+    'h|help'     => sub { usage( -exit => 0, -verbose => 1 ) },
+    'm|man'      => sub {
+        usage(
+            -exit => 0,
+
+            # "system('perldoc -V &>/dev/null')" appears shorter, but may not
+            # do what you expect ( it still returns 0 on debian squeeze with
+            # dash as system shell even if cannot find the command in $PATH)
+            -noperldoc => system('perldoc -V >/dev/null 2>&1'),
+            -verbose   => 2
+        );
+    },
+  )
+  and defined $user
+  or usage;
+
+$pass = useradd readconfig, $user, mkpasswd, $expiry;
+print "$pass\n";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/ius-dav-htuserdel	Tue Jul 19 09:08:12 2011 +0200
@@ -0,0 +1,42 @@
+#!/usr/bin/perl
+
+#    Copyright (C) 2011  Matthias Förste
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#    Matthias Förste <foerste@schlittermann.de>
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+use Ius::Dav::Htpasswd qw(readconfig userdel usage);
+
+my $user;
+
+GetOptions(
+    'u|user=s' => \$user,
+    "h|help"   => sub { usage( -verbose => 0, -exitval => 0 ) },
+    "m|man"    => sub {
+        usage(
+            -verbose   => 2,
+            -exitval   => 0,
+            -noperldoc => ( `perldoc -V 2>/dev/null`, $? != 0 )[-1]
+        );
+    },
+  )
+  and defined $user
+  or usage();
+
+exit userdel readconfig, $user;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/ius-dav-htuserexpiry	Tue Jul 19 09:08:12 2011 +0200
@@ -0,0 +1,43 @@
+#!/usr/bin/perl
+
+#    Copyright (C) 2011  Matthias Förste
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#    Matthias Förste <foerste@schlittermann.de>
+
+use strict;
+use warnings;
+
+use Ius::Dav::Htpasswd qw(readconfig userexpiry usage);
+
+use Getopt::Long;
+use Pod::Usage;
+
+GetOptions(
+    'h|help' => sub { usage( -exit => 0, -verbose => 1 ) },
+    'm|man'  => sub {
+        usage(
+            -exit => 0,
+
+            # "system('perldoc -V &>/dev/null')" appears shorter, but may not
+            # do what you expect ( it still returns 0 on debian squeeze with
+            # dash as system shell even if cannot find the command in $PATH)
+            -noperldoc => system('perldoc -V >/dev/null 2>&1'),
+            -verbose   => 2
+        );
+    },
+) or usage;
+
+exit userexpiry readconfig;
--- a/cgi-bin/admin/dav-htuseradd.cgi	Wed Jul 13 13:41:08 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-#!/usr/bin/perl
-
-#    Copyright (C) 2011  Matthias Förste
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-#    Matthias Förste <foerste@schlittermann.de>
-
-use warnings;
-
-# Using CGI::Fast will result in an Internal Server Error because we are
-# restarting apache when everything else works
-# use CGI::Fast;
-use CGI;
-use Ius::Dav::Htpasswd qw(mkpasswd readconfig useradd);
-
-my $q = new CGI;
-
-    print $q->header(-charset => 'UTF-8');
-    print $q->start_html(
-        -title   => $0,
-        -bgcolor => "white",
-    );
-
-    my ($user, $pass, $expiry) = (
-        $q->param('user'),
-        $q->param('pass'),
-        $q->param('expiry')
-    );
-
-    unless (defined $user or defined $pass or defined $expiry) {
-
-        print $q->start_form,
-            'New User' => $q->textfield('user'),
-            'Password' => $q->password_field('pass'),
-            'Expiry' => $q->textfield('expiry'),
-            $q->submit,
-            $q->end_form;
-
-    } else {
-
-        my @cmd = (qw(sudo dav-htuseradd -u), $user);
-        push @cmd, '-e', $expiry if defined $expiry and $expiry ne ''; 
-
-        if (my $pass = qx(@cmd)) {
-            chomp $pass;
-            print $q->p($pass);
-        } else {
-            print $q->p('Something went wrong');
-        } 
-
-    }
-
-    print $q->end_html;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cgi-bin/ius-dav-htuseradd.cgi	Tue Jul 19 09:08:12 2011 +0200
@@ -0,0 +1,105 @@
+#!/usr/bin/perl
+
+#    Copyright (C) 2011  Matthias Förste
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#    Matthias Förste <foerste@schlittermann.de>
+
+use warnings;
+
+# Using CGI::Fast will result in an Internal Server Error because we are
+# restarting apache when everything else works
+# use CGI::Fast;
+use CGI;
+use Ius::Dav::Htpasswd qw(mkpasswd readconfig useradd);
+
+my $css = <<EOC;
+body {
+        font-family: Helvetica, Arial, sans-serif;
+}
+h3#header {
+    background-color: #d60029;
+    color: #fff;
+    padding-top: 1em;
+    padding-bottom: 1em;
+}
+EOC
+
+my $q = new CGI;
+
+print $q->header( -charset => 'UTF-8' );
+print $q->start_html(
+    -title  => $0,
+    -style  => { -code => $css },
+    -onload => q{document.forms['passwd'].elements['user'].focus();}
+  ),
+  $q->h3( { -id => 'header' }, 'Befristete WebDAV Zugänge einrichten' ),
+  $q->hr;
+
+my $p;
+$p->{$_} = $q->param($_) for qw(user expiry add del);
+
+print $q->start_form( -id => 'passwd' ),
+  $q->table(
+    $q->Tr( $q->td('Nutzername'), $q->td( $q->textfield('user') ) ),
+    $q->Tr(
+        $q->td('Gültigkeitsdauer in Tagen (default: 1)'),
+        $q->td( $q->textfield('expiry') )
+    ),
+    $q->Tr(
+        $q->td( $q->submit( { -name => 'add', -value => 'Anlegen' } ) ),
+        $q->td( $q->submit( { -name => 'del', -value => 'Löschen' } ) )
+    )
+  ),
+  $q->end_form;
+
+my $doit = 0;
+
+my $conf = readconfig or die "Can't readconfig";
+
+if ( defined $p->{add} and $p->{add} ne '' ) {
+
+    print $q->hr;
+    my @cmd = ( qw(sudo ius-dav-htuseradd -u), $p->{user} );
+    push @cmd, '-e', $p->{expiry}
+      if defined $p->{expiry} and $p->{expiry} ne '';
+
+    if ( my $pass = qx(@cmd) ) {
+
+        my $url = "$conf->{dav_base_remote}/$p->{user}";
+
+        chomp $pass;
+
+        print $q->table(
+            $q->Tr(
+                $q->td('Url:'), $q->td( $q->a( { -href => $url }, $url ) )
+            ),
+            $q->Tr( $q->td('Passwort:'), $q->td($pass) )
+        );
+
+    }
+    else {
+        print $q->p('Something went wrong');
+    }
+
+}
+elsif ( defined $p->{del} and $p->{del} ne '' ) {
+
+    my @cmd = ( qw(sudo ius-dav-htuserdel -u), $p->{user} );
+    print $q->hr, $q->p('Something went wrong') if system @cmd;
+
+}
+
+print $q->hr, $q->end_html;
--- a/dav-htpasswd.conf.ex	Wed Jul 13 13:41:08 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-expiry 	    = 1                         # expire user after this many days per default
-expiry_min  = 1                         # don't accept arguments less than expiry_min to the --expiry option
-expiry_max  = 56                        # don't accept arguments greater than expiry_max to the --expiry option
-
-dav_base    = /srv/dav                  # user directories will be placed below dav_base
-htpasswd    = /etc/apache2/htpasswd     # where to place/look for the htpasswd
-conf_d      = /etc/apache2/dav.d        # where to place/look for configuration snippets
-
-www_user    = www-data                  # webserver user
-www_group   = www-data                  # webserver group
-
-master_user = david                     # master user with access to all directories (optional)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ius-dav-htpasswd.conf.ex	Tue Jul 19 09:08:12 2011 +0200
@@ -0,0 +1,33 @@
+# expire user after this many days per default
+expiry 	        = 1
+
+# don't accept arguments less than expiry_min to the --expiry option
+expiry_min      = 1
+
+# don't accept arguments greater than expiry_max to the --expiry option
+expiry_max      = 56
+
+
+# user directories will be placed below dav_base_local
+dav_base_local  = /srv/ius-dav
+
+# the username will be prefixed with dav_base_remote when displaying the uri
+# for the directory of a newly created user (you can omit the trailing slash)
+dav_base_remote = https://hostname/
+
+# where to place/look for the htpasswd; note that we may want a different
+# htpasswd file in the same directory to limit access to the admin cgi script
+htpasswd        = /usr/local/etc/ius-dav-htpasswd/htpasswd.dav
+
+# where to place/look for configuration snippets
+conf_d          = /usr/local/etc/ius-dav-htpasswd/apache.d
+
+
+# webserver user
+www_user        = www-data
+
+# webserver group
+www_group       = www-data
+
+# master user with access to all directories (optional)
+master_user     = davius
--- a/lib/Ius/Dav/Htpasswd.pm	Wed Jul 13 13:41:08 2011 +0200
+++ b/lib/Ius/Dav/Htpasswd.pm	Tue Jul 19 09:08:12 2011 +0200
@@ -32,14 +32,14 @@
 
 BEGIN {
 
-    our ($VERSION, @ISA, @EXPORT_OK);
+    our ( $VERSION, @ISA, @EXPORT_OK );
     use Exporter;
 
     # set the version for version checking
     $VERSION = 0.1;
 
-    @ISA    = qw(Exporter);
-    @EXPORT_OK = qw(readconfig mkpasswd useradd userdel usage);
+    @ISA       = qw(Exporter);
+    @EXPORT_OK = qw(readconfig mkpasswd useradd userdel userexpiry usage);
 }
 
 sub usage {
@@ -47,37 +47,43 @@
     use Pod::Usage;
     use Pod::Find qw(pod_where);
 
-    pod2usage( -input => pod_where({-inc => 1}, __PACKAGE__), @_ );
+    pod2usage( -input => pod_where( { -inc => 1 }, __PACKAGE__ ), @_ );
 
 }
 
 sub readconfig {
 
-    my $conf = new AppConfig(qw(
-        expiry=i
-        expiry_min=i
-        expiry_max=i
-        dav_base=s
-        htpasswd=s
-        conf_d=s
-        www_user=s
-        www_group=s
-        master_user=s)
+    my $conf = new AppConfig(
+        qw(
+          expiry=i
+          expiry_min=i
+          expiry_max=i
+          dav_base_local=s
+          dav_base_remote=s
+          htpasswd=s
+          conf_d=s
+          www_user=s
+          www_group=s
+          master_user=s)
     ) or die 'Failed to read config!';
-    $conf->file($_) for grep -e, map "$_/dav-htpasswd.conf", qw(/etc /usr/local/etc ~ .);
+    $conf->file($_)
+      for grep -e, map "$_/ius-dav-htpasswd.conf",
+      qw(/etc/ius-dav-htpasswd /usr/local/etc/ius-dav-htpasswd ~/.ius-dav-htpasswd ./ius-dav-htpasswd);
     return { $conf->varlist('.') };
 
 }
 
 sub validate {
 
-    my ($conf, $user, $expiry) = @_;
+    my ( $conf, $user, $expiry ) = @_;
 
-    return unless $user =~ /^[[:alpha:]_]+$/; 
+    return unless $user =~ /^[[:alpha:]_]+$/;
 
-    if (defined $expiry) {
+    if ( defined $expiry ) {
         return unless $expiry =~ /^[0-9]+$/;
-        return unless $expiry >= $conf->{expiry_min} and $expiry <= $conf->{expiry_max};
+        return
+          unless $expiry >= $conf->{expiry_min}
+              and $expiry <= $conf->{expiry_max};
     }
 
     return 1;
@@ -86,41 +92,42 @@
 
 sub useradd {
 
-    my ($conf, $user, $pass, $expiry) = @_;
+    my ( $conf, $user, $pass, $expiry ) = @_;
 
-    for (qw(expiry expiry_min expiry_max dav_base htpasswd conf_d www_user www_group)) {
+    for (
+        qw(expiry expiry_min expiry_max dav_base_local htpasswd conf_d www_user www_group)
+      )
+    {
         die "Can't determine '$_' - please check configuration"
-            unless defined $conf->{$_};
+          unless defined $conf->{$_};
     }
 
     $expiry = $conf->{expiry} unless defined $expiry and $expiry ne '';
     die 'Invalid input' unless validate $conf, $user, $expiry;
 
-    my $at_cmd = "at now + " . 24 * 60 * $expiry . " minutes";
-    open AT, "|$at_cmd"
-        or die "Can't open AT, '|$at_cmd': $!";
-    print AT "dav-htuserdel";
-    close AT;
-
-    my $user_dir = "$conf->{dav_base}/$user";
+    my $user_dir = "$conf->{dav_base_local}/$user";
     mkdir "$user_dir" or die "Can't mkdir '$user_dir': $!";
 
-    my ($www_user, $www_group) = @{$conf}{qw(www_user www_group)};
-    my $www_uid = getpwnam $www_user or die "Can't getpwnam '$www_user'";
+    my ( $www_user, $www_group ) = @{$conf}{qw(www_user www_group)};
+    my $www_uid = getpwnam $www_user  or die "Can't getpwnam '$www_user'";
     my $www_gid = getgrnam $www_group or die "Can't getgrnam '$www_group'";
-    chown $www_uid, $www_gid, "$user_dir" or die "Can't chown, '$www_uid', '$www_gid', '$user_dir': $!";
+    chown $www_uid, $www_gid, "$user_dir"
+      or die "Can't chown, '$www_uid', '$www_gid', '$user_dir': $!";
 
     my $htpasswd_file = $conf->{htpasswd};
-    unless (-e $htpasswd_file ) {
+    unless ( -e $htpasswd_file ) {
         open H, '>>', $htpasswd_file or die "Can't create '$htpasswd_file': $!";
         close H;
     }
+
     my $htpasswd = new Apache::Htpasswd $htpasswd_file;
-    $htpasswd->htpasswd($user, $pass)
-        or die $htpasswd->error;
+    $htpasswd->htpasswd( $user, $pass )
+      or die $htpasswd->error;
+    $htpasswd->writeInfo( $user, time + 24 * 60 * 60 * $expiry )
+      or die $htpasswd->error;
 
     my $master_user = $conf->{master_user};
-    my $conf_file = "$conf->{conf_d}/$user.conf";
+    my $conf_file   = "$conf->{conf_d}/$user.conf";
     open C, '>', $conf_file or die "Can't open '$conf_file': $!";
     print C <<EOC;
 <Directory "$user_dir">
@@ -137,7 +144,7 @@
     close C;
 
     0 == system qw(apache2ctl graceful)
-        or die "Can't 'apache2ctl graceful'!";
+      or die "Can't 'apache2ctl graceful'!";
 
     return $pass;
 
@@ -147,36 +154,65 @@
 
 sub userdel {
 
-    my ($conf, $user) = @_;
+    my ( $conf, $user ) = @_;
 
-    my $rc;
+    my $rc = 0;
 
-    for (qw(dav_base htpasswd conf_d)) {
+    for (qw(dav_base_local htpasswd conf_d)) {
         die "Can't determine '$_' - please check configuration"
-            unless defined $conf->{$_};
+          unless defined $conf->{$_};
     }
 
     # avoid 'Found = in conditional, should be ==' warnings
     no warnings qw(syntax);
-    my $user_dir = "$conf->{dav_base}/$user";
+    my $user_dir = "$conf->{dav_base_local}/$user";
     my $err;
-    rmtree($user_dir, error => $err)
-        or $rc = -1 and defined $err and warn "Errors occurred during rmtree '$user_dir': ", @{$err};
+    rmtree( $user_dir, error => $err )
+      or $rc = -1
+      and defined $err
+      and warn "Errors occurred during rmtree '$user_dir': ", @{$err};
 
     my $htpasswd_file = $conf->{htpasswd};
-    my $htpasswd = new Apache::Htpasswd $htpasswd_file;
+    my $htpasswd      = new Apache::Htpasswd $htpasswd_file;
     $htpasswd->htDelete($user)
-        or $rc = -1 and warn "Can't htdelete '$user': ", $htpasswd->error;
+      or $rc = -1 and warn "Can't htdelete '$user': ", $htpasswd->error;
 
     my $conf_file = "$conf->{conf_d}/$user.conf";
     unlink $conf_file
-        or $rc = -1 and warn "Can't unlink '$conf_file': $!";
-
-    # maybe TODO: remove at job if it still exists (record job# during #
-    # 'useradd'?)
+      or $rc = -1 and warn "Can't unlink '$conf_file': $!";
 
     0 == system qw(apache2ctl graceful)
-        or $rc =-1 and warn "Can't 'apache2ctl graceful'!";
+      or $rc = -1 and warn "Can't 'apache2ctl graceful'!";
+
+    return $rc;
+
+}
+
+sub userexpiry {
+
+    my ($conf) = @_;
+
+    for (qw(htpasswd)) {
+        die "Can't determine '$_' - please check configuration"
+          unless defined $conf->{$_};
+    }
+
+    my $htpasswd_file = $conf->{htpasswd};
+    my $htpasswd      = new Apache::Htpasswd $htpasswd_file;
+    my @users         = $htpasswd->fetchUsers
+      or die "Can't fetch htuser list: ", $htpasswd->error;
+    my $now = time;
+
+    for my $u (@users) {
+        if ( my $e = $htpasswd->fetchInfo($u) ) {
+            userdel( $conf, $u )
+              or warn "Can't 'userdel $conf, $u'\n"
+              if $now >= $e;
+        }
+        else {
+            warn "Can't get expiry for '$u': ", $htpasswd->error, "\n";
+        }
+    }
 
 }
 
@@ -188,47 +224,53 @@
 
 =head1 NAME
 
-dav-useradd
+ius-dav-useradd
 
-dav-useradd.cgi
+ius-dav-useradd.cgi
 
-dav-userdel
+ius-dav-userdel
 
-Ius::Dav::Htpasswd - Add dav users to htpasswd and remove them automatically
-after expiration or manually.
+ius-dav-userexpiry
+
+Ius::Dav::Htpasswd - Add dav users to htpasswd and remove them after
+expiration.
 
 =head1 SYNOPSIS
 
-dav-useradd  -u|--user user
-            [-e|--expiry expiry]
+ius-dav-useradd
+   -u|--user user
+  [-e|--expiry expiry]
 
-dav-userdel -u|--user user
+ius-dav-userdel
+   -u|--user user
+
+ius-dav-userexpiry
 
 common options
-
-            -m|--man
-            -h|--help
+  [-m|--man]
+  [-h|--help]
 
 =head1 DESCRIPTION
 
-=head2 dav-useradd
+=head2 ius-dav-useradd
 
-Add an at job to remove the user later. Make a directory for the user. Chown
-that directory to the webserver user and group. Add the user to an htpasswd
-file. Place a config snippet for the users directory inside a directory (which
-is included from the apache config). Reload apache (or maybe restart is
-required).
+Make a directory for the user. Chown that directory to the webserver user and
+group. Add the user to an htpasswd file. Add expiry information to that
+htpasswd file. Place a config snippet for the users directory inside a
+directory (which is included from the apache config). Reload apache.
 
-=head2 dav-useradd.cgi
+=head2 ius-dav-useradd.cgi
 
-Is supposed to do the same as dav-useradd.
+This is a CGI Wrapper around ius-dav-useradd.
 
-=head2 dav-userdel
+=head2 ius-dav-userdel
 
 Removes the directory of the user. Removes the user from the htpasswd file.
-Removes the config snippet for the users directory. Removes the at job that is
-supposed to remove the user if it still exists. Reload apache (or maybe restart
-is required).
+Removes the config snippet for the users directory. Reload apache.
+
+=head2 ius-dav-userexpiry
+
+Check the htpasswd file and run deletion for any expired users found.
 
 =head1 OPTIONS
 
@@ -262,7 +304,8 @@
 
 =head1 REQUIRES
 
-at from the 'at' job scheduler package. Several perl modules (should be installed automatically).
+Several perl modules (should be installed automatically). Some kind of cron
+daemon to run the user expiry is recommended.
 
 =head1 AUTHOR
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ssl-vhost-apache-example.conf	Tue Jul 19 09:08:12 2011 +0200
@@ -0,0 +1,62 @@
+# replace $PREFIX (usually with /usr or /usr/local)
+<IfModule mod_ssl.c>
+# note that you will need a wildcard certificate if you want namebased virtual
+# hosts + ssl
+<VirtualHost *:443>
+
+	DocumentRoot "$PREFIX/lib/ius-dav-htpasswd/cgi-bin"
+	AssignUserId "ius-dav-htpasswd" "ius-dav-htpasswd"
+
+	ServerAdmin webmaster@localhost
+	ServerName ius-dav-htpasswd.domain.tld
+	
+	ErrorLog /var/log/apache2/error.log
+	LogLevel warn
+	CustomLog /var/log/apache2/ius-dav-htpasswd.domain.tld/ssl_access.log combined
+
+	SSLEngine on
+	SSLCertificateFile    /etc/ssl/certs/wildcard-certificate.pem
+	SSLCertificateKeyFile /etc/ssl/private/key-for-wildcard-certificate.pem
+
+	#   SSL Protocol Adjustments:
+	#   The safe and default but still SSL/TLS standard compliant shutdown
+	#   approach is that mod_ssl sends the close notify alert but doesn't wait for
+	#   the close notify alert from client. When you need a different shutdown
+	#   approach you can use one of the following variables:
+	#   o ssl-unclean-shutdown:
+	#     This forces an unclean shutdown when the connection is closed, i.e. no
+	#     SSL close notify alert is send or allowed to received.  This violates
+	#     the SSL/TLS standard but is needed for some brain-dead browsers. Use
+	#     this when you receive I/O errors because of the standard approach where
+	#     mod_ssl sends the close notify alert.
+	#   o ssl-accurate-shutdown:
+	#     This forces an accurate shutdown when the connection is closed, i.e. a
+	#     SSL close notify alert is send and mod_ssl waits for the close notify
+	#     alert of the client. This is 100% SSL/TLS standard compliant, but in
+	#     practice often causes hanging connections with brain-dead browsers. Use
+	#     this only for browsers where you know that their SSL implementation
+	#     works correctly.
+	#   Notice: Most problems of broken clients are also related to the HTTP
+	#   keep-alive facility, so you usually additionally want to disable
+	#   keep-alive for those clients, too. Use variable "nokeepalive" for this.
+	#   Similarly, one has to force some clients to use HTTP/1.0 to workaround
+	#   their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and
+	#   "force-response-1.0" for this.
+	BrowserMatch ".*MSIE.*" \
+		nokeepalive ssl-unclean-shutdown \
+		downgrade-1.0 force-response-1.0
+
+	# local cgi scripts
+	<Directory "$PREFIX/lib/ius-dav-htpasswd/cgi-bin">
+	    Order Deny,Allow
+	    Deny from all
+	    Allow from 127.0.0.0/8
+	    AuthType "Basic"
+	    AuthName "ius-dav-htpasswd"
+	    AuthUserFile "/path/to/ius-dav-admin-htpasswd"
+	    Require valid-user
+	    SetHandler cgi-script
+	</Directory>
+
+</VirtualHost>
+</IfModule>