Attached is a patch to rlm_ldap v.1.0 pre 3 which provides SASL
[RFC2222] and DNS SRV [RFC2782] functionality.
The SASL part is written with GSSAPI in mind, so this SASL mechanism is
currently
hard-coded. The DNS SRV allows the use of _ldap._tcp.mydomain.com style
dn to find LDAP servers to connect to.

Configuration options added for both are:
use_sasl yes/no
sasl_secprops maxssf=0 or maxssf=56 etc.
sasl_auth_id host/<fqdn>

dns_lookup_host yes/no
dns_lookup_domain <domain name>

The patch is structured such that all SASL-specific code is disabled if
an appropriate SASL bind API is not found in the LDAP libraries, but the
various config options are preserved.  This seems consistent with the
handling of SSL options in the code.

You can use with a patch to the MIT gssapi library that
lets GSSAPI clients use keytabs instead of credentials caches for
initial credentials, but we create a stable root credentials cache
populated at an
appropriate interval with a crontab using kinit -k.

Add the host/myhost.domain.com to a keytab file, such as
/etc/krb5.keytab.

SASL patch based on patch to nss_ldap by Steve Langasek, which we also
use.

Regards
Allister Maguire

diff -urN ../rlm_ldap/Makefile.in /home/amaguire/rlm_ldap/Makefile.in
--- ../rlm_ldap/Makefile.in     2004-06-26 17:21:15.000000000 +1200
+++ /home/amaguire/rlm_ldap/Makefile.in 2004-06-26 17:42:37.000000000
+1200
@@ -1,5 +1,5 @@
 TARGET     = @targetname@
-SRCS       = rlm_ldap.c
+SRCS       = rlm_ldap.c locator.c
 HEADERS    =
 RLM_CFLAGS = @ldap_cflags@
 RLM_LIBS   = @ldap_ldflags@

diff -urN ../rlm_ldap/configure.in /home/amaguire/rlm_ldap/configure.in
--- ../rlm_ldap/configure.in    2004-06-26 17:21:15.000000000 +1200
+++ /home/amaguire/rlm_ldap/configure.in        2004-06-26
17:42:37.000000000 +1200
@@ -46,6 +46,8 @@
        smart_try_dir=$rlm_ldap_include_dir
        AC_SMART_CHECK_INCLUDE(lber.h)
        AC_SMART_CHECK_INCLUDE(ldap.h)
+       AC_SMART_CHECK_INCLUDE(sasl.h)
+       AC_SMART_CHECK_INCLUDE(sasl/sasl.h)
 
        AC_SMART_CHECK_LIB(sasl, sasl_encode)
        AC_SMART_CHECK_LIB(crypto, DH_new)
@@ -92,6 +94,12 @@
             SMART_CFLAGS="$SMART_CFLAGS -DHAVE_LDAP_INT_TLS_CONFIG"
          fi
 
+         AC_SMART_CHECK_LIB("$libldap", ldap_sasl_interactive_bind_s)
+
+         if test
"x${ac_cv_lib_ldap_ldap_sasl_interactive_bind_s}${ac_cv_lib_ldap_ldap_sa
sl_interactive_bind_s}" != "x"; then
+            SMART_CFLAGS="$SMART_CFLAGS
-DHAVE_LDAP_SASL_INTERACTIVE_BIND_S"
+         fi
+
        fi
 
 
