Piotr Roszatycki said:
> Why MySQL support for authdaemon is more flexible than LDAP support? I
> don't understand why I can find MYSQL_WHERE_CLAUSE option which
> provides me customised query but I can't find similar feature for LDAP.
> I have to use very bad and ugly workaround with LDAP ACL which probably
> will slow down my database to use customised LDAP query because there
> is no
> possibility to use additonal filter.

Well, seeing as the code is open source under the GPL, you always have the
option of adding what you need, or hiring someone else to do it.  That _is_
the "Free" in Free Software. ;-)

> Do you know how to set up slapd.conf so I could check additional
> contition: 'inetSubscriberStatus=active'? As far as Courier have poor
> LDAP support I think the ACLs are only solution.

Speaking of which, I needed something, so I added it.  It sounds relevant
to your needs, and I'm happy to share.  Unfortunately, I forgot to add a
feature for LDAP_EXTRA_FILTER, which would allow you to specify arbitrary
extra filter text.  There is a good example of how to do this in the code
for LDAP_GROUP_EXTRA_FILTER, however.  It should be a fairly easy fix, but
I'm unlikely do add it real soon, as I don't need it.

The attached file is a modified authlib/authldaplib.c.  There is a comment
block near the top about the added features, which may be suitable for your
needs.  There is a potential issue around LDAP_AUTHBIND caching, which I
will be fixing, RSN.

I don't have a commented example authldaprc with the extra configuration
items, but it's on my "to do" list.

Kelvin

> --
> Piotr Roszatycki, Netia Telekom S.A.                    .''`.
> mailto:[EMAIL PROTECTED]                   : :' :
> mailto:[EMAIL PROTECTED]                               `. `'
>                                                         `-
>
>
> _______________________________________________
> courier-users mailing list
> [EMAIL PROTECTED]
> Unsubscribe: https://lists.sourceforge.net/lists/listinfo/courier-users


/*
 * authldap.c - 
 *
 * courier-imap - 
 * 
 * Copyright 1999 Luc Saillard <[EMAIL PROTECTED]>.
 *
 * This module use a server LDAP to authenticate user.
 * See the README.ldap
 *
 * 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 2
 * 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; see the file COPYING. If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 
 * Boston, MA  02111-1307, USA.
 */

/*
 * Modified 21/02/2002 Kelvin Cookshaw <[EMAIL PROTECTED]>
 *
 * Should have done:
 *
 * 1. Should have added LDAP_EXTRA_FILTER, to allow extra filter text to
 *    be (& ...)ed to the initial filter (the non-group filter.)
 *
 * Did get done:
 *    
 * 1. Added LDAP_SCOPE to permit search scopes sub, oneLevel, base.
 *    
 * 2. Added LDAP_GROUP_BASEDN, LDAP_GROUP_SCOPE,
 *    LDAP_GROUP_EXTRA_FILTER, LDAP_GROUP_MEMBER, LDAP_GROUP_VALUE to
 *    allow a final query that checks for membership of the
 *    authenticated DN in a group.  An additional query was added if
 *    these options are used.
 *    
 * 3. Perform authenticating bind (LDAP_AUTHBIND) before account
 *    information is retrieved, to allow for tighter security in the
 *    directory.  Now, only the authenticated DN needs to be able to
 *    read their own attributes.  There is less need to bind as the
 *    Manager, or allow anonymous access to account information.  An
 *    additional query is performed for LDAP_AUTHBIND.
 *    
 * 4. Cache all connections.  Previously, the global connection,
 *    my_ldap_fp, was cached, but connections due to LDAP_AUTHBIND
 *    were not.  Currently, there is no builtin limit on how long an
 *    authenticated connection can be cached, and a password change
 *    will not be reflected in the cache.
 *
 *    New connections in the cache push out the least-recently
 *    accessed connection.  The previous behaviour can be preserved by
 *    setting the LDAP_CACHE_SIZE to 1.  Connections that are
 *    currently in use will not be pushed out of the cache.
 *
 *    This cache is suitable only for a small number of items, as a
 *    linear search is performed to find a cache item.  The default
 *    connection, which is used every request, is always the first
 *    item in the cache, and the quickest found.
 *
 * 5. A memory leak in copy_value (an extraneous strdup()) was fixed.
 *    
 * 6. Attribute arrays are prepared only once, rather than with each
 *    request.
 *    
 * 7. Added debug and log functions that require stdarg.h.  These got
 *    rid of a ton of precompiler statements.
 *    
 * 8. Refactored a little. :-)
 */ 

/*
 * Modified 28/11/2001 Iustin Pop <[EMAIL PROTECTED]>
 * There was a bug regarding the LDAP_TLS option: if both LDAP_TLS
 * and was LDAP_AUTHBIND were enabled, the ldap_start_tls function
 * was called only for the first connection, resulting in the fact
 * that the bind for checking the password was done without TLS,
 * sending the password in clear text over the network. Detected 
 * when using OpenLDAP with "security ssf=128" (which disalows any 
 * clear-text communication).
*/

/*
   Modified 01/21/2000 James Golovich <[EMAIL PROTECTED]>

1. If LDAP_AUTHBIND is set in the config file, then the ldap server will
handle passwords via authenticated binds, instead of checking these
internally.
2. Changed paramaters for authldap_get to include pass.
3. Added int authbind and int authbind_ok to struct ldap_info to
handle authenticated binds.

*/

