|
From: Anthony C. <ac...@an...> - 2006-07-19 16:53:43
|
Hello, webadmin-list!
I've currently got 3 deployments of Webmin (1.290, 1.260, and 1.250)
in conjuction with Samba 3.0.22 on FreeBSD 6.0-RELEASE-p4.
I recently performed a successful upgrade from OpenLDAP version 2.2.30
to 2.3.24. After the upgrade was performed, two problems with the
LDAP Users and Groups module have surfaced on each of these
deployments.
1. I can no longer add users.
Initially, each of the deployments give me the same error:
Failed to save user : Failed to add user to LDAP database : object
class 'inetOrgPerson' requires attribute 'sn'
After doing a bit of research, I discovered that most users have
been working around this by configuring "sn: ${USER}" or "sn:
${REAL}" as an additional attribute for new users. Not satisfied
with this solution (since I want the user's actual *surname* to be
in the *surname* attribute), I decided to try upgrading Webmin and
its dependencies and reconfigure the module. So I upgraded the
first box (which is how it became 1.290---it was 1.250 previously,
IIRC), rm'ed /usr/local/etc/webmin/ldap-useradmin/config, re-ran
/usr/local/lib/webmin/setup.sh (to regenerate the base config), and
set it back up. This eliminated the need to specify the sn
attribute, but now I'm seeing this:
Failed to save user : Failed to add user to LDAP database : object
class 'sambaSamAccount' requires attribute 'sambaSID'
Now here's the *really* bizarre thing. If I analyze the traffic
between miniserv.pl and slapd, I see that no sambaSamAccount
attributes are ever being sent over the wire. I can see all of the
objectClasses (posixAccount, shadowAccount, inetLocalMailRecipient,
sambaSamAccount, and inetOrgPerson). The associated attributes for
each of these objectClasses are being delivered, with the exception
of those for sambaSamAccount.
We've been patching Webmin to enable us to use SSHA1 password
hashes as well as use some slightly different SIDs, which AFAIK,
are perfectly legal according to the SMB protocol. I've attached
the (highly experimental and very crude!) patch below. I haven't
tried removing the patch just yet as I've got to turn my attention
to another very crucial project, but I will be doing so next week
sometime.
I've also included my ldap-useradmin config file, which could be
the source of our problems.
2. The NTLM password hash is not being stored.
It would seem that this is another effect of whatever is causing
the problem with issue #1 mentioned previously. The same symptoms
are exhibited: the UserPassword attribute is transmitted, but the
NTPassword and LMPassword attributes are not.
However, if I use /usr/local/sbin/smbldap-passwd (from the
smbldap-tools 0.9.1 package), UnixPassword, NTPassword, and
LMPassword all get updated properly. This is strong evidence that
the problem lies somewhere within Webmin.
We would very much appreciate to hear from somebody regarding this
issue, as one of the deployments normally sees a fair amount of new
users.
Cheers!
--
Anthony Chavez http://anthonychavez.org/
mailto:ac...@an... jabber:ac...@ja...
--8<---------------cut here---------------start------------->8---
diff -ru /home/acc/bak/ldap-useradmin-1.290/config ldap-useradmin/config
--- /home/acc/bak/ldap-useradmin-1.290/config Sat Jul 8 08:26:24 2006
+++ ldap-useradmin/config Sat Jul 8 08:34:23 2006
@@ -3,7 +3,7 @@
samba_class=sambaAccount
imap_class=SuSEeMailObject
imap_folders=old public sent-mail
-md5=0
+pw_encrypt=0
given=0
slappasswd=slappasswd
samba_def=0
diff -ru /home/acc/bak/ldap-useradmin-1.290/config-*-linux ldap-useradmin/config-*-linux
--- /home/acc/bak/ldap-useradmin-1.290/config-*-linux Sat Jul 8 08:26:24 2006
+++ ldap-useradmin/config-*-linux Sat Jul 8 08:34:23 2006
@@ -3,7 +3,7 @@
samba_class=sambaAccount
imap_class=SuSEeMailObject
imap_folders=old public sent-mail
-md5=0
+pw_encrypt=0
given=0
slappasswd=slappasswd
samba_def=0
diff -ru /home/acc/bak/ldap-useradmin-1.290/config-*-linux.orig ldap-useradmin/config-*-linux.orig
--- /home/acc/bak/ldap-useradmin-1.290/config-*-linux.orig Sun Nov 6 16:42:35 2005
+++ ldap-useradmin/config-*-linux.orig Sat Jul 8 08:26:24 2006
@@ -14,3 +14,5 @@
secmode=0
imap_folderalt=1
samba_gclass=sambaGroup
+alias_same=0
+given_class=inetOrgPerson
diff -ru /home/acc/bak/ldap-useradmin-1.290/config-debian-squirrelmail-linux ldap-useradmin/config-debian-squirrelmail-linux
--- /home/acc/bak/ldap-useradmin-1.290/config-debian-squirrelmail-linux Sat Jul 8 08:26:24 2006
+++ ldap-useradmin/config-debian-squirrelmail-linux Sat Jul 8 08:34:23 2006
@@ -3,7 +3,7 @@
samba_class=sambaSamAccount
imap_class=SquirrelMailUser inetLocalMailRecipient
imap_folders=old public sent-mail
-md5=0
+pw_encrypt=0
given=0
slappasswd=slappasswd
maillocaladdress=mailLocalAddress
diff -ru /home/acc/bak/ldap-useradmin-1.290/config-debian-squirrelmail-linux.orig ldap-useradmin/config-debian-squirrelmail-linux.orig
--- /home/acc/bak/ldap-useradmin-1.290/config-debian-squirrelmail-linux.orig Sun Nov 6 16:42:35 2005
+++ ldap-useradmin/config-debian-squirrelmail-linux.orig Sat Jul 8 08:26:24 2006
@@ -19,3 +19,5 @@
shells=fixed,ldap,passwd,shells
secmode=0
samba_gclass=sambaGroup
+alias_same=0
+given_class=inetOrgPerson
diff -ru /home/acc/bak/ldap-useradmin-1.290/config.info ldap-useradmin/config.info
--- /home/acc/bak/ldap-useradmin-1.290/config.info Sat Jul 8 08:26:24 2006
+++ ldap-useradmin/config.info Sat Jul 8 08:34:23 2006
@@ -32,7 +32,7 @@
line6=New user options,11
base_uid=Lowest UID for new users,3,From Users and Groups module
base_gid=Lowest GID for new groups,3,From Users and Groups module
-md5=Encryption method for passwords,1,3-LDAP MD5,1-Unix MD5,0-crypt,2-Plain text
+pw_encrypt=Encryption method for passwords,1,5-LDAP SSHA,4-LDAP SHA,3-LDAP MD5,1-Unix MD5,0-crypt,2-Plain text
shells=Build list of shells from,2,fixed-Builtin list,passwd-System users,shells-/etc/shells
secmode=Show secondary groups on user form?,1,0-Yes,1-No
diff -ru /home/acc/bak/ldap-useradmin-1.290/config.info.ca ldap-useradmin/config.info.ca
--- /home/acc/bak/ldap-useradmin-1.290/config.info.ca Sat Jul 8 08:26:24 2006
+++ ldap-useradmin/config.info.ca Sat Jul 8 08:34:23 2006
@@ -32,7 +32,7 @@
line6=Opcions d'usuari nou,11
base_uid=UID més baix per a usuaris nous,3,Del mòdul d'Usuaris i Grups
base_gid=GID més baix per a grups nous,3,Del mòdul d'Usuaris i Grups
-md5=Mètode de xifratge de les contrasenyes,1,3-LDAP MD5,1-MD5 de Unix,0-crypt,2-Text planer
+pw_encrypt=Mètode de xifratge de les contrasenyes,1,5-LDAP SSHA,4-LDAP SHA,3-LDAP MD5,1-MD5 de Unix,0-crypt,2-Text planer
shells=Construeix la llista de shells de,2,fixed-Llista integrada,passwd-Usuaris del sistema,shells-/etc/shells
secmode=Mostra els grups secundaris al formulari de l'usuari,1,0-Sí,1-No
diff -ru /home/acc/bak/ldap-useradmin-1.290/config.info.ca.orig ldap-useradmin/config.info.ca.orig
--- /home/acc/bak/ldap-useradmin-1.290/config.info.ca.orig Sun Nov 6 16:42:35 2005
+++ ldap-useradmin/config.info.ca.orig Sat Jul 8 08:26:24 2006
@@ -10,6 +10,7 @@
other_class=Altres objectClasses per afegir als usuaris nous,0
gother_class=Altres objectClasses per afegir als grups nous,0
given=Mostra els camps per al nom i cognom donats,1,1-Sí,0-No
+given_class=Classe d'objectes a afegir per al nom donat,0
slappasswd=Camí complet del programa <tt>slappasswd</tt>,0
line9=Atributs LDAP,11
@@ -20,6 +21,7 @@
group_mod_props=Propietats LDAP per als grups modificats,9,40,3,\t
group_fields=Propietats extra de grups LDAP per permetre l'edició de<br>(AL format de <i>fieldname</i> <i>description</i>),9,40,3,\t
multi_fields=Permet valors múltiples a les propietats extra,1,1-Sí,0-No
+noclash=Atributs que no permeten duplicats,0
line5=Opcions del directori arrel,11
homedir_perms=Permisos dels nous directoris arrel,3,Del mòdul d'Usuaris i Grups
@@ -56,7 +58,8 @@
samba_def=Activa el compte Samba per defecte,1,1-Sí,0-No
samba_domain=Domini SID de Samba3,0
samba_props=Propietats LDAP per als nous usuaris de Samba,9,40,3,\t
-samba_gclass=Classe d'objectes LDAP dels grups de Samba,0
+samba_gclass=Classe d'objectes LDAP dels grups de Samba,10,-sambaGroup (Samba 2),sambaSamGroup-sambaSamGroup (esquema antic Samba 3),sambaGroupMapping-sambaGroupMapping (Esquema nou Samba 3),Una altra
+samba_gid=SID del grup primari,10,none-Cap,-Dedueix-lo automàticament
line3=Opcions del servidor IMAP Cyrus,11
imap_class=Classe d'objecte LDAP per als usuaris d'IMAP,0
@@ -70,8 +73,9 @@
domain=Domini de correu de l'atribut de correu,3,No afegeixis atributs de correu
mailfmt=Format de l'adreá de correu,1,0-cognom.nom@domini,1-usuari@domini
quota=Quota per defecte dels nous usuaris de Cyrus,3,Cap
-addressbook=Base de la llibreta d'adreces,3,Cap
+addressbook=Base de la llibreta d'adreces,3,Cap,,,Kb
maillocaladdress=Atribut dels àlies de correu,3,Per defecte (alias)
+alias_same=Dos usuaris poden tenir el mateix àlies,1,1-Sí,0-No
line4=Ordres abans i després,11
pre_command=Ordre a executar abans de fer els canvis,0
diff -ru /home/acc/bak/ldap-useradmin-1.290/config.info.de ldap-useradmin/config.info.de
--- /home/acc/bak/ldap-useradmin-1.290/config.info.de Sat Jul 8 08:26:24 2006
+++ ldap-useradmin/config.info.de Sat Jul 8 08:34:23 2006
@@ -27,7 +27,7 @@
line6=Optionen für neue Benutzer,11
base_uid=Niedrigste UID für neue Benutzer,3,Aus dem Benutzer- und Gruppenmodul
base_gid=Niedrigste GID für neue Gruppen,3,Aus dem Benutzer- und Gruppenmodul
-md5=Verschlüsselungsmethodik für Passworte,1,3-LDAP MD5,1-Unix MD5,0-cryp,2-Einfacher Text
+pw_encrypt=Verschlüsselungsmethodik für Passworte,1,5-LDAP SSHA,4-LDAP SHA,3-LDAP MD5,1-Unix MD5,0-cryp,2-Einfacher Text
shells=Baue eine Liste der Shells aus,2,fixed-Eingebaute Liste,passwd-Systembenutzer,shells-/etc/shells
secmode=Zeige sekundäre Gruppen auf dem Benutzerformular?,0-Ja,1-Nein
line7=Standards für neue Benutzer,11
diff -ru /home/acc/bak/ldap-useradmin-1.290/config.info.es ldap-useradmin/config.info.es
--- /home/acc/bak/ldap-useradmin-1.290/config.info.es Sat Jul 8 08:26:24 2006
+++ ldap-useradmin/config.info.es Sat Jul 8 08:34:23 2006
@@ -27,7 +27,7 @@
line6=Opciones de usuario nuevo,11
base_uid=UID menor para nuevos usuarios,3,Del módulo de Usuarios y Grupos
base_gid=GID menor para nuevos grupos,3,Del módulo de Usuarios y Grupos
-md5=Método de encriptación de contraseñas,1,3-LDAP MD5,1-Unix MD5,0-crypt,2-Texto plano
+pw_encrypt=Método de encriptación de contraseñas,1,5-LDAP SSHA,4-LDAP SHA,3-LDAP MD5,1-Unix MD5,0-crypt,2-Texto plano
shells=Construir lista de shells desde,2,fijada-Lista original,passwd-Usuarios de sistema,shells-/etc/shells
secmode=¿Mostrar grupos secundarios en el formulario de usuario?,1,0-Sí,1-No
line7=Conf. por defecto de nuevo usuario,11
diff -ru /home/acc/bak/ldap-useradmin-1.290/config.info.orig ldap-useradmin/config.info.orig
--- /home/acc/bak/ldap-useradmin-1.290/config.info.orig Sun Nov 6 16:42:35 2005
+++ ldap-useradmin/config.info.orig Sat Jul 8 08:26:24 2006
@@ -10,6 +10,7 @@
other_class=Other objectClasses to add to new users,0
gother_class=Other objectClasses to add to new groups,0
given=Show fields for given name and surname?,1,1-Yes,0-No
+given_class=Object class to add for given name?,0
slappasswd=Full path to <tt>slappasswd</tt> program,0
line9=LDAP attributes,11
@@ -20,6 +21,7 @@
group_mod_props=LDAP properties for modified groups,9,40,3,\t
group_fields=Extra LDAP group properties to allow editing of<br>(In <i>fieldname</i> <i>description</i> format),9,40,3,\t
multi_fields=Allow multiple values for extra properties?,1,1-Yes,0-No
+noclash=Attributes for which duplicates are disallowed,0
line5=Home directory options,11
homedir_perms=Permissions on new home directories,3,From Users and Groups module
@@ -56,7 +58,8 @@
samba_def=Enabled Samba account by default?,1,1-Yes,0-No
samba_domain=Domain SID for Samba3,0
samba_props=LDAP properties for new Samba users,9,40,3,\t
-samba_gclass=LDAP object class for Samba groups,0
+samba_gclass=LDAP object class for Samba groups,10,-sambaGroup (Samba 2),sambaSamGroup-sambaSamGroup (Samba 3 old schema),sambaGroupMapping-sambaGroupMapping (Samba 3 new schema),Other
+samba_gid=Primary group SID,10,none-None,-Work out automatically
line3=Cyrus IMAP server options,11
imap_class=LDAP object class for IMAP users,0
@@ -70,8 +73,9 @@
domain=Email domain for mail attribute,0,Don't add mail attributes
mailfmt=Email address format,1,0-firstname.surname@domain,1-username@domain
addressbook=Address book base,3,None
-quota=Default quota for new Cyrus users,3,None
+quota=Default quota for new Cyrus users,3,None,,,kB
maillocaladdress=Attribute for mail aliases,3,Default (alias)
+alias_same=Can two users have the same alias?,1,1-Yes,0-No
line4=Before and after commands,11
pre_command=Command to run before making changes,0
diff -ru /home/acc/bak/ldap-useradmin-1.290/config.orig ldap-useradmin/config.orig
--- /home/acc/bak/ldap-useradmin-1.290/config.orig Sun Nov 6 16:42:35 2005
+++ ldap-useradmin/config.orig Sat Jul 8 08:26:24 2006
@@ -14,3 +14,5 @@
secmode=0
imap_folderalt=1
samba_gclass=sambaGroup
+alias_same=0
+given_class=inetOrgPerson
diff -ru /home/acc/bak/ldap-useradmin-1.290/edit_group.cgi ldap-useradmin/edit_group.cgi
--- /home/acc/bak/ldap-useradmin-1.290/edit_group.cgi Sat Jul 8 08:26:24 2006
+++ ldap-useradmin/edit_group.cgi Sat Jul 8 08:34:23 2006
@@ -85,10 +85,18 @@
print "<tr $cb> <td><table width=100%>\n";
print "<tr> <td><b>$text{'gedit_samba'}</b></td>\n";
-printf "<td><input type=radio name=samba value=1 %s> %s\n",
- $oclass{$samba_group_class} ? "checked" : "", $text{'yes'};
-printf "<input type=radio name=samba value=0 %s> %s</td>\n",
- $oclass{$samba_group_class} ? "" : "checked", $text{'no'};
+if ($in{'new'}) {
+ printf "<td><input type=radio name=samba value=1 %s> %s\n",
+ $samba_group_class ? "checked" : "", $text{'yes'};
+ printf "<input type=radio name=samba value=0 %s> %s</td>\n",
+ $samba_group_class ? "" : "checked", $text{'no'};
+}
+else {
+ printf "<td><input type=radio name=samba value=1 %s> %s\n",
+ $oclass{$samba_group_class} ? "checked" : "", $text{'yes'};
+ printf "<input type=radio name=samba value=0 %s> %s</td>\n",
+ $oclass{$samba_group_class} ? "" : "checked", $text{'no'};
+}
print "<td colspan=2 width=50%></td>\n";
diff -ru /home/acc/bak/ldap-useradmin-1.290/ldap-useradmin-lib.pl ldap-useradmin/ldap-useradmin-lib.pl
--- /home/acc/bak/ldap-useradmin-1.290/ldap-useradmin-lib.pl Sat Jul 8 08:26:24 2006
+++ ldap-useradmin/ldap-useradmin-lib.pl Sat Jul 8 09:03:55 2006
@@ -191,20 +191,23 @@
{
local ($pass, $salt) = @_;
&seed_random();
-if ($config{'md5'} == 3) {
- # LDAP MD5 encryption
+if ($config{'pw_encrypt'} == 5 || $config{'pw_encrypt'} == 4 ||
+ $config{'pw_encrypt'} == 3) {
+ # LDAP encryption
local $qp = quotemeta($pass);
- local $out = `$config{'slappasswd'} -h '{md5}' -s $qp 2>/dev/null`;
+ local $hash = $config{'pw_encrypt'} == 5 ? "ssha" :
+ $config{'pw_encrypt'} == 4 ? "sha" : "md5";
+ local $out = `$config{'slappasswd'} -h '{$hash}' -s $qp 2>/dev/null`;
$out =~ s/\s+$//;
- $out =~ s/^\{md5\}//i;
+ $out =~ s/^\{$hash\}//i;
return $out;
}
-if ($config{'md5'} == 1) {
+if ($config{'pw_encrypt'} == 1) {
# Unix MD5 encryption
&foreign_require("useradmin", "user-lib.pl");
return &useradmin::encrypt_md5($pass, $salt);
}
-elsif ($config{'md5'} == 0) {
+elsif ($config{'pw_encrypt'} == 0) {
# Standard Unix crypt
$salt ||= chr(int(rand(26))+65).chr(int(rand(26))+65);
return &unix_crypt($pass, $salt);
@@ -435,8 +438,11 @@
sub user_to_dn
{
local $pfx = $_[0]->{'pass'} =~ /^\{[a-z0-9]+\}/i ? undef :
- $config{'md5'} == 1 || $config{'md5'} == 3 ? "{md5}" :
- $config{'md5'} == 0 ? "{crypt}" : "";
+ $config{'pw_encrypt'} == 5 ? "{ssha}" :
+ $config{'pw_encrypt'} == 4 ? "{sha}" :
+ $config{'pw_encrypt'} == 1 || $config{'pw_encrypt'} == 3 ?
+ "{md5}" :
+ $config{'pw_encrypt'} == 0 ? "{crypt}" : "";
local $pass = $_[0]->{'pass'};
local $disabled;
if ($pass =~ s/^\!//) {
@@ -988,12 +994,23 @@
if (&in_schema($schema, "sambaSID") &&
$samba_schema == 3);
- push(@$props, "sambaPrimaryGroupSID",
+ if (&in_schema($schema, "sambaPrimaryGroupSID") &&
+ $samba_schema == 3 &&
+ $config{'samba_gid'} ne 'none') {
+ local $ldap = &ldap_connect();
+ local $base = &get_group_base();
+ local $rv = $ldap->search(base => $base,
+ filter => '(objectClass=sambaGroup)',
+ attrs => [ 'sambaSID' ]);
+ local $sid;
+ $sid = $rv->entry(0) if ($rv->count());
+ push(@$props, "sambaPrimaryGroupSID",
$config{'samba_gid'} ||
- $config{'samba_domain'}.'-'.($user->{'gid'}*2+1000))
- if (&in_schema($schema, "sambaPrimaryGroupSID") &&
- $samba_schema == 3 &&
- $config{'samba_gid'} ne 'none');
+ $sid ||
+ $config{'samba_domain'}.'-'.($user->{'gid'}*2+1001));
+ push(@$props, "sambaPrimaryGroupSID", $sid);
+ $ldap->unbind();
+ }
}
if (defined($opts)) {
diff -ru /home/acc/bak/ldap-useradmin-1.290/ldap-useradmin-lib.pl.orig ldap-useradmin/ldap-useradmin-lib.pl.orig
--- /home/acc/bak/ldap-useradmin-1.290/ldap-useradmin-lib.pl.orig Sun Nov 6 16:42:35 2005
+++ ldap-useradmin/ldap-useradmin-lib.pl.orig Sat Jul 8 08:26:24 2006
@@ -8,6 +8,8 @@
require '../ui-lib.pl';
&foreign_require("useradmin", "user-lib.pl");
%access = &get_module_acl();
+$useradmin::access{'udelete'} = 1; # needed for users_table / groups_table
+$useradmin::access{'gdelete'} = 1;
%utext = &load_language("useradmin");
foreach $t (keys %utext) {
@@ -30,8 +32,11 @@
$secret_file = "/etc/ldap.secret";
$samba_class = $config{'samba_class'} || "sambaAccount";
$samba_class =~ s/^\s+//; $samba_class =~ s/\s+$//;
+$samba_schema = $samba_class eq "sambaSamAccount" ? 3 : 2;
$samba_group_class = $config{'samba_gclass'} || "sambaGroup";
$samba_group_class =~ s/^\s+//; $samba_group_class =~ s/\s+$//;
+$samba_group_schema = $samba_group_class eq "sambaSamGroup" ||
+ $samba_group_class eq "sambaGroupMapping" ? 3 : 2;
$cyrus_class = $config{'imap_class'} || "SuSEeMailObject";
$cyrus_class =~ s/^\s+//; $cyrus_class =~ s/\s+$//;
@@ -181,13 +186,14 @@
return ($nt, $lm);
}
-# encrypt_password(string)
+# encrypt_password(string, [salt])
sub encrypt_password
{
+local ($pass, $salt) = @_;
&seed_random();
if ($config{'md5'} == 3) {
# LDAP MD5 encryption
- local $qp = quotemeta($_[0]);
+ local $qp = quotemeta($pass);
local $out = `$config{'slappasswd'} -h '{md5}' -s $qp 2>/dev/null`;
$out =~ s/\s+$//;
$out =~ s/^\{md5\}//i;
@@ -196,16 +202,16 @@
if ($config{'md5'} == 1) {
# Unix MD5 encryption
&foreign_require("useradmin", "user-lib.pl");
- return &useradmin::encrypt_md5($_[0]);
+ return &useradmin::encrypt_md5($pass, $salt);
}
elsif ($config{'md5'} == 0) {
# Standard Unix crypt
- local $salt = chr(int(rand(26))+65).chr(int(rand(26))+65);
- return &unix_crypt($_[0], $salt);
+ $salt ||= chr(int(rand(26))+65).chr(int(rand(26))+65);
+ return &unix_crypt($pass, $salt);
}
else {
# No encryption!
- return $_[0];
+ return $pass;
}
}
@@ -275,9 +281,9 @@
local $base = &get_user_base();
local @attrs = &user_to_dn($_[1]);
push(@attrs, &split_props($config{'mod_props'}, $_[1]));
-push(@attrs, @{$_[0]->{'ldap_attrs'}});
-if (defined($_[0]->{'ldap_class'})) {
- push(@attrs, "objectClass" => $_[0]->{'ldap_class'});
+push(@attrs, @{$_[1]->{'ldap_attrs'}});
+if (defined($_[1]->{'ldap_class'})) {
+ push(@attrs, "objectClass" => $_[1]->{'ldap_class'});
}
local $rv = $ldap->modify($_[0]->{'dn'}, replace => { @attrs });
if ($rv->code) {
@@ -407,7 +413,7 @@
'inactive' => $_[0]->get_value("shadowInactive") || "",
);
$user{'pass'} =~ s/^(\!?){[a-z0-9]+}/$1/i;
- $user{'all_ldap_attrs'} = { map { lc($_), $_[0]->get_value($_) }
+ $user{'all_ldap_attrs'} = { map { lc($_), scalar($_[0]->get_value($_)) }
$_[0]->attributes() };
$user{'ldap_class'} = [ $_[0]->get_value('objectClass') ];
return %user;
@@ -491,10 +497,18 @@
return undef;
}
+# set_user_envs(&hash, action, [plainpass], [secondary])
# Just call the useradmin function of the same name
sub set_user_envs
{
-return &useradmin::set_user_envs(@_);
+local $rv = &useradmin::set_user_envs(@_[0..3]);
+if ($_[0]->{'all_ldap_attrs'}) {
+ foreach my $a (keys %{$_[0]->{'all_ldap_attrs'}}) {
+ my $v = $_[0]->{'all_ldap_attrs'}->{$a};
+ $ENV{'USERADMIN_LDAP_'.uc($a)} = $v;
+ }
+ }
+return $rv;
}
# Just call the useradmin function of the same name
@@ -860,11 +874,12 @@
}
}
-# parse_extra_fields(fields-list, &props, &rprops)
+# parse_extra_fields(fields-list, &props, &rprops, &ldap, [dn])
sub parse_extra_fields
{
-local ($fields, $props, $rprops) = @_;
+local ($fields, $props, $rprops, $ldap, $dn) = @_;
local @fields = map { [ split(/\s+/, $_, 2) ] } split(/\t/, $fields);
+local %noclash = map { lc($_), 1 } split(/\s+/, $config{'noclash'});
local $i = 0;
local $f;
foreach $f (@fields) {
@@ -875,6 +890,24 @@
else {
$in{"field_$i"} =~ s/\r//g;
local @v = split(/\n/, $in{"field_$i"});
+ if ($noclash{lc($f->[0])}) {
+ ($dup, $dupwhat) = &check_duplicates($ldap, $f->[0],
+ \@v, $dn);
+ if ($dup && $dup->get_value('uid')) {
+ &error(&text('usave_eattrdupu',
+ $dup->get_value('uid'),
+ $f->[1], $dupwhat));
+ }
+ elsif ($dup && $dup->get_value('cn')) {
+ &error(&text('usave_eattrdupg',
+ $dup->get_value('cn'),
+ $f->[1], $dupwhat));
+ }
+ elsif ($dup) {
+ &error(&text('usave_eattrdup',
+ $dup->dn(), $f->[1], $dupwhat));
+ }
+ }
push(@$props, $f->[0], @v == 1 ? $v[0] : \@v);
}
$i++;
@@ -918,12 +951,16 @@
# Plain-text password to convert
($nt, $lm) = &samba_password($pass);
$opts = "U";
+ push(@$props, "sambaPwdLastSet", time())
+ if (&in_schema($schema, "sambaPwdLastSet"));
+ push(@$props, "sambaPwdCanChange", time())
+ if (&in_schema($schema, "sambaPwdCanChange"));
}
elsif ($passmode == 4) {
# No change
}
if (defined($nt)) {
- if ($config{'samba_class'} eq 'sambaSamAccount') {
+ if ($samba_schema == 3) {
push(@$props, "sambaNTPassword", $nt)
if (&in_schema($schema, "sambaNTPassword"));
push(@$props, "sambaLMPassword", $lm)
@@ -949,12 +986,14 @@
push(@$props, "sambaSID",
$config{'samba_domain'}.'-'.($user->{'uid'}*2+1000))
if (&in_schema($schema, "sambaSID") &&
- $config{'samba_class'} eq 'sambaSamAccount');
+ $samba_schema == 3);
push(@$props, "sambaPrimaryGroupSID",
+ $config{'samba_gid'} ||
$config{'samba_domain'}.'-'.($user->{'gid'}*2+1000))
if (&in_schema($schema, "sambaPrimaryGroupSID") &&
- $config{'samba_class'} eq 'sambaSamAccount');
+ $samba_schema == 3 &&
+ $config{'samba_gid'} ne 'none');
}
if (defined($opts)) {
@@ -964,7 +1003,7 @@
push(@$props, "sambaAcctFlags", sprintf("[%-11s]",$opts))
if (&in_schema($schema, "sambaAcctFlags") &&
- $config{'samba_class'} eq 'sambaSamAccount');
+ $samba_schema == 3);
}
push(@$props, &split_props($config{'samba_props'}, $user));
@@ -977,10 +1016,10 @@
local ($user, $schema, $rprops) = @_;
push(@$rprops, "ntPassword", "lmPassword",
"ntuid", "rid", "acctFlags")
- if ($config{'samba_class'} eq 'sambaAccount');
+ if ($samba_schema == 2);
push(@$rprops, "sambaNTPassword", "sambaLMPassword",
"sambaSID","sambaAcctFlags")
- if ($config{'samba_class'} eq 'sambaSamAccount');
+ if ($samba_schema == 3);
push(@$rprops, &split_first($config{'samba_props'}));
}
@@ -996,6 +1035,63 @@
}
}
return @rv;
+}
+
+# check_duplicates(&ldap, name, &values, [dn])
+# Returns a DN object and the clashing value if some other user has an
+# attribute with the same name and value
+sub check_duplicates
+{
+local ($ldap, $name, $values, $dn) = @_;
+local $base = &get_user_base();
+foreach my $v (@$values) {
+ local $search = "($name=$v)";
+ $rv = $ldap->search(base => $base, filter => $search);
+ next if ($rv->code);
+ foreach my $u ($rv->all_entries) {
+ if ($u->dn() ne $dn) {
+ return ($u, $v);
+ }
+ }
+ }
+return ();
+}
+
+# delete_ldap_subtree(&ldap, dn)
+# Deletes an LDAP entry and all those below it. Returns undef on success, or
+# an errpr message on failure.
+sub delete_ldap_subtree
+{
+local ($ldap, $dn) = @_;
+local $rv = $ldap->search(base => $dn, scope => 'one',
+ filter => '(objectClass=*)');
+if ($rv->code) {
+ print "subtree search error ",$rv->error,"<br>\n";
+ }
+foreach my $e ($rv->all_entries) {
+ &delete_ldap_subtree($ldap, $e->dn());
+ }
+local $rv = $ldap->delete($dn);
+return $rv->code ? $rv->error : undef;
+}
+
+# remove_accents(text)
+# Given some text with european accented characters, convert them to ascii
+sub remove_accents
+{
+local ($string) = @_;
+$string =~ tr/ÀÁÂÃÄÅàáâãäå/a/;
+$string =~ tr/Çç/c/;
+$string =~ tr/ÈÉÊËèéêë/e/;
+$string =~ tr/ÌÍÎÏìíîï/i/;
+$string =~ tr/Ð/d/;
+$string =~ tr/Ññ/n/;
+$string =~ tr/ÒÓÔÕÖØðòóôõöø/o/;
+$string =~ tr/ÙÚÛÜùúûü/u/;
+$string =~ tr/Ýýÿ/y/;
+$string =~ tr/ß/b/;
+$string =~ s/æÆ/ae/go;
+return $string;
}
1;
diff -ru /home/acc/bak/ldap-useradmin-1.290/save_group.cgi ldap-useradmin/save_group.cgi
--- /home/acc/bak/ldap-useradmin-1.290/save_group.cgi Sat Jul 8 08:26:24 2006
+++ ldap-useradmin/save_group.cgi Sat Jul 8 09:21:40 2006
@@ -129,8 +129,10 @@
&error(&text('gsave_einuse', $group));
}
-$pfx = $config{'md5'} == 1 || $config{'md5'} == 3 ? "{md5}" :
- $config{'md5'} == 0 ? "{crypt}" : "";
+$pfx = $config{'pw_encrypt'} == 5 ? "{ssha}" :
+ $config{'pw_encrypt'} == 4 ? "{sha}" :
+ $config{'pw_encrypt'} == 1 || $config{'pw_encrypt'} == 3 ? "{md5}" :
+ $config{'pw_encrypt'} == 0 ? "{crypt}" : "";
if ($in{'passmode'} == 0) {
$pass = "";
}
@@ -195,8 +197,9 @@
push(@props, "rid", $gid*2+1001)
if (&in_schema($schema, "rid") &&
$samba_group_schema == 2);
+ # TODO: Create posixGroup with sambaGroupMapping
push(@props, "sambaSID",
- "$config{'samba_domain'}-".($gid*2+1000))
+ "$config{'samba_domain'}-".($gid*2+1001))
if (&in_schema($schema, "sambaSID") &&
$samba_group_schema == 3);
push(@props, "sambaGrouptype", 2)
@@ -266,8 +269,9 @@
push(@props, "rid", $gid*2+1000)
if (&in_schema($schema, "rid") &&
$samba_group_class eq 'sambaGroup');
+ # TODO: Create posixGroup with sambaGroupMapping
push(@props, "sambaSID",
- "$config{'samba_domain'}-".($gid*2+1000))
+ "$config{'samba_domain'}-".($gid*2+1001))
if (&in_schema($schema, "sambaSID") &&
$samba_group_schema == 3);
push(@props, "sambaGrouptype", 2)
diff -ru /home/acc/bak/ldap-useradmin-1.290/save_group.cgi.orig ldap-useradmin/save_group.cgi.orig
--- /home/acc/bak/ldap-useradmin-1.290/save_group.cgi.orig Sun Nov 6 16:42:35 2005
+++ ldap-useradmin/save_group.cgi.orig Sat Jul 8 08:26:24 2006
@@ -186,22 +186,26 @@
# Remove Samba attributes
@classes = grep { $_ ne $samba_group_class } @classes;
push(@rprops,
- $samba_group_class eq "sambaGroup" ? "rid" : "sambaSID");
+ $samba_group_class eq "sambaGroup" ? ( "rid" )
+ : ( "sambaSID", "sambaGrouptype" ));
}
elsif (!$wassamba && $in{'samba'}) {
# Add Samba attributes
push(@classes, $samba_group_class);
- push(@props, "rid", $gid*2+1000)
+ push(@props, "rid", $gid*2+1001)
if (&in_schema($schema, "rid") &&
- $samba_group_class eq 'sambaGroup');
+ $samba_group_schema == 2);
push(@props, "sambaSID",
"$config{'samba_domain'}-".($gid*2+1000))
if (&in_schema($schema, "sambaSID") &&
- $samba_group_class eq 'sambaSamGroup');
+ $samba_group_schema == 3);
+ push(@props, "sambaGrouptype", 2)
+ if (&in_schema($schema, "sambaGrouptype") &&
+ $samba_group_schema == 3);
}
# Add extra fields
- &parse_extra_fields($config{'group_fields'}, \@props, \@rprops);
+ &parse_extra_fields($config{'group_fields'}, \@props, \@rprops, $ldap);
# Get the properties for modified groups
push(@props, &split_props($config{'group_mod_props'}, \%ghash));
@@ -246,7 +250,8 @@
if (defined($merr));
# Parse extra fields
- &parse_extra_fields($config{'group_fields'}, \@props, \@rprops);
+ &parse_extra_fields($config{'group_fields'}, \@props, \@rprops,
+ $ldap, $in{'dn'});
# Get the properties for new groups
push(@props, &split_props($config{'group_props'}, \%ghash));
@@ -264,7 +269,10 @@
push(@props, "sambaSID",
"$config{'samba_domain'}-".($gid*2+1000))
if (&in_schema($schema, "sambaSID") &&
- $samba_group_class eq 'sambaSamGroup');
+ $samba_group_schema == 3);
+ push(@props, "sambaGrouptype", 2)
+ if (&in_schema($schema, "sambaGrouptype") &&
+ $samba_group_schema == 3);
}
$rv = $ldap->add($newdn, attr =>
[ "cn" => $group,
diff -ru /home/acc/bak/ldap-useradmin-1.290/save_user.cgi ldap-useradmin/save_user.cgi
--- /home/acc/bak/ldap-useradmin-1.290/save_user.cgi Sat Jul 8 08:26:24 2006
+++ ldap-useradmin/save_user.cgi Sat Jul 8 08:34:23 2006
@@ -221,8 +221,11 @@
}
}
- local $pfx = $config{'md5'} == 1 || $config{'md5'} == 3 ? "{md5}" :
- $config{'md5'} == 0 ? "{crypt}" : "";
+ local $pfx = $config{'pw_encrypt'} == 5 ? "{ssha}" :
+ $config{'pw_encrypt'} == 4 ? "{sha}" :
+ $config{'pw_encrypt'} == 1 || $config{'pw_encrypt'} == 3 ?
+ "{md5}" :
+ $config{'pw_encrypt'} == 0 ? "{crypt}" : "";
if ($in{'passmode'} == 0) {
# Password is blank
if (!$mconfig{'empty_mode'}) {
diff -ru /home/acc/bak/ldap-useradmin-1.290/save_user.cgi.orig ldap-useradmin/save_user.cgi.orig
--- /home/acc/bak/ldap-useradmin-1.290/save_user.cgi.orig Sun Nov 6 16:42:35 2005
+++ ldap-useradmin/save_user.cgi.orig Sat Jul 8 08:26:24 2006
@@ -39,11 +39,16 @@
if ($in{'confirm'}) {
# Run the before command
%uhash = &dn_to_hash($uinfo);
- &set_user_envs(\%uhash, 'DELETE_USER', undef);
+ &set_user_envs(\%uhash, 'DELETE_USER', undef, undef);
$merr = &making_changes();
&error(&text('usave_emaking', "<tt>$merr</tt>"))
if (defined($merr));
+ # Work out old classes
+ @classes = $uinfo->get_value("objectClass");
+ @cyrus_class_2 = split(' ',$cyrus_class);
+ $wascyrus = &indexof($cyrus_class_2[0], @classes) >= 0;
+
# Delete from other modules
%user = &dn_to_hash($uinfo);
if ($in{'others'}) {
@@ -84,10 +89,15 @@
print "$text{'udel_done'}<p>\n";
# Delete his addressbook entry
- if ($config{'addressbook'}) {
+ if ($config{'addressbook'} && $wascyrus) {
print "$text{'udel_book'}<br>\n";
- &delete_addressbook();
- print "$text{'udel_done'}<p>\n";
+ $err = &delete_addressbook();
+ if ($err) {
+ print &text('udel_failed', $err),"<p>\n";
+ }
+ else {
+ print "$text{'udel_done'}<p>\n";
+ }
}
# Delete his home directory
@@ -125,7 +135,7 @@
print "<input type=hidden name=confirm value=1>\n";
if ($home ne "/" && -d $home) {
- $size = &disk_usage_kb($home);
+ $size = &nice_size(&disk_usage_kb($home)*1024);
print "<center><b>",&text('udel_sure', $user, $home,
$size),"</b><p>\n";
print "<input type=submit ",
@@ -175,6 +185,7 @@
$in{'user'} =~ /^[^:\t]+$/ ||
&error(&text('usave_ebadname', $in{'user'}));
$in{'user'} =~ s/\r//g;
+ $in{'real'} || &error($text{'usave_ereal2'});
@users = split(/\n/, $in{'user'});
$user = $users[0];
$in{'uid'} =~ /^\-?[0-9]+$/ || &error(&text('usave_euid', $in{'uid'}));
@@ -213,16 +224,28 @@
local $pfx = $config{'md5'} == 1 || $config{'md5'} == 3 ? "{md5}" :
$config{'md5'} == 0 ? "{crypt}" : "";
if ($in{'passmode'} == 0) {
+ # Password is blank
+ if (!$mconfig{'empty_mode'}) {
+ local $err = &useradmin::check_password_restrictions(
+ "", $user);
+ &error($err) if ($err);
+ }
$pass = "";
}
elsif ($in{'passmode'} == 1) {
+ # Password is locked
$pass = $mconfig{'lock_string'};
}
elsif ($in{'passmode'} == 2) {
+ # Specific encrypted password entered, or possibly no change
$pass = $in{'encpass'};
$pass = $pfx.$pass if ($pass !~ /^\{[a-z0-9]+\}/i && $pfx);
}
elsif ($in{'passmode'} == 3) {
+ # Normal password entered - check restrictions
+ local $err = &useradmin::check_password_restrictions(
+ $in{'pass'}, $user);
+ &error($err) if ($err);
$pass = $pfx.&encrypt_password($in{'pass'});
$plainpass = $in{'pass'};
}
@@ -259,7 +282,8 @@
# Run the pre-change command
&set_user_envs(\%uhash, 'CREATE_USER',
- $in{'passmode'} == 3 ? $in{'pass'} : "");
+ $in{'passmode'} == 3 ? $in{'pass'} : "",
+ undef);
$merr = &making_changes();
&error(&text('usave_emaking', "<tt>$merr</tt>"))
if (defined($merr));
@@ -295,7 +319,8 @@
}
# Add any extra LDAP fields
- &parse_extra_fields($config{'fields'}, \@props, \@rprops);
+ &parse_extra_fields($config{'fields'}, \@props, \@rprops,
+ $ldap);
# Add shadow LDAP fields
$shadow = &shadow_fields();
@@ -306,10 +331,10 @@
push(@classes, split(/\s+/, $config{'other_class'}));
push(@classes, $samba_class) if ($in{'samba'});
push(@classes, split(' ',$cyrus_class)) if ($in{'cyrus'});
+ &name_fields();
@classes = &unique(@classes);
$base = &get_user_base();
$newdn = "uid=$user,$base";
- &name_fields();
$rv = $ldap->add($newdn, attr =>
[ "cn" => $real,
"uid" => \@users,
@@ -352,6 +377,7 @@
}
}
else {
+ # Modifying a user
$olduser = $uinfo->get_value('uid');
if ($olduser ne $user) {
defined(&all_getpwnam($user)) &&
@@ -371,7 +397,8 @@
# Run the pre-change command
&set_user_envs(\%uhash, 'MODIFY_USER',
- $in{'passmode'} == 3 ? $in{'pass'} : "");
+ $in{'passmode'} == 3 ? $in{'pass'} : "",
+ undef);
$merr = &making_changes();
&error(&text('usave_emaking', "<tt>$merr</tt>"))
if (defined($merr));
@@ -447,7 +474,8 @@
}
# Add or update any extra LDAP fields
- &parse_extra_fields($config{'fields'}, \@props, \@rprops);
+ &parse_extra_fields($config{'fields'}, \@props, \@rprops,
+ $ldap, $in{'dn'});
# Add or update shadow LDAP fields
$shadow = &shadow_fields();
@@ -470,10 +498,10 @@
}
push(@classes, "shadowAccount") if ($shadow);
+ &name_fields();
@classes = &unique(@classes);
@rprops = grep { defined($uinfo->get_value($_)) } @rprops;
$newdn = $in{'dn'};
- &name_fields();
$rv = $ldap->modify($in{'dn'}, replace =>
{ "cn" => $real,
"uid" => \@users,
@@ -532,7 +560,7 @@
}
# Setup the imap account as well
- &setup_imap();
+ &setup_imap(\%uhash);
}
elsif (!$in{'cyrus'} && $wascyrus) {
if ($config{'addressbook'}) {
@@ -592,16 +620,20 @@
}
}
+ # Get the updated user object
+ $rv = $ldap->search(base => $newdn,
+ scope => 'base',
+ filter => '(&(objectClass=posixAccount))');
+ ($uinfo) = $rv->all_entries;
+ %user = &dn_to_hash($uinfo);
+
# Run post-change script
+ &set_user_envs(\%user, $in{'new'} ? 'CREATE_USER' : 'MODIFY_USER',
+ $in{'passmode'} == 3 ? $in{'pass'} : "", undef);
&made_changes();
# Run other modules' scripts
if ($in{'others'}) {
- $rv = $ldap->search(base => $newdn,
- scope => 'base',
- filter => '(&(objectClass=posixAccount))');
- ($uinfo) = $rv->all_entries;
- %user = &dn_to_hash($uinfo);
$user{'passmode'} = $in{'passmode'};
if ($in{'passmode'} == 2 && $user{'pass'} eq $ouser{'pass'}) {
# not changing password
@@ -634,38 +666,36 @@
return if (!$config{'domain'});
# Add surname and first name details
-local ($firstname, $surname);
-if ($in{'real'} =~ /(\S+)\s+(\S+)$/) {
- $firstname = lc($1);
- $surname = lc($2);
+local ($autofirstname, $autolastname);
+if ($firstname && $lastname) {
+ $autofirstname = $firstname;
+ $autolastname = $lastname;
+ }
+elsif ($in{'real'} =~ /(\S+)\s+(\S+)$/) {
+ $autofirstname = lc($1);
+ $autolastname = lc($2);
}
elsif ($in{'real'} =~ /(\S+)/) {
- $firstname = lc($1);
+ $autofirstname = lc($1);
}
else {
- $firstname = lc($in{'user'});
+ $autofirstname = lc($in{'user'});
}
-push(@props, "name", $firstname)
- if (&in_schema($schema, "name"));
-if ($surname) {
+if ($autolastname) {
if (&in_schema($schema, "mail")) {
if ($config{'mailfmt'} == 0) {
push(@props, "mail",
- "$firstname.$surname\@$config{'domain'}")
+ "$autofirstname.$autolastname\@$config{'domain'}")
}
else {
push(@props, "mail",
"$user\@$config{'domain'}")
}
}
- push(@props, "surname", $surname)
- if (&in_schema($schema, "surname"));
}
else {
- push(@props, "mail", "$firstname\@$config{'domain'}")
+ push(@props, "mail", "$autofirstname\@$config{'domain'}")
if (&in_schema($schema, "mail"));
- push(@rprops, "surname")
- if (&in_schema($schema, "surname"));
}
# Add extra aliases
@@ -673,6 +703,10 @@
if (&in_schema($schema, $aattr)) {
local @alias = split(/\s+/, $in{'alias'});
if ($in{'alias'}) {
+ if (!$config{'alias_same'}) {
+ ($dup, $dupwhat) = &check_duplicates($ldap, $aattr, \@alias, $in{'dn'});
+ $dup && &error(&text('save_ealiasdup', $dupwhat, $dup->dn()));
+ }
push(@props, $aattr, \@alias);
}
else {
@@ -696,33 +730,28 @@
if (($battr ne "") && &in_schema($schema, $battr)) {
push(@rprops, $battr);
}
-push(@rprops, "name")
- if (&in_schema($schema, "name"));
push(@rprops, "mail")
if (&in_schema($schema, "mail"));
-push(@rprops, "surname")
- if (&in_schema($schema, "surname"));
}
sub delete_addressbook
{
-$rv = $ldap->delete("ou=$user, $config{'addressbook'}");
+return &delete_ldap_subtree($ldap, "ou=$user, $config{'addressbook'}");
}
sub name_fields
{
if ($config{'given'}) {
- push(@props, "givenName", $firstname)
+ push(@props, "gn", $firstname)
if ($firstname && &in_schema($schema, "givenName"));
push(@props, "sn", $lastname)
if ($lastname && &in_schema($schema, "sn"));
- }
-else {
- #push(@props, "sn", $in{'real'})
- # if (&in_schema($schema, "sn"));
+ if ($firstname || $lastname) {
+ push(@classes, $config{'given_class'});
+ }
}
if (&in_schema($schema, "gecos")) {
- push(@props, "gecos", $in{'real'});
+ push(@props, "gecos", &remove_accents($in{'real'}));
}
}
--8<---------------cut here---------------end--------------->8---
--8<---------------cut here---------------start------------->8---
given=1
imap_login=cyrus
alias_same=0
multi_fields=1
samba_def=1
mailfmt=1
imap_def=0
samba_gclass=sambaGroupMapping
ldap_host=localhost
imap_folders=old public sent-mail
imap_folderalt=1
secmode=0
pw_encrypt=5
shells=fixed,passwd,shells
given_class=inetOrgPerson
imap_class=
samba_class=sambaSamAccount
slappasswd=/usr/local/sbin/slappasswd
default_min=
group_props=
addressbook=
random_password=
passwd_stars=
ldap_tls=0
other_class=inetLocalMailRecipient
default_warn=
group_fields=mailLocalAddress Group email address(es)
charset=
login=
imap_props=
pre_command=
base_gid=2000
user_files=
homedir_perms=
user_base=ou=People,ou=Users,dc=workstationit,dc=com
domain=workstationit.com
default_inactive=
gother_class=
ldap_port=
group_base=
fields=mailLocalAddress Email address(es)
imap_pass=
display_max=
home_style=
group_mod_props=
default_max=
imap_host=
default_secs=
samba_props=
props=
base_uid=2000
default_group=Domain Users
quota=
home_base=
default_shell=/usr/sbin/nologin
samba_gid=
maillocaladdress=
default_other=
noclash=
auth_ldap=/usr/local/etc/nss_ldap.conf
samba_domain=S-1-5-21-etc-etc-etc
post_command=/usr/local/sbin/imapcreatefolders
mod_props=
default_expire=
--8<---------------cut here---------------end--------------->8---
--
Anthony Chavez http://anthonychavez.org/
mailto:ac...@an... jabber:ac...@ja...
|