diff -urN ../rlm_ldap/locator.c /home/amaguire/rlm_ldap/locator.c
--- ../rlm_ldap/locator.c       1970-01-01 12:00:00.000000000 +1200
+++ /home/amaguire/rlm_ldap/locator.c   2004-06-26 17:42:37.000000000
+1200
@@ -0,0 +1,199 @@
+/*--
+
+MICROSOFT BROAD SOURCE LICENSE 
+
+This License governs use of the accompanying Software.  Microsoft
+hopes you find this Software useful.
+
+You are licensed to do anything you want with the Software. 
+
+In return, we simply require that you agree:
+
+1. Not to remove any copyright notices from the Software.
+
+2. That the Software comes "as is", with no warranties.  None
+   whatsoever. This means no implied warranty of merchantability
+   or fitness for a particular purpose or any warranty of
+   non-infringement.  Also, you must pass this disclaimer
+   on whenever you distribute the Software.
+
+3. That we will not be liable for any of those types of damages
+   known as indirect, special, consequential, or incidental related
+   to the Software or this License. Also, you must pass this
+   limitation of liability on whenever you distribute the Software.
+
+4. That if you sue anyone over patents that you think may apply to
+   the Software, your license to the Software ends automatically
+   (this applies even when the rest of the License ends).
+
+5. That the patent rights Microsoft is licensing only apply to
+   the Software, not to any derivatives you make.
+
+6. That your rights under the License end automatically if you
+   breach this in any way.
+
+THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
+ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+PARTICULAR PURPOSE.
+
+Copyright (C) 1999  Microsoft Corporation.  All rights reserved.
+
+Module Name:
+
+    locator.c
+
+Abstract:
+
+    Locate SRV records in DNS
+
+--*/
+
+static const char rcsid[] =
+"$Id: locator.c,v 1.0.0.0 2004/07/25 15:25:15 Exp $";
+
+#include       "autoconf.h"
+
+#include       <stdio.h>
+#include       <sys/socket.h>
+#include       <netinet/in.h>
+#include       <arpa/inet.h>
+#include       <arpa/nameser.h>
+
+#include       <stdlib.h>
+#include       <string.h>
+#include       <unistd.h>
+
+#include       "locator.h"
+#include       "missing.h"
+
+/*
+ * Higher priority is given preference.
+ */
+static int dns_rr_priority_sort(const void *v1, const void *v2)
+{
+    PDNS_SRV_RECORD *l1 = (PDNS_SRV_RECORD *) v1;
+    PDNS_SRV_RECORD *l2 = (PDNS_SRV_RECORD *) v2;
+
+       if ((*l1)->srv_priority > (*l2)->srv_priority) {
+               return -1;
+       }
+    if ((*l1)->srv_priority == (*l2)->srv_priority) {
+               if ((*l1)->srv_weight > (*l2)->srv_weight) {
+                       return -1;
+               }
+               if ((*l1)->srv_weight < (*l2)->srv_weight) {
+                       return 1;
+               }
+               return 0;
+    }
+
+    return 1;
+}
+
+void dns_free_srv(PDNS_SRV_RECORD *loc, int *num){
+       int i;
+       for (i = 0; i < *num; i++) {
+               free(loc[i]->srv_host);
+               free(loc[i]);
+       }
+       free(loc);
+}
+
+/*
+ * Locate SRV records in DNS.
+ */
+int dns_locate_srv(
+    const char *domain,
+    const char *srv,
+    const char *protocol,
+    PDNS_SRV_RECORD **ploc,
+    int *pnum
+    )
+{
+    char service[128], host[128];
+    int len;
+    unsigned char reply[1024];
+    int status;
+    unsigned char *p;
+    int priority, weight;
+    u_short port;
+    PDNS_SRV_RECORD *locs;
+    int num_locs = 0;
+    
+    if (!protocol)
+       protocol = TCP_PROTO;
+    
+    
+#ifdef HAVE_SNPRINTF
+       snprintf(service, sizeof(service), "%s.%s.%s.",
+                srv, protocol, domain);
+#else
+       sprintf(service, "%s.%s.%s.",
+               srv, protocol, domain);
+#endif
+    len = res_search(service, C_IN, T_SRV, reply, sizeof(reply));
+    if (len >=0) {
+
+               /* Parse out query */
+               p = reply;
+               p += sizeof(HEADER);
+               status = dn_expand(reply, reply + len, p, host,
sizeof(host));
+               if (status < 0) {
+                       return -1;
+               }
+               p += status;
+               p += 4;
+
+               while (p < reply + len) {
+                       int type, class, ttl, size;
+                       status = dn_expand(reply, reply + len, p, host,
sizeof(host));
+                       if (status < 0) {
+                               if (num_locs) {
+                                       break;
+                               }
+                               else {
+                                       return -1;
+                               }
+                       }
+                       p += status;
+                       type = (p[0] << 8) | p[1];
+                       p += 2;
+                       class = (p[0] << 8) | p[1];
+                       p += 2;
+                       ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8)
| p[3];
+                       p += 4;
+                       size = (p[0] << 8) | p[1];
+                       p += 2;
+                       if (type == T_SRV) {
+                               status = dn_expand(reply, reply + len, p
+ 6, host, sizeof(host));
+                               if (status < 0) {
+                                       return -1;
+                               }
+                               priority = (p[0] << 8) | p[1];
+                               weight = (p[2] << 8) | p[3];
+                               port = (p[4] << 8) | p[5];
+
+                               if (num_locs) {
+                                       locs = realloc(locs,
sizeof(PDNS_SRV_RECORD)*(num_locs+1));
+                               }
+                               else {
+                                       locs =
malloc(sizeof(PDNS_SRV_RECORD));
+                               }
+                               locs[num_locs] =
malloc(sizeof(DNS_SRV_RECORD));
+                               locs[num_locs]->srv_host = strdup(host);
+                               locs[num_locs]->srv_port = port;
+                               locs[num_locs]->srv_weight = weight;
+                               locs[num_locs]->srv_priority = priority;
+                               num_locs++;
+                               p += size;
+                       }
+               }
+    }
+
+    qsort(locs, num_locs, sizeof(PDNS_SRV_RECORD),
dns_rr_priority_sort);
+    
+    *ploc = locs;
+    *pnum = num_locs;
+    return 0;
+}
diff -urN ../rlm_ldap/locator.h /home/amaguire/rlm_ldap/locator.h
--- ../rlm_ldap/locator.h       1970-01-01 12:00:00.000000000 +1200
+++ /home/amaguire/rlm_ldap/locator.h   2004-06-26 17:42:37.000000000
+1200
@@ -0,0 +1,76 @@
+/*--
+
+MICROSOFT BROAD SOURCE LICENSE 
+
+This License governs use of the accompanying Software.  Microsoft
+hopes you find this Software useful.
+
+You are licensed to do anything you want with the Software. 
+
+In return, we simply require that you agree:
+
+1. Not to remove any copyright notices from the Software.
+
+2. That the Software comes "as is", with no warranties.  None
+   whatsoever. This means no implied warranty of merchantability
+   or fitness for a particular purpose or any warranty of
+   non-infringement.  Also, you must pass this disclaimer
+   on whenever you distribute the Software.
+
+3. That we will not be liable for any of those types of damages
+   known as indirect, special, consequential, or incidental related
+   to the Software or this License. Also, you must pass this
+   limitation of liability on whenever you distribute the Software.
+
+4. That if you sue anyone over patents that you think may apply to
+   the Software, your license to the Software ends automatically
+   (this applies even when the rest of the License ends).
+
+5. That the patent rights Microsoft is licensing only apply to
+   the Software, not to any derivatives you make.
+
+6. That your rights under the License end automatically if you
+   breach this in any way.
+
+THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
+ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+PARTICULAR PURPOSE.
+
+Copyright (C) 1999  Microsoft Corporation.  All rights reserved.
+
+Module Name:
+
+    locator.h
+
+Abstract:
+
+    Locate SRV records in DNS
+
+--*/
+
+#ifndef T_SRV
+#define T_SRV 33
+#endif
+
+typedef struct _dns_srv_record
+{
+    char       *srv_host;
+    u_short    srv_port;
+    int        srv_weight;
+    int        srv_priority;
+} DNS_SRV_RECORD, *PDNS_SRV_RECORD;
+
+/*
+ * Locate SRV records in DNS.
+ */
+int dns_locate_srv(const char *, const char *, const char *,
PDNS_SRV_RECORD **, int *);
+
+#define LDAP_SRV       "_ldap"
+#define KRB5_SRV       "_kerberos"
+#define KMASTER_SRV    "_kerberos-master"
+#define KADMIN_SRV     "_kerberos-adm"
+#define KPASSWD_SRV    "_kpasswd"
+
+#define TCP_PROTO      "_tcp"
+#define UDP_PROTO      "_udp"
diff -urN ../rlm_ldap/rlm_ldap.c /home/amaguire/rlm_ldap/rlm_ldap.c
--- ../rlm_ldap/rlm_ldap.c      2004-04-30 19:54:56.000000000 +1200
+++ /home/amaguire/rlm_ldap/rlm_ldap.c  2004-06-26 17:42:37.000000000
+1200
@@ -180,12 +180,18 @@
 #include       <errno.h>
 #include       <unistd.h>
 #include       <pthread.h>