/*
   Modified 12/31/99 Sam Varshavchik:

1. read_env reads from a configuration file, instead of the environment
2. read_config appropriately modified.
3. If 'user' contains the @ character, domain from config is NOT appended.
4. added 'homeDir' attribute.  Use 'homeDir' instead of mailDir, and put
   mailDir into MAILDIR=
5. read_config renamed to authldap_read_config
6. get_user_info renamed to authldap_get
7. Added authldap_free_config, to clean up all the allocated memory
   (required for preauthldap).
8. Output LDAP attributes are defined in the configuration file as well.
9. Allow both plaintext and crypted passwords to be read from LDAP.
10. Added GLOB_UID GLOB_GID, as well as UID and GID params.

2/19/2000 Sam.

Rewrite to allow this code to be used in a long-running authentication daemon
(for Courier).  authldap_get renamed to authldapcommon, will initialize and
maintain a persistent connection.  Password checking moved entirely to
authldap.c.  authldapclose() will unbind and close the connection.

connection gets closed and reopened automatically after a protocol error.

error return from authldapcommon will indicate whether this is a transient,
or a permanent failure.

authldap_free_config removed - no longer required.

*/

#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_LBER_H
#include <lber.h>
#endif
#if HAVE_LDAP_H
#include <ldap.h>
#if LDAP_VENDOR_VERSION > 20000
#define OPENLDAPV2
#endif
#endif
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#if HAVE_SYSLOG_H
#include <syslog.h>
#else
#define syslog(a,b)
#endif

#include "authldap.h"
#include "auth.h"
#include "authldaprc.h"

#include "stdarg.h"

#define DEBUG_LDAP 0

static void debug(const char * format, ...);
static void auth_ldap_log(const char * format, ...);

struct ldap_info
{
	const char *hostname;
	int   port;
	const char *binddn;
	const char *bindpw;
	const char *basedn;
	const char *mail;
	const char *domain;
        const char *scope;
        const char *groupbasedn;
        const char *groupscope;
        const char *groupextra;
        const char *groupmember;
        const char *groupvalue;
	uid_t uid;
	gid_t gid;
	struct timeval timeout;
	int   authbind;
	int   deref;
	int   tls;

	int cachesize;

	const char *attributes[10];
	const char *ldap_attributes[10];
};

/*
 * Connection cache
 * Keep some connections alive, so we don't have to reopen them often.
 * Configure the number of connections kept around with the
 * LDAP_CACHE_SIZE item.
 */
struct conn_item
{
	LDAP * conn;
	int createtime;
	int lastaccesstime;
	int references;
	const char * binddn;
	const char * bindpw;
};

struct conn_cache
{
	int max_slots;
	struct conn_item ** items;
};

static struct conn_cache cache = { 0, NULL };

static int cache_init(struct conn_cache * cache, int size)
{
	int i;
	if (size < 1) {
		return 1;
	}

	cache->max_slots = size;
	cache->items = (struct conn_item**)malloc(size * sizeof(*cache->items));
	if (cache->items == NULL)
	{
		auth_ldap_log("Could not allocate %d * %d bytes.", size,
			      sizeof(*cache->items));
		return 1;
	}

	for (i = 0; i < size; i++)
	{
		cache->items[i] = NULL;
	}
	return 0;
}


/*
 * Get an item without allocating it.
 */
static struct conn_item * cache_get(const char * binddn)
{
	int i;
	for (i = 0; i < cache.max_slots; i++)
	{
		if (cache.items[i] == NULL)
		{
			continue;
		}
		if ((binddn == NULL || cache.items[i]->binddn == NULL) &&
		    binddn != cache.items[i]->binddn)
		{
			continue;
		}

		if (binddn == cache.items[i]->binddn ||
		    strcasecmp(cache.items[i]->binddn, binddn) == 0)
		{
			debug("Found a matching cache, slot: %d, create=%d, access=%d, refs=%d",
			      i, cache.items[i]->createtime,
			      cache.items[i]->lastaccesstime,
			      cache.items[i]->references);
			return cache.items[i];
		}
	}
	debug("No cache match found for %s", binddn);
	return NULL;
}

/*
 * Allocate an item.
 */
static struct conn_item * cache_alloc(const char * binddn)
{
	struct conn_item * item = cache_get(binddn);
	if (!item)
	{
		return NULL;
	}
	debug("Updating cache item for %s", binddn);
	item->lastaccesstime = time(NULL);
	item->references++;
	return item;
}


static int cache_get_index(LDAP * conn)
{
	int i;
	for (i = 0; i < cache.max_slots; i++)
	{
		if (cache.items[i] && cache.items[i]->conn == conn)
		{
			return i;
		}
	}
	return -1;
}

static void cache_remove(LDAP * conn)
{
	int i = cache_get_index(conn);
	if (i < 0)
	{
		auth_ldap_log("Attempted to remove a connection, but it was not in the cache.");
		return;
	}
	else
	{
		struct conn_item * item = cache.items[i];
		cache.items[i] = NULL;
		free((char *)item->binddn);
		free((char *)item->bindpw);
		free(item);
	}
}

static void cache_clear(struct conn_cache * cache)
{
	int i;
	for (i = 0; i < cache->max_slots; i++)
	{
		if (cache->items[i])
		{
			struct conn_item * item = cache->items[i];
			cache->items[i] = NULL;
			ldap_unbind(item->conn);
			free(item);
		}
	}			
}

static void close_conn(LDAP * conn);

static void cache_add(LDAP * conn, const char * binddn, const char * bindpw,
		      int references, int * prc)
{
	struct conn_item * item;
	int slot = -1;
	int now = time(NULL);
	int age = now;
	int oldslot = -1;
	int i;

	*prc = 0;

	for (i = 0; i < cache.max_slots; i++)
	{
		if (cache.items[i] == NULL) {
			slot = i;
			break;
		}
		else
		{
			if (cache.items[i]->lastaccesstime < age &&
			    cache.items[i]->references < 1)
			{
				oldslot = i;
				age = cache.items[i]->lastaccesstime;
			}
		}
	}

	if (slot < 0 && oldslot >= 0)
	{
		close_conn(cache.items[oldslot]->conn);
		slot = oldslot;
	}

	if (slot < 0)
	{
		*prc = 1;
		return;
	}

	item = (struct conn_item *)malloc(sizeof(struct conn_item));
	item->conn = conn;
	item->binddn = binddn ? strdup(binddn) : NULL;
	item->bindpw = bindpw ? strdup(bindpw) : NULL;
	item->references = references;
	item->createtime = time(NULL);
	item->lastaccesstime = item->createtime;

	cache.items[slot] = item;
}

