LDAP user login support
authorJamie Cameron <jcameron@webmin.com>
Fri, 17 Sep 2010 00:08:39 +0000 (17:08 -0700)
committerJamie Cameron <jcameron@webmin.com>
Fri, 17 Sep 2010 00:08:39 +0000 (17:08 -0700)
acl/acl-lib.pl
miniserv.pl
web-lib-funcs.pl

index 6461fda..9334f82 100755 (executable)
@@ -97,7 +97,7 @@ close(PWFILE);
 
 # If a user DB is enabled, get users from it too
 if ($miniserv{'userdb'}) {
-       my ($dbh, $proto) = &connect_userdb($miniserv{'userdb'});
+       my ($dbh, $proto, $prefix, $args) =&connect_userdb($miniserv{'userdb'});
        &error("Failed to connect to user database : $dbh") if (!ref($dbh));
        if ($proto eq "mysql" || $proto eq "postgresql") {
                # Fetch users with SQL
@@ -131,7 +131,29 @@ if ($miniserv{'userdb'}) {
                }
        elsif ($proto eq "ldap") {
                # Find users with LDAP query
-               # XXX
+               my $rv = $dbh->search(
+                       base => $prefix,
+                       filter => '(objectClass='.$args->{'userclass'}.')',
+                       scope => 'one');
+               if (!$rv || $rv->code) {
+                       &error("Failed to search users : ".
+                               ($rv ? $rv->error : "Unknown error"));
+                       }
+               foreach my $l ($rv->all_entries) {
+                       my $u = { 'name' => $l->get_value('cn'),
+                                 'pass' => $l->get_value('webminPass'),
+                                 'proto' => $proto,
+                                 'id' => $l->dn() };
+                       foreach my $la ($l->get_value('webminAttr')) {
+                               my ($attr, $value) = split(/=/, $la, 2);
+                               if ($attr eq "olds" || $attr eq "ownmods") {
+                                       $value = [ split(/\s+/, $value) ];
+                                       }
+                               $u->{$attr} = $value;
+                               }
+                       $u->{'modules'} = [ $l->get_value('webminModule') ];
+                       push(@rv, $u);
+                       }
                }
        &disconnect_userdb($miniserv{'userdb'}, $dbh);
        }
@@ -227,6 +249,7 @@ if ($miniserv{'userdb'}) {
                                if ($attr eq "members" || $attr eq "ownmods") {
                                        $value = [ split(/\s+/, $value) ];
                                        }
+                               $g->{$attr} = $value;
                                }
                        $g->{'modules'} = [ $l->get_value('webminModule') ];
                        push(@rv, $g);
@@ -279,7 +302,7 @@ my @mods = &list_modules();
 
 if ($miniserv{'userdb'} && !$miniserv{'userdb_addto'}) {
        # Adding to user database
-       my ($dbh, $proto) = &connect_userdb($miniserv{'userdb'});
+       my ($dbh, $proto, $prefix, $args) =&connect_userdb($miniserv{'userdb'});
         &error("Failed to connect to user database : $dbh") if (!ref($dbh));
        if ($proto eq "mysql" || $proto eq "postgresql") {
                # Add user with SQL
@@ -309,7 +332,33 @@ if ($miniserv{'userdb'} && !$miniserv{'userdb_addto'}) {
                }
        elsif ($proto eq "ldap") {
                # Add user to LDAP
-               # XXX
+               my $dn = "cn=".$user{'name'}.",".$prefix;
+               my @attrs = ( "objectClass", $args->{'userclass'},
+                             "cn", $user{'name'},
+                             "webminPass", $user{'pass'} );
+               my @webminattrs;
+               foreach my $attr (keys %user) {
+                       next if ($attr eq "name" || $attr eq "pass" ||
+                                $attr eq "modules");
+                       my $value = $user{$attr};
+                       if ($attr eq "olds" || $attr eq "ownmods") {
+                               $value = join(" ", @$value);
+                               }
+                       push(@webminattrs,
+                            defined($value) ? $attr."=".$value : $attr);
+                       }
+               if (@webminattrs) {
+                       push(@attrs, "webminAttr", \@webminattrs);
+                       }
+               if (@{$user{'modules'}}) {
+                       push(@attrs, "webminModule", $user{'modules'});
+                       }
+               my $rv = $dbh->add($dn, attr => \@attrs);
+               if (!$rv || $rv->code) {
+                       &error("Failed to add user to LDAP : ".
+                              ($rv ? $rv->error : "Unknown error"));
+                       }
+
                }
        &disconnect_userdb($miniserv{'userdb'}, $dbh);
        $user{'proto'} = $proto;
@@ -450,7 +499,40 @@ if ($user{'proto'}) {
                        }
                }
        elsif ($proto eq "ldap") {
-               # XXX update in ldap
+               # Rename in LDAP if needed
+               if ($user{'name'} ne $username) {
+                       my $newdn = $user{'id'};
+                       $newdn =~ s/^cn=\Q$username\E,/cn=$user{'name'},/;
+                       my $rv = $dbh->moddn($user{'id'},
+                                            newrdn => "cn=$user{'name'}");
+                       if (!$rv || $rv->code) {
+                               &error("Failed to rename user : ".
+                                      ($rv ? $rv->error : "Unknown error"));
+                               }
+                       $user{'id'} = $newdn;
+                       }
+
+               # Re-save all the attributes
+               my @attrs = ( "cn", $user{'name'},
+                             "webminPass", $user{'pass'} );
+               my @webminattrs;
+               foreach my $attr (keys %user) {
+                       next if ($attr eq "name" || $attr eq "desc" ||
+                                $attr eq "modules");
+                       my $value = $user{$attr};
+                       if ($attr eq "olds" || $attr eq "ownmods") {
+                               $value = join(" ", @$value);
+                               }
+                       push(@webminattrs,
+                            defined($value) ? $attr."=".$value : $attr);
+                       }
+               push(@attrs, "webminAttr", \@webminattrs);
+               push(@attrs, "webminModule", $user{'modules'});
+               my $rv = $dbh->modify($user{'id'}, replace => { @attrs });
+               if (!$rv || $rv->code) {
+                       &error("Failed to modify user : ".
+                              ($rv ? $rv->error : "Unknown error"));
+                       }
                }
        }
 else {
@@ -671,7 +753,7 @@ if ($miniserv{'session'}) {
 
 if ($miniserv{'userdb'}) {
        # Also delete from user database
-       my ($dbh, $proto) = &connect_userdb($miniserv{'userdb'});
+       my ($dbh, $proto, $prefix, $args) =&connect_userdb($miniserv{'userdb'});
        &error("Failed to connect to user database : $dbh") if (!ref($dbh));
        if ($proto eq "mysql" || $proto eq "postgresql") {
                # Find the user with SQL query
@@ -709,7 +791,25 @@ if ($miniserv{'userdb'}) {
                }
        elsif ($proto eq "ldap") {
                # Find user with LDAP query
-               # XXX
+               my $rv = $dbh->search(
+                       base => $prefix,
+                       filter => '(&(cn='.$username.')(objectClass='.
+                                 $args->{'userclass'}.'))',
+                       scope => 'one');
+               if (!$rv || $rv->code) {
+                       &error("Failed to find user : ".
+                              ($rv ? $rv->error : "Unknown error"));
+                       }
+               my ($user) = $rv->all_entries;
+
+               if ($user) {
+                       # Delete the user from LDAP
+                       my $rv = $dbh->delete($user->dn());
+                       if (!$rv || $rv->code) {
+                               &error("Failed to delete user : ".
+                                      ($rv ? $rv->error : "Unknown error"));
+                               }
+                       }
                }
        &disconnect_userdb($miniserv{'userdb'}, $dbh);
        }
@@ -980,7 +1080,8 @@ if ($miniserv{'userdb'}) {
                # Find group with LDAP query
                my $rv = $dbh->search(
                        base => $prefix,
-                       filter => '(cn='.$groupname.')',
+                       filter => '(&(cn='.$groupname.')(objectClass='.
+                                  $args->{'groupclass'}.'))',
                        scope => 'one');
                if (!$rv || $rv->code) {
                        &error("Failed to find group : ".
index 50aca78..c42c2ed 100755 (executable)
@@ -4099,15 +4099,16 @@ if ($config{'userdb'}) {
                return $get_user_details_cache{$username};
                }
        print DEBUG "get_user_details: Connecting to user database\n";
-       my ($dbh, $proto) = &connect_userdb($config{'userdb'});
+       my ($dbh, $proto, $prefix, $args) = &connect_userdb($config{'userdb'});
        my $user;
+       my %attrs;
        if (!ref($dbh)) {
                print DEBUG "get_user_details: Failed : $dbh\n";
                print STDERR "Failed to connect to user database : $dbh\n";
                }
        elsif ($proto eq "mysql" || $proto eq "postgresql") {
                # Fetch user ID and password with SQL
-               print DEBUG "get_user_details: Looking for $username\n";
+               print DEBUG "get_user_details: Looking for $username in SQL\n";
                my $cmd = $dbh->prepare(
                        "select id,pass from webmin_user where name = ?");
                if (!$cmd || !$cmd->execute($username)) {
@@ -4138,13 +4139,48 @@ if ($config{'userdb'}) {
                          'id' => $id,
                          'pass' => $pass,
                          'proto' => $proto };
-               my %attrs;
                while(my ($attr, $value) = $cmd->fetchrow()) {
                        $attrs{$attr} = $value;
                        }
+               $cmd->finish();
+               }
+       elsif ($proto eq "ldap") {
+               # Fetch user DN with LDAP
+               print DEBUG "get_user_details: Looking for $username in LDAP\n";
+               my $rv = $dbh->search(
+                       base => $prefix,
+                       filter => '(&(cn='.$username.')(objectClass='.
+                                  $args->{'userclass'}.'))',
+                       scope => 'one');
+               if (!$rv || $rv->code) {
+                       print STDERR "Failed to lookup user : ",
+                                    ($rv ? $rv->error : "Unknown error"),"\n";
+                       return undef;
+                       }
+               my ($u) = $rv->all_entries();
+               if (!$u) {
+                       &disconnect_userdb($config{'userdb'}, $dbh);
+                        $get_user_details_cache{$username} = undef;
+                       print DEBUG "get_user_details: User not found\n";
+                        return undef;
+                       }
+
+               # Extract attributes
+               $user = { 'name' => $username,
+                         'id' => $u->dn(),
+                         'pass' => $u->get_value('pass'),
+                         'proto' => $proto };
+               my %attrs;
+               foreach my $la ($u->get_value('webminAttr')) {
+                       my ($attr, $value) = split(/=/, $la, 2);
+                       $attrs{$attr} = $value;
+                       }
+               }
+
+       # Convert DB attributes into user object fields
+       if ($user) {
                print DEBUG "get_user_details: got ",scalar(keys %attrs),
                            " attributes\n";
-               $cmd->finish();
                $user->{'certs'} = $attrs{'cert'};
                if ($attrs{'allow'}) {
                        $user->{'allow'} = $config{'alwaysresolve'} ?
@@ -4169,10 +4205,6 @@ if ($config{'userdb'}) {
                $user->{'temppass'} = $attrs{'temppass'};
                $user->{'preroot'} = $attrs{'theme'};
                }
-       elsif ($proto eq "ldap") {
-               # Fetch with LDAP
-               # XXX
-               }
        &disconnect_userdb($config{'userdb'}, $dbh);
        $get_user_details_cache{$user->{'name'}} = $user;
        return $user;
@@ -4242,7 +4274,7 @@ if ($proto eq "mysql") {
        my $dbh = $drh->connect($cstr, $user, $pass, { });
        $dbh || return &text('sql_emysqlconnect', $drh->errstr);
        print DEBUG "connect_userdb: Connected OK\n";
-       return wantarray ? ($dbh, $proto) : $dbh;
+       return wantarray ? ($dbh, $proto, $prefix, $args) : $dbh;
        }
 elsif ($proto eq "postgresql") {
        # Connect to PostgreSQL with DBI
@@ -4255,7 +4287,7 @@ elsif ($proto eq "postgresql") {
        my $dbh = $drh->connect($cstr, $user, $pass);
        $dbh || return &text('sql_epostgresqlconnect', $drh->errstr);
        print DEBUG "connect_userdb: Connected OK\n";
-       return wantarray ? ($dbh, $proto) : $dbh;
+       return wantarray ? ($dbh, $proto, $prefix, $args) : $dbh;
        }
 elsif ($proto eq "ldap") {
        # Connect with perl LDAP module
@@ -4290,7 +4322,7 @@ elsif ($proto eq "ldap") {
                return &text('sql_eldaplogin', $user,
                             $mesg ? $mesg->error : "Unknown error");
                }
-       return wantarray ? ($ldap, $proto) : $ldap;
+       return wantarray ? ($ldap, $proto, $prefix, $args) : $ldap;
        }
 else {
        return "Unknown protocol $proto";
index 741fa7c..93eb522 100755 (executable)
@@ -1720,7 +1720,8 @@ if (!%main::acl_hash_cache) {
 
        # Read from user DB
        my $userdb = &get_userdb_string();
-       my ($dbh, $proto) = $userdb ? &connect_userdb($userdb) : ( );
+       my ($dbh, $proto, $prefix, $args) =
+               $userdb ? &connect_userdb($userdb) : ( );
        if (ref($dbh)) {
                if ($proto eq "mysql" || $proto eq "postgresql") {
                        # Select usernames and modules from SQL DB
@@ -1738,7 +1739,24 @@ if (!%main::acl_hash_cache) {
                        $cmd->finish() if ($cmd);
                        }
                elsif ($proto eq "ldap") {
-                       # XXX read from LDAP
+                       # Find users in LDAP
+                       # XXX limit attrs?
+                       my $rv = $dbh->search(
+                               base => $prefix,
+                               filter => '(objectClass='.
+                                         $args->{'userclass'}.')',
+                               scope => 'one');
+                       if ($rv && !$rv->code) {
+                               foreach my $u ($rv->all_entries) {
+                                       my $user = $u->get_value('cn');
+                                       my @mods =$u->get_value('webminModule');
+                                       foreach my $m (@mods) {
+                                               $main::acl_hash_cache{$user,
+                                                                     $m}++;
+                                               }
+                                       $main::acl_array_cache{$user} = \@mods;
+                                       }
+                               }
                        }
                &disconnect_userdb($userdb, $dbh);
                }
@@ -3321,7 +3339,7 @@ elsif ($u ne '') {
        if ($userdb && ($u ne $base_remote_user || $remote_user_proto)) {
                # Look for this user in the user/group DB, if one is defined
                # and if the user might be in the DB
-               my ($dbh, $proto) = &connect_userdb($userdb);
+               my ($dbh, $proto, $prefix, $args) = &connect_userdb($userdb);
                ref($dbh) || &error(&text('euserdbacl', $dbh));
                if ($proto eq "mysql" || $proto eq "postgresql") {
                        # Find the user in the SQL DB
@@ -3347,8 +3365,38 @@ elsif ($u ne '') {
                                }
                        }
                elsif ($proto eq "ldap") {
-                       # Fetch ACLs from LDAP
-                       # XXX
+                       # Find user in LDAP
+                       my $rv = $dbh->search(
+                               base => $prefix,
+                               filter => '(&(cn='.$u.')(objectClass='.
+                                         $args->{'userclass'}.'))',
+                               scope => 'one');
+                       if (!$rv || $rv->code) {
+                               &error(&text('euserdbacl',
+                                    $rv ? $rv->error : "Unknown error"));
+                               }
+                       my ($user) = $rv->all_entries;
+
+                       # Find ACL sub-object for the module
+                       my $ldapm = $m || "global";
+                       if ($user) {
+                               my $rv = $dbh->search(
+                                       base => $user->dn(),
+                                       filter => '(cn='.$ldapm.')',
+                                       scope => 'one');
+                               if (!$rv || $rv->code) {
+                                       &error(&text('euserdbacl',
+                                          $rv ? $rv->error : "Unknown error"));
+                                       }
+                               my ($acl) = $rv->all_entries;
+                               if ($acl) {
+                                       foreach my $av ($acl->get_value(
+                                                               'webminAcl')) {
+                                               my ($a, $v) = split(/=/, $av,2);
+                                               $rv{$a} = $v;
+                                               }
+                                       }
+                               }
                        }
                &disconnect_userdb($userdb, $dbh);
                }
@@ -3420,7 +3468,8 @@ if ($userdb) {
                # Find group in LDAP
                my $rv = $dbh->search(
                        base => $prefix,
-                       filter => '(cn='.$g.')',
+                       filter => '(&(cn='.$g.')(objectClass='.
+                                  $args->{'groupclass'}.'))',
                        scope => 'one');
                if (!$rv || $rv->code) {
                        &error(&text('egroupdbacl',
@@ -3429,10 +3478,11 @@ if ($userdb) {
                my ($group) = $rv->all_entries;
 
                # Find ACL sub-object for the module
+               my $ldapm = $m;
                if ($group) {
                        my $rv = $dbh->search(
                                base => $group->dn(),
-                               filter => '(cn='.$m.')',
+                               filter => '(cn='.$ldapm.')',
                                scope => 'one');
                        if (!$rv || $rv->code) {
                                &error(&text('egroupdbacl',
@@ -3497,7 +3547,7 @@ my $userdb = &get_userdb_string();
 my $foundindb = 0;
 if ($userdb && ($u ne $base_remote_user || $remote_user_proto)) {
        # Look for this user in the user/group DB
-       my ($dbh, $proto) = &connect_userdb($userdb);
+       my ($dbh, $proto, $prefix, $args) = &connect_userdb($userdb);
        ref($dbh) || &error(&text('euserdbacl', $dbh));
        if ($proto eq "mysql" || $proto eq "postgresql") {
                # Find the user in the SQL DB
@@ -3532,8 +3582,53 @@ if ($userdb && ($u ne $base_remote_user || $remote_user_proto)) {
                        }
                }
        elsif ($proto eq "ldap") {
-               # Update ACLs in LDAP
-               # XXX
+               # Find the user in LDAP
+               my $rv = $dbh->search(
+                       base => $prefix,
+                       filter => '(&(cn='.$u.')(objectClass='.
+                                  $args->{'userclass'}.'))',
+                       scope => 'one');
+               if (!$rv || $rv->code) {
+                       &error(&text('euserdbacl',
+                                    $rv ? $rv->error : "Unknown error"));
+                       }
+               my ($user) = $rv->all_entries;
+
+               if ($user) {
+                       # Find the ACL sub-object for the module
+                       my $ldapm = $m || "global";
+                       my $rv = $dbh->search(
+                               base => $user->dn(),
+                               filter => '(cn='.$ldapm.')',
+                               scope => 'one');
+                       if (!$rv || $rv->code) {
+                               &error(&text('euserdbacl',
+                                    $rv ? $rv->error : "Unknown error"));
+                               }
+                       my ($acl) = $rv->all_entries;
+
+                       my @attrs;
+                       foreach my $a (keys %{$_[0]}) {
+                               push(@attrs, "webminAclEntry",
+                                            $a."=".$_[0]->{$a});
+                               }
+                       if ($acl) {
+                               # Update attributes
+                               $rv = $dbh->modify($acl->dn(),
+                                                  replace => { @attrs });
+                               }
+                       else {
+                               # Add a sub-object
+                               push(@attrs, "cn", $ldapm,
+                                            "objectClass", "webminAcl");
+                               $rv = $dbh->add("cn=".$ldapm.",".$user->dn(),
+                                               attr => \@attrs);
+                               }
+                       if (!$rv || $rv->code) {
+                               &error(&text('euserdbacl2',
+                                    $rv ? $rv->error : "Unknown error"));
+                               }
+                       }
                }
        &disconnect_userdb($userdb, $dbh);
        }
@@ -3628,7 +3723,8 @@ if ($userdb) {
                # Find the group in LDAP
                my $rv = $dbh->search(
                        base => $prefix,
-                       filter => '(cn='.$g.')',
+                       filter => '(&(cn='.$g.')(objectClass='.
+                                  $args->{'groupclass'}.'))',
                        scope => 'one');
                if (!$rv || $rv->code) {
                        &error(&text('egroupdbacl',
@@ -3636,11 +3732,12 @@ if ($userdb) {
                        }
                my ($group) = $rv->all_entries;
 
+               my $ldapm = $m;
                if ($group) {
                        # Find the ACL sub-object for the module
                        my $rv = $dbh->search(
                                base => $group->dn(),
-                               filter => '(cn='.$m.')',
+                               filter => '(cn='.$ldapm.')',
                                scope => 'one');
                        if (!$rv || $rv->code) {
                                &error(&text('egroupdbacl',
@@ -3660,9 +3757,9 @@ if ($userdb) {
                                }
                        else {
                                # Add a sub-object
-                               push(@attrs, "cn", $m,
+                               push(@attrs, "cn", $ldapm,
                                             "objectClass", "webminAcl");
-                               $rv = $dbh->add("cn=".$m.",".$group->dn(),
+                               $rv = $dbh->add("cn=".$ldapm.",".$group->dn(),
                                                attr => \@attrs);
                                }
                        if (!$rv || $rv->code) {