+#ifdef HAVE_SASL_SASL_H
+#include       <sasl/sasl.h>
+#elif defined(HAVE_SASL_H)
+#include       <sasl.h>
+#endif
 
 #include        "libradius.h"
 #include       "radiusd.h"
 #include       "conffile.h"
 #include       "modules.h"
 #include       "rad_assert.h"
+#include       "locator.h"
 
 #ifndef HAVE_PTHREAD_H
 /*
@@ -262,6 +268,8 @@
        int             default_allow;
        int             failed_conns;
        int             is_url;
+       int             use_sasl;
+       int             dns_lookup_host;
        char           *login;
        char           *password;
        char           *filter;
@@ -288,6 +296,9 @@
        char            *tls_keyfile;
        char            *tls_randfile;
        char            *tls_require_cert;
+       char            *sasl_secprops;
+       char            *sasl_auth_id;
+       char            *dns_lookup_domain;
 }               ldap_instance;
 
 /* The default setting for TLS Certificate Verification */
@@ -331,6 +342,13 @@
        {"compare_check_items", PW_TYPE_BOOLEAN,
offsetof(ldap_instance,do_comp), NULL, "no"},
        {"access_attr_used_for_allow", PW_TYPE_BOOLEAN,
offsetof(ldap_instance,default_allow), NULL, "yes"},
        {"do_xlat", PW_TYPE_BOOLEAN, offsetof(ldap_instance,do_xlat),
NULL, "yes"},
+       /* Indicates whether LDAP SASL Interactive Bind should be used,
and associated properties */
+       {"use_sasl", PW_TYPE_BOOLEAN, offsetof(ldap_instance,use_sasl),
NULL, "no"},
+       {"sasl_secprops", PW_TYPE_STRING_PTR,
offsetof(ldap_instance,sasl_secprops), NULL, NULL},
+       {"sasl_auth_id", PW_TYPE_STRING_PTR,
offsetof(ldap_instance,sasl_auth_id), NULL, NULL},
+       /* Indicates whether DNS SRV records should be used to locate
directory servers */
+       {"dns_lookup_host", PW_TYPE_BOOLEAN,
offsetof(ldap_instance,dns_lookup_host), NULL, "no"},
+       {"dns_lookup_domain", PW_TYPE_STRING_PTR,
offsetof(ldap_instance,dns_lookup_domain), NULL, NULL},
 
        {NULL, -1, 0, NULL, NULL}
 };