static void close_conn(LDAP * conn)
{
	cache_remove(conn);
	ldap_unbind(conn);
}

void release_conn(LDAP * conn, int * prc)
{
	int i = cache_get_index(conn);
	*prc = 0;
	if (i < 0)
	{
		debug("Attempted to release a connection, but it was not in the cache.");
		ldap_unbind(conn);
		/* not an error, just a full cache. */
		return;
	};

	cache.items[i]->references--;
	return;
}

static LDAP * auth_ldap_bind(const char * binddn,
			  const char * bindpass, int * prc);

static LDAP * get_conn(const char * binddn, const char * bindpw, int * prc)
{
	LDAP * conn;
	struct conn_item * item;
	*prc = 0;

	debug("Looking for a connection for %s", binddn);

	item = cache_alloc(binddn);
	if (item)
	{
		if ((!bindpw && !item->bindpw) ||
		    strcmp(bindpw, item->bindpw) == 0)
		{
			item->references++;
			return item->conn;
		}
		else
		{
			close_conn(item->conn);
		}
	}
	debug("No cached connection, make a new one for %s", binddn);
	conn = auth_ldap_bind(binddn, bindpw, prc);
	if (*prc == 0)
	{
		cache_add(conn, binddn, bindpw, 1, prc);
		if (*prc != 0)
		{
			/* we can safely return the connection, since it will
			 * be closed when we try to release it, later, when we
			 * discover it has no slot.
			 */
			auth_ldap_log("No room in the cache for %s.", binddn);
		}
		return conn;
	}
	return NULL;
}	

/*
 * This will (probably) only work with an ANSI stdandard stdarg.h
 * Hmmmm... may not be acceptable.
 */
static void debug(const char * format, ...)
{
#if DEBUG_LDAP
	char message[256];
	va_list pvar;
	va_start(pvar, format);

#if HAVE_SYSLOG_H
	/* The result of the vsnprintf _must_not_ contain an unescaped '%'. */
	vsnprintf(message, sizeof(message), format, pvar);
	syslog(LOG_DAEMON | LOG_CRIT, "[%d] %s", getpid(), message);
#else
	fvprintf(stderr, format, pvar);
#endif

	va_end(pvar);
#endif
}

static void auth_ldap_log(const char * format, ...)
{
#if HAVE_SYSLOG_H
	char message[256];
	va_list pvar;
	va_start(pvar, format);

	/* The result of the vsnprintf _must_not_ contain an unescaped '%'. */
	vsnprintf(message, sizeof(message), format, pvar);
	syslog(LOG_DAEMON | LOG_CRIT, message);
	va_end(pvar);
#endif
}

static void auth_ldap_log_error(int ldrc, const char * format)
{
#if	HAVE_SYSLOG_H
	const char * s = ldap_err2string(ldrc);
	syslog(LOG_DAEMON|LOG_CRIT, format, s);
#endif
}

/*
** There's a memory leak in OpenLDAP 1.2.11, presumably in earlier versions
** too.  See http://www.OpenLDAP.org/its/index.cgi?findid=864 for more
** information.  To work around the bug, the first time a connection fails
** we stop trying for 60 seconds.  After 60 seconds we kill the process,
** and let the parent process restart it.
**
** We'll control this behavior via LDAP_MEMORY_LEAK.  Set it to ZERO to turn
** off this behavior (whenever OpenLDAP gets fixed).
*/

static time_t ldapfailflag=0;

static void ldapconnfailure()
{
	const char *p=getenv("LDAP_MEMORY_LEAK");

	if (!p) p="1";

	if (atoi(p) && !ldapfailflag)
	{
		time(&ldapfailflag);
		ldapfailflag += 60;
	}
}

static int ldapconncheck()
{
	time_t t;

	if (!ldapfailflag)
		return (0);

	time(&t);

	if (t >= ldapfailflag)
		exit(0);
	return (1);
}

/*
 * Function: read_env
 * Copy the environnement $env and copy to $copy if not null
 * if needit is false, and env doesn't exist, copy $value_default to $copy
 * INPUT:
 *   $env: pointer to the environnement name
 *   $copy: where the value go
 *   $err: print a nice message when $env not_found and $needit is true
 *   $needit: if $needit is true and $value not found, return a error
 *   $value_default: the default value when $needit is false and $env doesn't exists
 * OUTPUT:
 *   boolean
 */
static int read_env(const char *env, const char **copy,
	const char *err, int needit, const char *value_default)
{
	static char *ldapauth=0;
	static size_t ldapauth_size=0;
	size_t	i;
	char	*p=0;
	int	l=strlen(env);

	if (!ldapauth)
	{
		FILE	*f=fopen(AUTHLDAPRC, "r");
		struct	stat	buf;

		if (!f)	return (0);
		if (fstat(fileno(f), &buf) ||
		    (ldapauth=malloc(buf.st_size+2)) == 0)
		{
			fclose(f);
			return (0);
		}
		if (fread(ldapauth, buf.st_size, 1, f) != 1)
		{
			free(ldapauth);
			ldapauth=0;
			fclose(f);
			return (0);
		}
		ldapauth[ldapauth_size=buf.st_size]=0;

		for (i=0; i<ldapauth_size; i++)
			if (ldapauth[i] == '\n')
				ldapauth[i]=0;
	}

	for (i=0; i<ldapauth_size; )
	{
		p=ldapauth+i;
		if (memcmp(p, env, l) == 0 &&
		    isspace((int)(unsigned char)p[l]))
		{
			p += l;
			while (*p && *p != '\n' &&
			       isspace((int)(unsigned char)*p))
				++p;
			break;
		}

		while (i < ldapauth_size)
			if (ldapauth[i++] == 0)	break;
	}

	if (i < ldapauth_size)
	{
		*copy= p;
		return (1);
	}

	if (needit)
	{
		fprintf(stderr, "ERR: %s\n",err);
		fflush(stderr);
		return 0;
	}

	*copy=0;
	if (value_default)
		*copy=value_default;

	return 1;
}

static void prepare_attributes(const char *attributes[],
			       const char *ldap_attributes[],
			       int array_size)
{
	int i;
	const char **pattr;

	/* We ignore the return value of read_env() here.
	 * This is not the first time it is called.
	 */
	read_env("LDAP_HOMEDIR", &attributes[0], "", 0, "homeDir");
	read_env("LDAP_MAILDIR", &attributes[1], "", 0, 0);
	read_env("LDAP_FULLNAME", &attributes[2], "", 0, "cn");
	read_env("LDAP_CLEARPW", &attributes[3], "", 0, 0);
	read_env("LDAP_CRYPTPW", &attributes[4], "", 0, 0);
	read_env("LDAP_UID", &attributes[5], "", 0, 0);
	read_env("LDAP_GID", &attributes[6], "", 0, 0);
	/* duplicates read_env done below for ldap->mail */
	read_env("LDAP_MAIL",&attributes[7], "", 0, "mail");

	read_env("LDAP_MAILDIRQUOTA", &attributes[8], "", 0, 0);

	pattr = ldap_attributes;
	for (i=0; i < array_size; i++)
	{
		if (attributes[i])
			*pattr++ = attributes[i];
	}

	*pattr = NULL;
}

/*
 * Function: authldap_read_config
 *   Read Configuration from the environnement table
 * INPUT:
 *   $ldap: a structure where we place information
 * OUTPUT:
 *   boolean
 */
static int authldap_read_config(struct ldap_info *ldap)
{
	struct passwd *pwent;
	struct group  *grent;
	const char *p;

	memset(ldap,0,sizeof(struct ldap_info));
	
	if (!read_env("LDAP_SERVER",&ldap->hostname,"You need to specify a ldap server in config file",1,NULL))
		return 0;

	if (!read_env("LDAP_AUTHBIND", &p, "", 0, ""))
		return (0);

	if (p)
		sscanf(p,"%d",&ldap->authbind);

	ldap->port=LDAP_PORT;

	if (!read_env("LDAP_PORT", &p, "", 0, ""))
		return (0);

	if (p)
		sscanf(p,"%d",&ldap->port);

	if (!read_env("LDAP_BASEDN",&ldap->basedn,"You need to specify a basedn in config file",1,NULL))
		return 0;
	if (!read_env("LDAP_BINDDN",&ldap->binddn,"You need to specify a BINDDN in config file",0,NULL))
		return 0;
	if (!read_env("LDAP_BINDPW",&ldap->bindpw,"You need to specify a password for the BINDDN in config file",0,NULL))
		return 0;
	if (!read_env("LDAP_MAIL",&ldap->mail,"You need to specify a attribute for mail in config file",0,"mail"))
		return 0;
	if (!read_env("LDAP_DOMAIN",&ldap->domain,"You need to specify a domain for mail in config file",0,""))
		return 0;
	if (!read_env("LDAP_SCOPE", &ldap->scope,
		      "You need to specify a scope for the search on the base DN in config file",
		      0, NULL))
		return 0;
	if (!read_env("LDAP_GROUP_BASEDN", &ldap->groupbasedn,
		      "You need to specify a group base DN in config file",
		      0, NULL))
		return 0;
	if (!read_env("LDAP_GROUP_SCOPE", &ldap->groupscope,
		      "You need to specify a group scope in config file",
		      0, NULL))
		return 0;
	if (!read_env("LDAP_GROUP_EXTRA_FILTER", &ldap->groupextra,
		      "You need to specify extra group filter text in config file",
		      0, NULL))
		return 0;
	if (!read_env("LDAP_GROUP_MEMBER", &ldap->groupmember,
		      "You need to specify a group membership attribute in config file",
		      0, NULL))
		return 0;
	if (!read_env("LDAP_GROUP_VALUE", &ldap->groupvalue,
		      "You need to specify a group membership value in config file",
		      0, NULL))
		return 0;

	p=0;
	ldap->uid=0;
	if (!read_env("LDAP_GLOB_UID", &p, "", 0, ""))
		return (0);

	if (p && *p)
	{
		unsigned long n;

		if (sscanf(p, "%lu", &n) == 1)
			ldap->uid=(uid_t)n;
		else
		{
			pwent=getpwnam(p);
			if (!pwent)
			{
				syslog(LOG_DAEMON|LOG_CRIT,
				       "authldap: INVALID LDAP_GLOB_UID\n");
				return (0);
			}
			ldap->uid=pwent->pw_uid;
		}
	}

	ldap->gid=0;
	p=0;
	if (!read_env("LDAP_GLOB_GID", &p, "", 0, ""))
		return (0);

	if (p && *p)
	{
		unsigned long n;

		if (sscanf(p, "%lu", &n) == 1)
			ldap->gid=(gid_t)n;
		else
		{
			grent=getgrnam(p);
			if (!grent)
			{
				syslog(LOG_DAEMON|LOG_CRIT,
				       "authldap: INVALID LDAP_GLOB_GID\n");
				return (0);
			}
			ldap->gid=grent->gr_gid;
		}
	}

	ldap->timeout.tv_sec = 5;
	ldap->timeout.tv_usec = 0;
	p=0;
	if (read_env("LDAP_TIMEOUT", &p, "", 0, NULL) && p)
	{
		int timeout = 0; /* to prevent a warning from gcc. */
		sscanf(p,"%d",&timeout);
		ldap->timeout.tv_sec = timeout;
	}

	ldap->cachesize = 4;
	p = 0;
	if (read_env("LDAP_CACHE_SIZE", &p, "", 0, NULL) && p)
	{
		sscanf(p, "%d", &ldap->cachesize);
	}

	ldap->tls=0;
	p=0;
	if (read_env("LDAP_TLS", &p, "", 0, "") && p)
	{
		ldap->tls=atoi(p);
	}

	ldap->deref = LDAP_DEREF_NEVER; 
	p=0;
	if (!read_env("LDAP_DEREF", &p, "", 0, ""))
		return (0);
	if (p)
	{
		if (!strcasecmp (p, "never"))
			ldap->deref = LDAP_DEREF_NEVER;
		else if (!strcasecmp (p, "searching"))
			ldap->deref = LDAP_DEREF_SEARCHING;
		else if (!strcasecmp (p, "finding"))
			ldap->deref = LDAP_DEREF_FINDING;
		else if (!strcasecmp (p, "always"))
			ldap->deref = LDAP_DEREF_ALWAYS; 
	}

	prepare_attributes(ldap->attributes, ldap->ldap_attributes,
			     sizeof(ldap->attributes)
			   / sizeof(ldap->attributes[0]));

	return 1;
}