@@ -405,11 +423,40 @@
                return -1;
        }
 
-       if (inst->server == NULL) {
-               radlog(L_ERR, "rlm_ldap: missing 'server' directive.");
-               free(inst);
-               return -1;
+       /* Try DNS locator. */
+       if(inst->dns_lookup_host) {
+               radlog(L_INFO, "rlm_ldap: Using DNS SRV [RFC2782] to
locate directory servers. Ignore 'server' directive.");
+               if (inst->dns_lookup_domain == NULL) {
+                       /* should dynamically find domain name. */
+                       radlog(L_ERR, "rlm_ldap: Missing
'dns_lookup_domain' directive.");
+                       free(inst);
+                       return -1;
+               }
+               DEBUG("rlm_ldap: Domain %s, will be used to locate
directory servers.", inst->dns_lookup_domain);
        }
+       else {
+               if (inst->server == NULL) {
+                       radlog(L_ERR, "rlm_ldap: Missing 'server'
directive.");
+                       free(inst);
+                       return -1;
+               }
+       }
+
+/* Define if you have the ldap_sasl_interactive_bind_s function.  */
+#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
+       if(inst->use_sasl){
+               /* set login as NULL so we know to use a
ldap_sasl_interactive_bind_s(), use a
+               ldap_simple_bind() for user authenication if dn is not
NULL. */
+               DEBUG("rlm_ldap: Using SASL [RFC2222] GSSAPI mechanism
to bind to directory servers. Ignore 'identity' and 'password'
directives.");
+               inst->login = NULL;
+               inst->password = NULL;
+               if (inst->sasl_auth_id == NULL) {
+                       radlog(L_ERR, "rlm_ldap: Missing 'sasl_auth_id'
directive.");
+                       free(inst);
+                       return -1;
+               }
+       }
+#endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */
        inst->is_url = 0;
        if (ldap_is_ldap_url(inst->server)){
 #ifdef HAVE_LDAP_INITIALIZE
@@ -746,6 +793,43 @@
        return res;
 }
 
+/* Define if you have the ldap_sasl_interactive_bind_s function.  */
+#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
+static int ldap_sasl_interact (LDAP * ld, unsigned flags, void
*defaults, void *_interact)
+{
+       char *authzid = (char *) defaults;
+       sasl_interact_t *interact = (sasl_interact_t *) _interact;
+
+       while (interact->id != SASL_CB_LIST_END)
+       {
+               if (interact->id == SASL_CB_USER) {
+                       if (authzid != NULL) {
+                               interact->result = authzid;
+                               interact->len = strlen (authzid);
+                       }
+                       else if (interact->defresult != NULL) {
+                               interact->result = &interact->defresult;
+                               interact->len = strlen
(interact->defresult);
+                       }
+                       else {
+                               interact->result = "";
+                               interact->len = 0;
+                       }
+#if SASL_VERSION_MAJOR < 2
+                       interact->result = strdup(interact->result);
+                       if (interact->result == NULL) {
+                               return LDAP_NO_MEMORY;
+                       }
+#endif /* SASL_VERSION_MAJOR < 2 */
+               }
+               else {
+                       return LDAP_PARAM_ERROR;
+               }
+               interact++;
+       }
+       return LDAP_SUCCESS;
+}
+#endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */
 
 /*
  *     Translate the LDAP queries.
@@ -1532,9 +1616,10 @@
 {
        ldap_instance  *inst = instance;
        LDAP           *ld = NULL;
-       int             msgid, rc, ldap_version;
+       int             msgid, rc, ldap_version, nh, num_ds_hosts;
        int             ldap_errno = 0;
        LDAPMessage    *res;
+       PDNS_SRV_RECORD *ds_hosts;
 
        if (inst->is_url){
 #ifdef HAVE_LDAP_INITIALIZE
@@ -1547,11 +1632,42 @@
 #endif
        }
        else{
-               DEBUG("rlm_ldap: (re)connect to %s:%d, authentication
%d", inst->server, inst->port, auth);
-               if ((ld = ldap_init(inst->server, inst->port)) == NULL)
{
-                       radlog(L_ERR, "rlm_ldap: ldap_init() failed");
-                       *result = RLM_MODULE_FAIL;
-                       return (NULL);
+               /* Use DNS SRV records to locate a directory server */
+               if (inst->dns_lookup_host) {
+                       DEBUG("rlm_ldap: (re)connect to %s.%s.%s,
authentication %d", LDAP_SRV, TCP_PROTO, inst->dns_lookup_domain, auth);
+                       if (dns_locate_srv(inst->dns_lookup_domain,
LDAP_SRV, TCP_PROTO, &ds_hosts, &num_ds_hosts) == -1 || num_ds_hosts ==
0) {
+                               radlog(L_ERR, "rlm_ldap: cannot locate a
directory server.");
+                               *result = RLM_MODULE_FAIL;
+                               return (NULL);
+                       }
+                       for (nh = 0; nh < num_ds_hosts; nh++) {
+                               DEBUG("rlm_ldap: (re)connecting to
%s:%d", ds_hosts[nh]->srv_host, ds_hosts[nh]->srv_port);
+                               /* Try ldap_init() for each host, I
think this is a more flexible way than passing all hosts. */
+                               ld = ldap_init(ds_hosts[nh]->srv_host,
ds_hosts[nh]->srv_port);
+                               if (ld) {
+                                       /* Update 'ldap_instance' struct
with 'host' and 'port' info, for DEBUG() outputs. */
+                                       inst->server =
strdup(ds_hosts[nh]->srv_host);
+                                       inst->port =
ds_hosts[nh]->srv_port;
+                                       DEBUG("rlm_ldap: (re)connected
to %s:%d", inst->server, inst->port);
+                                       break;
+                               }
+                       }
+                       /* Cleanup. */
+                       dns_free_srv(ds_hosts, &num_ds_hosts);
+
+                       if (ld == NULL) {
+                               radlog(L_ERR, "rlm_ldap: ldap_init()
failed");
+                               *result = RLM_MODULE_FAIL;
+                               return (NULL);
+                       }
+               }
+               else {
+                       DEBUG("rlm_ldap: (re)connect to %s:%d,
authentication %d", inst->server, inst->port, auth);
+                       if ((ld = ldap_init(inst->server, inst->port))
== NULL) {
+                               radlog(L_ERR, "rlm_ldap: ldap_init()
failed");
+                               *result = RLM_MODULE_FAIL;
+                               return (NULL);
+                       }
                }
        }
        if (ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, (void *)
&(inst->net_timeout)) != LDAP_OPT_SUCCESS) {
@@ -1666,13 +1782,31 @@
        }
 #endif /* HAVE_LDAP_START_TLS */
 
+/* Define if you have the ldap_sasl_interactive_bind_s function.  */
+#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
+       if (inst->use_sasl) {
+               if(ldap_set_option(ld, LDAP_OPT_X_SASL_SECPROPS, 
+                       (void *) inst->sasl_secprops) !=
LDAP_OPT_SUCCESS) {
+                               radlog(L_ERR, "rlm_ldap: Could not set
SASL security properties (LDAP_OPT_X_SASL_SECPROPS) %s",
inst->sasl_secprops);
+               }
+       }
+#endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */
+
        if (inst->is_url){
                DEBUG("rlm_ldap: bind as %s/%s to %s", dn, password,
inst->server);
        }
        else{
                DEBUG("rlm_ldap: bind as %s/%s to %s:%d", dn, password,
inst->server, inst->port);
        }
-       msgid = ldap_bind(ld, dn, password,LDAP_AUTH_SIMPLE);
+/* Define if you have the ldap_sasl_interactive_bind_s function.  */
+#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
+       if(inst->use_sasl && dn == NULL) {
+               ldap_errno = ldap_sasl_interactive_bind_s(ld, dn,
"GSSAPI", NULL, NULL, LDAP_SASL_QUIET,
+                       ldap_sasl_interact, (void *)inst->sasl_auth_id);
+               goto resultcheck;
+       }
+#endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */
+       msgid = ldap_simple_bind(ld, dn, password);
        if (msgid == -1) {
                ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
                if (inst->is_url)
@@ -1705,6 +1839,7 @@
                return (NULL);
        }
        ldap_errno = ldap_result2error(ld, res, 1);
+resultcheck:
        switch (ldap_errno) {
        case LDAP_SUCCESS:
                DEBUG("rlm_ldap: Bind was successful");
@@ -1779,6 +1914,12 @@
                free((char *) inst->access_attr);
        if (inst->profile_attr)
                free((char *) inst->profile_attr);
+       if (inst->sasl_secprops)
+               free((char *) inst->sasl_secprops);
+       if (inst->sasl_auth_id)
+               free((char *) inst->sasl_auth_id);
+       if (inst->dns_lookup_domain)
+               free((char *) inst->dns_lookup_domain);
        if (inst->conns){
                int i=0;

-
List info/subscribe/unsubscribe? See http://www.freeradius.org/list/users.html

Reply via email to