/*
 * Function: copy_value
 *   Copy value from a LDAP attribute to $copy
 * INPUT:
 *   $ld:       the connection with the LDAP server
 *   $entry:    the entry who contains attributes
 *   $attribut: this attribut
 *   $copy:     where data can go
 * OUTPUT:
 *   An allocated char * that must be freed.
 */
static char * copy_value(LDAP *ld, LDAPMessage *entry, const char *attribut,
	const char *username)
{
	char * copy = 0;
	char ** values;
	values=ldap_get_values(ld,entry, (char *)attribut);

	if (values==NULL)
	{
#ifdef OPENLDAPV2
		int ld_errno = ldap_result2error(ld,entry,0);
		if (ld_errno && ld_errno != LDAP_DECODING_ERROR)
			/* We didn't ask for this attribute */
			ldap_perror(ld,"ldap_get_values");
#else
		if (ld->ld_errno != LDAP_DECODING_ERROR)
			/* We didn't ask for this attribute */
			ldap_perror(ld,"ldap_get_values");
#endif
		return NULL;
	}
	/* We accept only attribute with one value */
	else if (ldap_count_values(values)>1)
	{
		syslog(LOG_DAEMON,
		       "authldaplib: duplicate attribute %s for %s\n",
		       attribut,
		       username);
		copy = NULL;
	}
	/* No value for this attribute. */
	else if (ldap_count_values(values)!=1)
	{
		copy = NULL;
	}
	else
	{
		copy=strdup(values[0]);
	}
/*	debug("copy_value %s: %s\n",attribut,values[0]); */

	ldap_value_free(values);
	return copy;
}

static struct ldap_info my_ldap;

/*
 * What exactly should this do?  It is called from outside this file.
 */
void authldapclose()
{
	cache_clear(&cache);
}

static int is_protocol_error(int rc)
{
#ifdef OPENLDAPV2
	return rc && !LDAP_NAME_ERROR(rc);
#else
	return rc && !NAME_ERROR(rc);
#endif
}

/* This function takes a ldap connection and 
 * tries to enable TLS on it.
*/
static int enable_tls_on(LDAP *conn)
{
#if HAVE_LDAP_TLS
	int version;
	int ldrc;

	ldrc = ldap_get_option(conn, LDAP_OPT_PROTOCOL_VERSION, &version);
	if (ldrc != LDAP_SUCCESS)
	{
		auth_ldap_log_error(ldrc, "ldap_get_option failed: %s");

		if (is_protocol_error(ldrc))
		{
			close_conn(conn);
			ldapconnfailure();
		}

		return (-1);
	}

	if (version < LDAP_VERSION3)
	{
		version = LDAP_VERSION3;
		(void)ldap_set_option (conn,
				       LDAP_OPT_PROTOCOL_VERSION,
				       &version);
	}

	ldrc = ldap_start_tls_s(conn, NULL, NULL);
	if (ldrc != LDAP_SUCCESS)
	{
		auth_ldap_log_error(ldrc, "ldap_start_tls_s failed: %s");

		if (is_protocol_error(ldrc))
		{
			close_conn(conn);
			ldapconnfailure();
		}
		return (-1);
	}
	return 0;
#else
	auth_ldap_log("TLS was requested, but is not compiled into authdaemond.ldap");
	return (-1);
#endif
}

static LDAP *ldapconnect()
{
	LDAP	*p;

	debug("Hostname: %s:%d\n",my_ldap.hostname,my_ldap.port);
	debug("UID:      %d\n",my_ldap.uid);
	debug("GID:      %d\n",my_ldap.gid);

	if (ldapconncheck())
		return (NULL);

	p=ldap_init((char *)my_ldap.hostname,my_ldap.port);

	if (p==NULL)
	{
		debug("Cannot connect to LDAP server (%s:%d): %s",
		      my_ldap.hostname, my_ldap.port, strerror(errno));
		ldapconnfailure();
	}
	return (p);
}

static LDAP * auth_ldap_bind(const char * binddn,
			  const char * bindpw, int * prc)
{
	int ldrc = 0;
	LDAP * bindp = ldapconnect();

	*prc = 0;

	debug("auth_ldap_bind: %s, %s\n", binddn, bindpw);

	if (!bindp)
	{
		*prc = 1;
		return NULL;
	}

	if(my_ldap.tls && enable_tls_on(bindp)) {
		auth_ldap_log("authlib: LDAP_TLS enabled but I'm unable to start tls, check your config\n");
		ldapconnfailure();
		ldap_unbind(bindp);
		*prc = -1;
		return NULL;
	}

#ifdef LDAP_OPT_DEREF

	/* Set deferencing mode */
	ldrc = ldap_set_option(bindp, LDAP_OPT_DEREF,
			       (void *) & my_ldap.deref);
	if (ldrc != LDAP_SUCCESS)
	{
		auth_ldap_log_error(ldrc, "ldap_set_option failed: %s");

		ldapconnfailure();
		ldap_unbind(bindp);
		*prc = -1;
		return NULL;
	}
#endif

	switch (ldrc = ldap_simple_bind_s(bindp, (char *)binddn,
					  (char *)bindpw))
	{
	case LDAP_SUCCESS:
		break;
	
	case LDAP_INVALID_CREDENTIALS:
		*prc = -1;
		break;
	
	default:
		*prc = 1;
		break;
	}
	
	debug("auth_ldap_bind ldrc=%d, rc=%d\n", ldrc, *prc);
	
	if (*prc != 0)
	{
		auth_ldap_log_error(ldrc, "ldap_simple_bind_s failed: %s");
		ldapconnfailure();
		ldap_unbind(bindp);
	}

	return bindp;
}

static int auth_ldap_do(const char *, const char *,
			int (*)(struct authinfo *, void *),
                        void *arg, const char *);

int auth_ldap_changepw(const char *dummy, const char *user,
		       const char *pass,
		       const char *newpass)
{
	return auth_ldap_do(user, pass, NULL, NULL, newpass);
}

/*
 * Function: authldapcommon
 *   Get information from the LDAP server ($ldap) for this $user
 * INPUT:
 *   $user: the login name
 *   $pass: the login password (NULL if we don't want to check the pw)
 *   callback - callback function with filled in authentication info
 *   arg - extra argument for the callback function.
 * OUTPUT:
 *   < 0 - authentication failure
 *   > 0 - temporary failure
 *   else return code from the callback function.
 */

int authldapcommon(const char *user, const char *pass,
		   int (*callback)(struct authinfo *, void *),
		   void *arg)
{
	return (auth_ldap_do(user, pass, callback, arg, NULL));
}

static int auth_ldap_do2(const char *user, const char *pass,
			int (*callback)(struct authinfo *, void *),
			 void *arg, const char *newpass);

static int first_time = 1;

static int auth_ldap_do(const char *user, const char *pass,
			int (*callback)(struct authinfo *, void *),
                        void *arg, const char *newpass)
{
	const char *p;
	int i;
	char *q, *r;

	if (first_time)
	{
		first_time = 0;
		if (authldap_read_config(&my_ldap) == 0)
		{
			return 1;
		}
		if (cache_init(&cache, my_ldap.cachesize))
		{
			return 1;
		}
	}

	for (i=0, p=user; *p; p++)
		if (strchr("*()\\", *p))
			++i;

	if (i == 0)
		return (auth_ldap_do2(user, pass, callback, arg, newpass));

	/* We need to escape special characters with '\', in user. */
	q=malloc(strlen(user)+i+1);

	if (!q)
	{
		perror("malloc");
		exit(1);
	}

	for (r=q, p=user; *p; p++)
	{
		if (strchr("*()\\", *p))
			*r++= '\\';
		*r++ = *p;
	}
	*r=0;

	i=auth_ldap_do2(q, pass, callback, arg, newpass);
	free(q);
	return (i);
}


/* 
 * Allocates new memory, and the caller owns it, and must cleanup.
 */
char * auth_ldap_make_filter(const char * extra, const char * attr,
			     const char * value)
{
	char * filter;
	
	if (!attr || !value)
		return 0;

	filter = malloc(1 + strlen(attr) + sizeof("(=)") + strlen(value) +
			(extra ? strlen(extra) + sizeof("(&)") : 0));

	if (!filter)
		return 0;

	*filter = 0;

	if (extra && strlen(extra) > 0)
		strcat(strcat(filter, "(&"), extra);

	strcat(strcat(strcat(strcat(strcat(
		filter, "("), attr), "="), value), ")");

	if (extra && strlen(extra) > 0)
		strcat(filter, ")");

	debug("Filter is \"%s\"\n", filter);

	return filter;
}

static char * auth_ldap_user_domain(const char * user, const char * domain)
{
	if ( domain && domain[0] && strchr(user, '@') == 0 )
	{
		char * userDomain = malloc(strlen(user) + strlen(domain) + 2);
		strcat(strcat(strcpy(userDomain, user), "@"), domain);
		return userDomain;
	}
	else
	{
		return strdup(user);
	}
}

static unsigned long get_number_value(LDAP *ld, LDAPMessage *entry,
				      const char *attribute, const char *user,
				      unsigned long defaultn)
{
	char *p = 0;
	unsigned long n;

	if (attribute == NULL)
	{
		return defaultn;
	}

	p = copy_value(ld, entry, attribute, user);

	if (p)
	{
		if (sscanf(p, "%lu", &n) < 1)
			n = defaultn;
		free(p);
	}
	return n;
}

static int prepare_callback_auth(LDAP * conn, LDAPMessage * result,
				 struct authinfo * pauth, const char * user)
{
	LDAPMessage * entry;
	memset(pauth, 0, sizeof(*pauth));

	/* Get the pointer on this result */
	entry = ldap_first_entry(conn, result);
	if (entry == NULL)
	{
		ldap_perror(conn,"ldap_first_entry");
		return -1;
	}

	/* Copy the directory and the password into struct */
	pauth->homedir = copy_value(conn, entry,
				    my_ldap.attributes[0], user);

	if (my_ldap.attributes[1])
		pauth->maildir = copy_value(conn, entry,
					    my_ldap.attributes[1], user);

	pauth->fullname = copy_value(conn, entry,
				     my_ldap.attributes[2], user);

	if (my_ldap.attributes[3])
		pauth->clearpasswd = copy_value(conn, entry,
						my_ldap.attributes[3], user);

	if (my_ldap.attributes[4])
		pauth->passwd = copy_value(conn, entry,
					   my_ldap.attributes[4], user);

	pauth->sysuserid = NULL;
	if (my_ldap.attributes[5] != NULL)
	{
		uid_t au = get_number_value(conn, entry,
					    my_ldap.attributes[5],
					    user, my_ldap.uid);
		uid_t * puid = malloc(sizeof(pauth->sysuserid));
		*puid = au;
		pauth->sysuserid = puid;
		debug("au= %d\n", au);
	}

	pauth->sysgroupid = get_number_value(conn, entry,
					     my_ldap.attributes[6],
					     user, my_ldap.gid);
	debug("ag= %d\n", pauth->sysgroupid);

	if (my_ldap.attributes[8])
		pauth->quota = copy_value(conn,entry,
					  my_ldap.attributes[8], user);

	/* Remember not to free these fields later. */
	if (user)
	{
		pauth->sysusername = user;
		pauth->address = user;
	}
	else
	{
		pauth->sysusername = "";
		pauth->address= "";
	}

	if (pauth->homedir == NULL)
	{
		pauth->homedir = strdup("");
	}

	return 0;
}

static void cleanup_callback_auth(struct authinfo * pauth)
{
	/* need to cast away constness for free. */
	free((uid_t *)pauth->sysuserid);
	free((char *)pauth->homedir);
	free((char *)pauth->fullname);
	free((char *)pauth->maildir);
	free((char *)pauth->quota);
	free((char *)pauth->passwd);
	free((char *)pauth->clearpasswd);
}

static int ldap_scope(const char *scope)
{
	if (!scope)
		return LDAP_SCOPE_SUBTREE;
	
	if (!strcasecmp(scope, "base"))
		return LDAP_SCOPE_BASE;
	
	if (!strcasecmp(scope, "onelevel"))
		return LDAP_SCOPE_ONELEVEL;
	
	if (!strcasecmp(scope, "subtree"))
		return LDAP_SCOPE_SUBTREE;
	
	return LDAP_SCOPE_DEFAULT;
}

static int auth_ldap_modify_passwd(LDAP * conn, const char * dn,
				   const char * newpass,
				   const char * newpass_crypt);

static int auth_ldap_check_group(LDAP * conn,
				 struct ldap_info * info,
				 LDAPMessage *entry, const char * user);

static int auth_ldap_do2(const char *user, const char *pass,
			 int (*callback)(struct authinfo *, void *),
			 void *arg, const char *newpass)
{
	LDAP * conn;
	LDAPMessage *result;
	char *newpass_crypt=0;
	char *filter;
	char *dn;
	struct authinfo auth;
	int rc = 0;
	char * userDomain;

	conn = get_conn(my_ldap.binddn, my_ldap.bindpw, &rc);
	if (rc)
	{
		return 1;
	}

	userDomain = auth_ldap_user_domain(user, my_ldap.domain);
	filter = auth_ldap_make_filter(NULL, my_ldap.mail, userDomain);
	free(userDomain);
	if (!filter)
	{
		perror("malloc");
		return 1;
	}

	if (my_ldap.authbind)
	{
		/* return no attributes, only the dn is needed. */
		const char *ldap_attributes[2] = { "1.1", NULL };

		rc = ldap_search_st(conn,
				    (char *)my_ldap.basedn,
				    ldap_scope(my_ldap.scope),
				    filter, (char **)ldap_attributes, 0,
				    &my_ldap.timeout, &result);
		if (rc != LDAP_SUCCESS)
		{
			free(filter);
			if (is_protocol_error(rc))
			{
				/* If there was a protocol error, close the connection */
				close_conn(conn);
				ldapconnfailure();
				return 1;
			}
			return -1;
		}

		/* If we are more than one result, reject */
		if (ldap_count_entries(conn, result) != 1)
		{
			free(filter);
			ldap_msgfree(result);
			return -1;
		}
	
		dn = ldap_get_dn(conn, result);
		ldap_msgfree(result);

		debug("Rebind DN:    %s\n", dn);
		if (dn == NULL) 
		{
			ldap_perror(conn, "ldap_get_dn");
			free(filter);
			return -1;
		}

		release_conn(conn, &rc);
		/* ignore the release conn error. */

		debug("Acquiring a new connection for %s", dn);
		conn = get_conn(dn, pass, &rc);
		if (rc != 0)
		{
			free(filter);
			return 1;
		}
	}
		

	rc = ldap_search_st(conn,
			    (char *)my_ldap.basedn, ldap_scope(my_ldap.scope),
			    filter, (char **)my_ldap.ldap_attributes, 0,
			    &my_ldap.timeout, &result);
	free(filter);

	if (rc != LDAP_SUCCESS)
	{
		if (is_protocol_error(rc))
		{
			/* If there was a protocol error, close the connection */
			close_conn(conn);
			ldapconnfailure();
			return 1;
		}
		return -1;
	}

	/* If we are more than one result, reject */
	if (ldap_count_entries(conn,result)!=1)
	{
		ldap_msgfree(result);
		return -1;
	}

	debug("Nombre de r�sulat:    %d\n",ldap_count_entries(conn,result));

	dn = ldap_get_dn(conn, result);

	debug("DN:    %s\n",dn);

	if (dn == NULL) 
	{
		ldap_perror(conn, "ldap_get_dn");
		ldap_msgfree(result);
		return -1;
	}

	rc = prepare_callback_auth(conn, result, &auth, user);
	if (rc)
	{
		free(dn);
		ldap_msgfree(result);
		return rc;
	}

	debug("Callback auth: %s, %p, au=%d, ag=%d, h=%s, a=%s, cn=%s, dir=%s, q=%s",
	      auth.sysusername, auth.sysuserid,
	      auth.sysuserid ? *(auth.sysuserid) : -1, auth.sysgroupid, 
	      auth.homedir, auth.address, auth.fullname, auth.maildir,
	      auth.quota, auth.passwd, auth.clearpasswd);

	if ((auth.sysuserid != NULL && *(auth.sysuserid) == 0) ||
	    auth.sysgroupid == 0)
	{
		auth_ldap_log("authlib: refuse to authenticate %s: uid=%d, gid=%d\n",
			      user, *(auth.sysuserid), auth.sysgroupid);
		rc = 1;
	}

	if (rc == 0 && pass)
	{
		if (my_ldap.authbind) 
		{
			if (newpass)
			{
				newpass_crypt = authcryptpasswd(newpass, NULL);
				if (newpass_crypt == 0)
				{
					rc= -1;
				}
			}
		}
		else
		{
			if (auth.clearpasswd)
			{
				if (strcmp(pass,auth.clearpasswd))
					rc= -1;
			}
			else
			{
				const char *p=auth.passwd;

				if (p && strncasecmp(p, "{crypt}", 7) == 0)
					p += 7; /* For authcheckpassword */

				if (!p || authcheckpassword(pass, p))
					rc= -1;
			}

			if (rc == 0 && newpass && auth.passwd)
			{
				if ((newpass_crypt=authcryptpasswd(newpass,
								   auth.passwd)
					) == 0)
					rc= -1;
			}
		}
        }

	if (rc == 0)
	{
		LDAPMessage * entry = ldap_first_entry(conn, result);
		rc = auth_ldap_check_group(conn, &my_ldap, entry, user);
	}

	debug("After auth_ldap_check_group rc=%d\n", rc);

	if (rc == 0 && newpass)
	{
		rc = auth_ldap_modify_passwd(conn, dn, newpass,
					     newpass_crypt);
	}

	if (newpass_crypt)
		free(newpass_crypt);

	free (dn);

	debug("before callback rc=%d\n", rc);

	if (rc == 0 && callback)
		rc= (*callback)(&auth, arg);

	cleanup_callback_auth(&auth);

	debug("after callback rc=%d\n",rc);

	ldap_msgfree(result);

	return (rc);
}

static int auth_ldap_modify_passwd(LDAP * conn, const char * dn,
				   const char * newpass,
				   const char * newpass_crypt)
{
	LDAPMod *mods[3];
	int mod_index=0;
	int rc = 0;

	LDAPMod mod_clear, mod_crypt;
	char *mod_clear_vals[2], *mod_crypt_vals[2];

	if (my_ldap.attributes[3])
	{
		mods[mod_index]= &mod_clear;
		mod_clear.mod_op=LDAP_MOD_REPLACE;
		mod_clear.mod_type=(char *)my_ldap.attributes[3];
		mod_clear.mod_values=mod_clear_vals;

		mod_clear_vals[0]=(char *)newpass;
		mod_clear_vals[1]=NULL;
		++mod_index;
	}

	if (my_ldap.attributes[4] && newpass_crypt)
	{
		mods[mod_index]= &mod_crypt;
		mod_crypt.mod_op=LDAP_MOD_REPLACE;
		mod_crypt.mod_type=(char *)my_ldap.attributes[4];
		mod_crypt.mod_values=mod_crypt_vals;

		mod_crypt_vals[0]= (char *)newpass_crypt;
		mod_crypt_vals[1]=NULL;
		++mod_index;
	}
	if (mod_index == 0)
		rc= -1;
	else
	{
		mods[mod_index]=0;

		if (ldap_modify_s(conn, dn, mods))
		{
			rc= -1;
		}
	}
	return rc;
}


/*
 * Allocates new memory, and the caller owns it, and must cleanup.
 */
char * auth_ldap_get_value(LDAP * conn, const char * valuespec,
			   LDAPMessage * entry, const char * user)
{
	char * value;
	if (*valuespec == ':')
	{
		valuespec++;
		if (!strcasecmp("dn", valuespec))
		{
			debug("Group membership based on %s (dn) for %s\n", valuespec, user);
			return ldap_get_dn(conn, entry);
		}
	
		value = copy_value(conn, entry, valuespec, user);
	
		debug("Group membership based on %s for %s\n", valuespec, user);
		return value;
	}
	else
	{
		return strdup(valuespec);
	}
}	
	
/*
 * Return -1 if the authenticated DN is not a member of the required
 * group.
 * Return 1 if an error prevented checking of this condition.
 * Return 0 if the DN passes the test, or if the configuration is not
 * sufficiently specified to perform the test.  (The test is optional.)
 */
static int auth_ldap_check_group(LDAP * conn,
				 struct ldap_info * info,
				 LDAPMessage *entry, const char * user)

{
	/* return no attributes, only the dn is needed. */
	const char *ldap_attributes[2] = { "1.1", NULL };

	LDAPMessage *result;
	char * filter;
	char * value;
	int count;
	int rc = 0;

	debug("Group membership will now be checked for %s\n", user);

	if (!info->groupvalue)
		return 0;

	debug("Group value specification %s\n", info->groupvalue);

	value = auth_ldap_get_value(conn, info->groupvalue, entry, user);

	if (!value)
	{
		/* authldap is not configured to check for group membership. */
		return 0;
	}

	debug("Group value %s\n", value);

	filter = auth_ldap_make_filter(info->groupextra,
				       info->groupmember, value);
	free(value);

	if (!filter)
	{
		perror("malloc");
		return 1;
	}

	rc = ldap_search_st(conn, (char *)info->groupbasedn,
			    ldap_scope(info->groupscope), filter,
			    (char **)ldap_attributes, 0, &info->timeout,
			    &result);
	free(filter);

	if (rc != LDAP_SUCCESS)
	{
		if (is_protocol_error(rc))
		{
			/* If there was a protocol error, close the connection */
			close_conn(conn);
			ldapconnfailure();
			return 1;
		}
		return -1;
	}

	/* If we are more than one result, reject */
	count = ldap_count_entries(conn, result);
	ldap_msgfree(result);

	debug("auth_ldap_check_group count=%d\n", count);

	if (count != 1)
	{
		return -1;
	}

	return 0;
}

Attachment: ""
Description: Binary data

Reply via email to