[Date Prev][Date Next] [Chronological] [Thread] [Top]

Two samba related openldap overlays



Two openldap overlays that may be of interest to others.

In the first, I've hacked up Howard Chu's smbk5pwd openldap overlay to
include the automatic addition of sambaNTPassword, sambaLMPassword, and
sambaPwdLastSet attributes to any sambaSamAccount entries that are being
ADDED to the directory.

WARNING: This overlay is ONLY useful with cleartext passwords.

In the second, I've modified my automatic uidnumber generator overlay to
also add sambaSID's to sambaSamAccount entries that are being added to
the directory. The algorithm used to generate the SID is the same as
employed by smbldap-tools (2*uidNumber+1000). Currently, the samba
domain SID is hardcoded to "S-1-0-0-". If you want to use this overlay,
change this variable accordingly or teach the overlay how to read from
config.

jr


/* smbk5pwd.c - Overlay for managing Samba and Heimdal passwords */
/* $OpenLDAP: pkg/ldap/contrib/slapd-modules/smbk5pwd/smbk5pwd.c,v 1.17.2.5 2007/10/09 00:18:47 quanah Exp $ */
/*
 * Copyright 2004-2005 by Howard Chu, Symas Corp.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in the file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * <http://www.OpenLDAP.org/license.html>.
 */
/*
 * Support for table-driven configuration added by Pierangelo Masarati.
 * Support for sambaPwdMustChange and sambaPwdCanChange added by Marco D'Ettorre.
 *
 * The conditions of the OpenLDAP Public License apply.
 */

#include <portable.h>

#ifndef SLAPD_OVER_SMBK5PWD
#define SLAPD_OVER_SMBK5PWD SLAPD_MOD_DYNAMIC
#endif

#ifdef SLAPD_OVER_SMBK5PWD

#include <slap.h>
#include <ac/errno.h>
#include <ac/string.h>

#include "config.h"

#ifdef DO_KRB5
#include <lber.h>
#include <lber_pvt.h>
#include <lutil.h>

/* make ASN1_MALLOC_ENCODE use our allocator */
#define malloc	ch_malloc

#include <krb5.h>
#include <kadm5/admin.h>
#include <hdb.h>

#ifndef HDB_INTERFACE_VERSION
#define	HDB_MASTER_KEY_SET	master_key_set
#else
#define	HDB_MASTER_KEY_SET	hdb_master_key_set
#endif

static krb5_context context;
static void *kadm_context;
static kadm5_config_params conf;
static HDB *db;

static AttributeDescription *ad_krb5Key;
static AttributeDescription *ad_krb5KeyVersionNumber;
static AttributeDescription *ad_krb5PrincipalName;
static ObjectClass *oc_krb5KDCEntry;
#endif

#ifdef DO_SAMBA
#include <openssl/des.h>
#include <openssl/md4.h>
#include "ldap_utf8.h"

static AttributeDescription *ad_sambaLMPassword;
static AttributeDescription *ad_sambaNTPassword;
static AttributeDescription *ad_sambaPwdLastSet;
static AttributeDescription *ad_sambaPwdMustChange;
static AttributeDescription *ad_sambaPwdCanChange;
static ObjectClass *oc_sambaSamAccount;
#endif

/* Per-instance configuration information */
typedef struct smbk5pwd_t {
	unsigned	mode;
#define	SMBK5PWD_F_KRB5		(0x1U)
#define	SMBK5PWD_F_SAMBA	(0x2U)

#define SMBK5PWD_DO_KRB5(pi)	((pi)->mode & SMBK5PWD_F_KRB5)
#define SMBK5PWD_DO_SAMBA(pi)	((pi)->mode & SMBK5PWD_F_SAMBA)

#ifdef DO_KRB5
	/* nothing yet */
#endif

#ifdef DO_SAMBA
	/* How many seconds before forcing a password change? */
	time_t	smb_must_change;
	/* How many seconds after allowing a password change? */
	time_t	smb_can_change;
#endif
} smbk5pwd_t;

static const unsigned SMBK5PWD_F_ALL	=
	0
#ifdef DO_KRB5
	| SMBK5PWD_F_KRB5
#endif
#ifdef DO_SAMBA
	| SMBK5PWD_F_SAMBA
#endif
	;

static int smbk5pwd_modules_init( smbk5pwd_t *pi );

#ifdef DO_SAMBA
static const char hex[] = "0123456789abcdef";

/* From liblutil/passwd.c... */
static void lmPasswd_to_key(
														const char *lmPasswd,
														des_cblock *key)
{
	const unsigned char *lpw = (const unsigned char *)lmPasswd;
	unsigned char *k = (unsigned char *)key;

	/* make room for parity bits */
	k[0] = lpw[0];
	k[1] = ((lpw[0]&0x01)<<7) | (lpw[1]>>1);
	k[2] = ((lpw[1]&0x03)<<6) | (lpw[2]>>2);
	k[3] = ((lpw[2]&0x07)<<5) | (lpw[3]>>3);
	k[4] = ((lpw[3]&0x0F)<<4) | (lpw[4]>>4);
	k[5] = ((lpw[4]&0x1F)<<3) | (lpw[5]>>5);
	k[6] = ((lpw[5]&0x3F)<<2) | (lpw[6]>>6);
	k[7] = ((lpw[6]&0x7F)<<1);

	des_set_odd_parity( key );
}

#define MAX_PWLEN 256
#define	HASHLEN	16

static void hexify(
									 const char in[HASHLEN],
									 struct berval *out
									 )
{
	int i;
	char *a;
	unsigned char *b;

	out->bv_val = ch_malloc(HASHLEN*2 + 1);
	out->bv_len = HASHLEN*2;

	a = out->bv_val;
	b = (unsigned char *)in;
	for (i=0; i<HASHLEN; i++) {
		*a++ = hex[*b >> 4];
		*a++ = hex[*b++ & 0x0f];
	}
	*a++ = '\0';
}

static void lmhash(
									 struct berval *passwd,
									 struct berval *hash
									 )
{
	char UcasePassword[15];
	des_cblock key;
	des_key_schedule schedule;
	des_cblock StdText = "KGS!@#$%";
	des_cblock hbuf[2];

	strncpy( UcasePassword, passwd->bv_val, 14 );
	UcasePassword[14] = '\0';
	ldap_pvt_str2upper( UcasePassword );

	lmPasswd_to_key( UcasePassword, &key );
	des_set_key_unchecked( &key, schedule );
	des_ecb_encrypt( &StdText, &hbuf[0], schedule , DES_ENCRYPT );

	lmPasswd_to_key( &UcasePassword[7], &key );
	des_set_key_unchecked( &key, schedule );
	des_ecb_encrypt( &StdText, &hbuf[1], schedule , DES_ENCRYPT );

	hexify( (char *)hbuf, hash );
}

static void nthash(
									 struct berval *passwd,
									 struct berval *hash
									 )
{
	/* Windows currently only allows 14 character passwords, but
	 * may support up to 256 in the future. We assume this means
	 * 256 UCS2 characters, not 256 bytes...
	 */
	char hbuf[HASHLEN];
	MD4_CTX ctx;

	if (passwd->bv_len > MAX_PWLEN*2)
		passwd->bv_len = MAX_PWLEN*2;
		
	MD4_Init( &ctx );
	MD4_Update( &ctx, passwd->bv_val, passwd->bv_len );
	MD4_Final( (unsigned char *)hbuf, &ctx );

	hexify( hbuf, hash );
}
#endif /* DO_SAMBA */

#ifdef DO_KRB5

static int smbk5pwd_op_cleanup(
															 Operation *op,
															 SlapReply *rs )
{
	slap_callback *cb;

	/* clear out the current key */
	ldap_pvt_thread_pool_setkey( op->o_threadctx, smbk5pwd_op_cleanup,
															 NULL, NULL );

	/* free the callback */
	cb = op->o_callback;
	op->o_callback = cb->sc_next;
	op->o_tmpfree( cb, op->o_tmpmemctx );
	return 0;
}

static int smbk5pwd_op_bind(
														Operation *op,
														SlapReply *rs )
{
	/* If this is a simple Bind, stash the Op pointer so our chk
	 * function can find it. Set a cleanup callback to clear it
	 * out when the Bind completes.
	 */
	if ( op->oq_bind.rb_method == LDAP_AUTH_SIMPLE ) {
		slap_callback *cb;
		ldap_pvt_thread_pool_setkey( op->o_threadctx, smbk5pwd_op_cleanup, op,
																 NULL );
		cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
		cb->sc_cleanup = smbk5pwd_op_cleanup;
		cb->sc_next = op->o_callback;
		op->o_callback = cb;
	}
	return SLAP_CB_CONTINUE;
}

static LUTIL_PASSWD_CHK_FUNC k5key_chk;
static LUTIL_PASSWD_HASH_FUNC k5key_hash;
static const struct berval k5key_scheme = BER_BVC("{K5KEY}");

/* This password scheme stores no data in the userPassword attribute
 * other than the scheme name. It assumes the invoking entry is a
 * krb5KDCentry and compares the passed-in credentials against the
 * krb5Key attribute. The krb5Key may be multi-valued, but they are
 * simply multiple keytypes generated from the same input string, so
 * only the first value needs to be compared here.
 *
 * Since the lutil_passwd API doesn't pass the Entry object in, we
 * have to fetch it ourselves in order to get access to the other
 * attributes. We accomplish this with the help of the overlay's Bind
 * function, which stores the current Operation pointer in thread-specific
 * storage so we can retrieve it here. The Operation provides all
 * the necessary context for us to get Entry from the database.
 */
static int k5key_chk(
										 const struct berval *sc,
										 const struct berval *passwd,
										 const struct berval *cred,
										 const char **text )
{
	void *ctx;
	Operation *op;
	int rc;
	Entry *e;
	Attribute *a;
	krb5_error_code ret;
	krb5_keyblock key;
	krb5_salt salt;
	hdb_entry ent;

	/* Find our thread context, find our Operation */
	ctx = ldap_pvt_thread_pool_context();

	if ( ldap_pvt_thread_pool_getkey( ctx, smbk5pwd_op_cleanup, (void **)&op, NULL ) ||
			 !op )
		return LUTIL_PASSWD_ERR;

	rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
	if ( rc != LDAP_SUCCESS ) return LUTIL_PASSWD_ERR;

	rc = LUTIL_PASSWD_ERR;
	do {
		size_t l;
		Key ekey = {0};

		a = attr_find( e->e_attrs, ad_krb5PrincipalName );
		if (!a ) break;

		memset( &ent, 0, sizeof(ent) );
		ret = krb5_parse_name(context, a->a_vals[0].bv_val, &ent.principal);
		if ( ret ) break;
		krb5_get_pw_salt( context, ent.principal, &salt );
		krb5_free_principal( context, ent.principal );

		a = attr_find( e->e_attrs, ad_krb5Key );
		if ( !a ) break;

		ent.keys.len = 1;
		ent.keys.val = &ekey;
		decode_Key((unsigned char *) a->a_vals[0].bv_val,
							 (size_t) a->a_vals[0].bv_len, &ent.keys.val[0], &l);
		if ( db->HDB_MASTER_KEY_SET )
			hdb_unseal_keys( context, db, &ent );

		krb5_string_to_key_salt( context, ekey.key.keytype, cred->bv_val,
														 salt, &key );

		krb5_free_salt( context, salt );

		if ( memcmp( ekey.key.keyvalue.data, key.keyvalue.data,
								 key.keyvalue.length ) == 0 ) rc = LUTIL_PASSWD_OK;

		krb5_free_keyblock_contents( context, &key );
		krb5_free_keyblock_contents( context, &ekey.key );

	} while(0);
	be_entry_release_r( op, e );
	return rc;
}

static int k5key_hash(
											const struct berval *scheme,
											const struct berval *passwd,
											struct berval *hash,
											const char **text )
{
	ber_dupbv( hash, (struct berval *)&k5key_scheme );
	return LUTIL_PASSWD_OK;
}
#endif /* DO_KRB5 */

static int smbk5pwd_exop_passwd(
																Operation *op,
																SlapReply *rs )
{
	int rc;
	req_pwdexop_s *qpw = &op->oq_pwdexop;
	Entry *e;
	Modifications *ml;
	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
	smbk5pwd_t *pi = on->on_bi.bi_private;

	/* Not the operation we expected, pass it on... */
	if ( ber_bvcmp( &slap_EXOP_MODIFY_PASSWD, &op->ore_reqoid ) ) {
		return SLAP_CB_CONTINUE;
	}

	op->o_bd->bd_info = (BackendInfo *)on->on_info;
	rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
	if ( rc != LDAP_SUCCESS ) return rc;

#ifdef DO_KRB5
	/* Kerberos stuff */
	do {
		krb5_error_code ret;
		hdb_entry ent;
		struct berval *keys;
		int kvno, i;
		Attribute *a;

		if ( !SMBK5PWD_DO_KRB5( pi ) ) break;

		if ( !is_entry_objectclass(e, oc_krb5KDCEntry, 0 ) ) break;

		a = attr_find( e->e_attrs, ad_krb5PrincipalName );
		if ( !a ) break;

		memset( &ent, 0, sizeof(ent) );
		ret = krb5_parse_name(context, a->a_vals[0].bv_val, &ent.principal);
		if ( ret ) break;

		a = attr_find( e->e_attrs, ad_krb5KeyVersionNumber );
		kvno = 0;
		if ( a ) {
			if ( lutil_atoi( &kvno, a->a_vals[0].bv_val ) != 0 ) {
				Debug( LDAP_DEBUG_ANY, "%s smbk5pwd EXOP: "
							 "dn=\"%s\" unable to parse krb5KeyVersionNumber=\"%s\"\n",
							 op->o_log_prefix, e->e_name.bv_val, a->a_vals[0].bv_val );
			}

		} else {
			/* shouldn't happen, this is a required attr */
			Debug( LDAP_DEBUG_ANY, "%s smbk5pwd EXOP: "
						 "dn=\"%s\" missing krb5KeyVersionNumber\n",
						 op->o_log_prefix, e->e_name.bv_val, 0 );
		}

		ret = _kadm5_set_keys(kadm_context, &ent, qpw->rs_new.bv_val);
		hdb_seal_keys(context, db, &ent);
		krb5_free_principal( context, ent.principal );

		keys = ch_malloc( (ent.keys.len + 1) * sizeof(struct berval));

		for (i = 0; i < ent.keys.len; i++) {
			unsigned char *buf;
			size_t len;

			ASN1_MALLOC_ENCODE(Key, buf, len, &ent.keys.val[i], &len, ret);
			if (ret != 0)
				break;
			
			keys[i].bv_val = (char *)buf;
			keys[i].bv_len = len;
		}
		BER_BVZERO( &keys[i] );

		_kadm5_free_keys(kadm_context, ent.keys.len, ent.keys.val);

		if ( i != ent.keys.len ) {
			ber_bvarray_free( keys );
			break;
		}

		ml = ch_malloc(sizeof(Modifications));
		if (!qpw->rs_modtail) qpw->rs_modtail = &ml->sml_next;
		ml->sml_next = qpw->rs_mods;
		qpw->rs_mods = ml;

		ml->sml_desc = ad_krb5Key;
		ml->sml_op = LDAP_MOD_REPLACE;
#ifdef SLAP_MOD_INTERNAL
		ml->sml_flags = SLAP_MOD_INTERNAL;
#endif
		ml->sml_numvals = i;
		ml->sml_values = keys;
		ml->sml_nvalues = NULL;
		
		ml = ch_malloc(sizeof(Modifications));
		ml->sml_next = qpw->rs_mods;
		qpw->rs_mods = ml;
		
		ml->sml_desc = ad_krb5KeyVersionNumber;
		ml->sml_op = LDAP_MOD_REPLACE;
#ifdef SLAP_MOD_INTERNAL
		ml->sml_flags = SLAP_MOD_INTERNAL;
#endif
		ml->sml_numvals = 1;
		ml->sml_values = ch_malloc( 2 * sizeof(struct berval));
		ml->sml_values[0].bv_val = ch_malloc( 64 );
		ml->sml_values[0].bv_len = sprintf(ml->sml_values[0].bv_val,
																			 "%d", kvno+1 );
		BER_BVZERO( &ml->sml_values[1] );
		ml->sml_nvalues = NULL;
	} while ( 0 );
#endif /* DO_KRB5 */

#ifdef DO_SAMBA
	/* Samba stuff */
	if ( SMBK5PWD_DO_SAMBA( pi ) && is_entry_objectclass(e, oc_sambaSamAccount, 0 ) ) {
		struct berval *keys;
		ber_len_t j,l;
		wchar_t *wcs, wc;
		char *c, *d;
		struct berval pwd;
		
		/* Expand incoming UTF8 string to UCS4 */
		l = ldap_utf8_chars(qpw->rs_new.bv_val);
		wcs = ch_malloc((l+1) * sizeof(wchar_t));

		ldap_x_utf8s_to_wcs( wcs, qpw->rs_new.bv_val, l );
		
		/* Truncate UCS4 to UCS2 */
		c = (char *)wcs;
		for (j=0; j<l; j++) {
			wc = wcs[j];
			*c++ = wc & 0xff;
			*c++ = (wc >> 8) & 0xff;
		}
		*c++ = 0;
		pwd.bv_val = (char *)wcs;
		pwd.bv_len = l * 2;

		ml = ch_malloc(sizeof(Modifications));
		if (!qpw->rs_modtail) qpw->rs_modtail = &ml->sml_next;
		ml->sml_next = qpw->rs_mods;
		qpw->rs_mods = ml;

		keys = ch_malloc( 2 * sizeof(struct berval) );
		BER_BVZERO( &keys[1] );
		nthash( &pwd, keys );
		
		ml->sml_desc = ad_sambaNTPassword;
		ml->sml_op = LDAP_MOD_REPLACE;
#ifdef SLAP_MOD_INTERNAL
		ml->sml_flags = SLAP_MOD_INTERNAL;
#endif
		ml->sml_numvals = 1;
		ml->sml_values = keys;
		ml->sml_nvalues = NULL;

		/* Truncate UCS2 to 8-bit ASCII */
		c = pwd.bv_val+1;
		d = pwd.bv_val+2;
		for (j=1; j<l; j++) {
			*c++ = *d++;
			d++;
		}
		pwd.bv_len /= 2;
		pwd.bv_val[pwd.bv_len] = '\0';

		ml = ch_malloc(sizeof(Modifications));
		ml->sml_next = qpw->rs_mods;
		qpw->rs_mods = ml;

		keys = ch_malloc( 2 * sizeof(struct berval) );
		BER_BVZERO( &keys[1] );
		lmhash( &pwd, keys );
		
		ml->sml_desc = ad_sambaLMPassword;
		ml->sml_op = LDAP_MOD_REPLACE;
#ifdef SLAP_MOD_INTERNAL
		ml->sml_flags = SLAP_MOD_INTERNAL;
#endif
		ml->sml_numvals = 1;
		ml->sml_values = keys;
		ml->sml_nvalues = NULL;

		ch_free(wcs);

		ml = ch_malloc(sizeof(Modifications));
		ml->sml_next = qpw->rs_mods;
		qpw->rs_mods = ml;

		keys = ch_malloc( 2 * sizeof(struct berval) );
		keys[0].bv_val = ch_malloc( STRLENOF( "9223372036854775807L" ) + 1 );
		keys[0].bv_len = snprintf(keys[0].bv_val,
															STRLENOF( "9223372036854775807L" ) + 1,
															"%ld", slap_get_time());
		BER_BVZERO( &keys[1] );
		
		ml->sml_desc = ad_sambaPwdLastSet;
		ml->sml_op = LDAP_MOD_REPLACE;
#ifdef SLAP_MOD_INTERNAL
		ml->sml_flags = SLAP_MOD_INTERNAL;
#endif
		ml->sml_numvals = 1;
		ml->sml_values = keys;
		ml->sml_nvalues = NULL;

		if (pi->smb_must_change)
			{
				ml = ch_malloc(sizeof(Modifications));
				ml->sml_next = qpw->rs_mods;
				qpw->rs_mods = ml;

				keys = ch_malloc( 2 * sizeof(struct berval) );
				keys[0].bv_val = ch_malloc( STRLENOF( "9223372036854775807L" ) + 1 );
				keys[0].bv_len = snprintf(keys[0].bv_val,
																	STRLENOF( "9223372036854775807L" ) + 1,
																	"%ld", slap_get_time() + pi->smb_must_change);
				BER_BVZERO( &keys[1] );

				ml->sml_desc = ad_sambaPwdMustChange;
				ml->sml_op = LDAP_MOD_REPLACE;
#ifdef SLAP_MOD_INTERNAL
				ml->sml_flags = SLAP_MOD_INTERNAL;
#endif
				ml->sml_numvals = 1;
				ml->sml_values = keys;
				ml->sml_nvalues = NULL;
			}

		if (pi->smb_can_change)
			{
				ml = ch_malloc(sizeof(Modifications));
				ml->sml_next = qpw->rs_mods;
				qpw->rs_mods = ml;

				keys = ch_malloc( 2 * sizeof(struct berval) );
				keys[0].bv_val = ch_malloc( STRLENOF( "9223372036854775807L" ) + 1 );
				keys[0].bv_len = snprintf(keys[0].bv_val,
																	STRLENOF( "9223372036854775807L" ) + 1,
																	"%ld", slap_get_time() + pi->smb_can_change);
				BER_BVZERO( &keys[1] );

				ml->sml_desc = ad_sambaPwdCanChange;
				ml->sml_op = LDAP_MOD_REPLACE;
#ifdef SLAP_MOD_INTERNAL
				ml->sml_flags = SLAP_MOD_INTERNAL;
#endif
				ml->sml_numvals = 1;
				ml->sml_values = keys;
				ml->sml_nvalues = NULL;
			}
	}
#endif /* DO_SAMBA */
	be_entry_release_r( op, e );

	return SLAP_CB_CONTINUE;
}

static slap_overinst smbk5pwd;

/* back-config stuff */
enum {
	PC_SMB_MUST_CHANGE = 1,
	PC_SMB_CAN_CHANGE,
	PC_SMB_ENABLE
};

static ConfigDriver smbk5pwd_cf_func;

/*
 * NOTE: uses OID arcs OLcfgCtAt:1 and OLcfgCtOc:1
 */

static ConfigTable smbk5pwd_cfats[] = {
	{ "smbk5pwd-enable", "arg",
		2, 0, 0, ARG_MAGIC|PC_SMB_ENABLE, smbk5pwd_cf_func,
		"( OLcfgCtAt:1.1 NAME 'olcSmbK5PwdEnable' "
		"DESC 'Modules to be enabled' "
		"SYNTAX OMsDirectoryString )", NULL, NULL },
	{ "smbk5pwd-must-change", "time",
		2, 2, 0, ARG_MAGIC|ARG_INT|PC_SMB_MUST_CHANGE, smbk5pwd_cf_func,
		"( OLcfgCtAt:1.2 NAME 'olcSmbK5PwdMustChange' "
		"DESC 'Credentials validity interval' "
		"SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
	{ "smbk5pwd-can-change", "time",
		2, 2, 0, ARG_MAGIC|ARG_INT|PC_SMB_CAN_CHANGE, smbk5pwd_cf_func,
		"( OLcfgCtAt:1.3 NAME 'olcSmbK5PwdCanChange' "
		"DESC 'Credentials minimum validity interval' "
		"SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },

	{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
};

static ConfigOCs smbk5pwd_cfocs[] = {
	{ "( OLcfgCtOc:1.1 "
		"NAME 'olcSmbK5PwdConfig' "
		"DESC 'smbk5pwd overlay configuration' "
		"SUP olcOverlayConfig "
		"MAY ( "
		"olcSmbK5PwdEnable "
		"$ olcSmbK5PwdMustChange "
		"$ olcSmbK5PwdCanChange "
		") )", Cft_Overlay, smbk5pwd_cfats },

	{ NULL, 0, NULL }
};

/*
 * add here other functionalities; handle their initialization
 * as appropriate in smbk5pwd_modules_init().
 */
static slap_verbmasks smbk5pwd_modules[] = {
	{ BER_BVC( "krb5" ),		SMBK5PWD_F_KRB5	},
	{ BER_BVC( "samba" ),		SMBK5PWD_F_SAMBA },
	{ BER_BVNULL,			-1 }
};

static int
smbk5pwd_cf_func( ConfigArgs *c )
{
	slap_overinst	*on = (slap_overinst *)c->bi;

	int		rc = 0;
	smbk5pwd_t	*pi = on->on_bi.bi_private;

	if ( c->op == SLAP_CONFIG_EMIT ) {
		switch( c->type ) {
		case PC_SMB_MUST_CHANGE:
#ifdef DO_SAMBA
			c->value_int = pi->smb_must_change;
#else /* ! DO_SAMBA */
			c->value_int = 0;
#endif /* ! DO_SAMBA */
			break;

		case PC_SMB_CAN_CHANGE:
#ifdef DO_SAMBA
			c->value_int = pi->smb_can_change;
#else /* ! DO_SAMBA */
			c->value_int = 0;
#endif /* ! DO_SAMBA */
			break;

		case PC_SMB_ENABLE:
			c->rvalue_vals = NULL;
			if ( pi->mode ) {
				mask_to_verbs( smbk5pwd_modules, pi->mode, &c->rvalue_vals );
				if ( c->rvalue_vals == NULL ) {
					rc = 1;
				}
			}
			break;

		default:
			assert( 0 );
			rc = 1;
		}
		return rc;

	} else if ( c->op == LDAP_MOD_DELETE ) {
		switch( c->type ) {
		case PC_SMB_MUST_CHANGE:
			break;

		case PC_SMB_CAN_CHANGE:
			break;

		case PC_SMB_ENABLE:
			if ( !c->line ) {
				pi->mode = 0;

			} else {
				slap_mask_t	m;

				m = verb_to_mask( c->line, smbk5pwd_modules );
				pi->mode &= ~m;
			}
			break;

		default:
			assert( 0 );
			rc = 1;
		}
		return rc;
	}

	switch( c->type ) {
	case PC_SMB_MUST_CHANGE:
#ifdef DO_SAMBA
		if ( c->value_int < 0 ) {
			Debug( LDAP_DEBUG_ANY, "%s: smbk5pwd: "
						 "<%s> invalid negative value \"%d\".",
						 c->log, c->argv[ 0 ], 0 );
			return 1;
		}
		pi->smb_must_change = c->value_int;
#else /* ! DO_SAMBA */
		Debug( LDAP_DEBUG_ANY, "%s: smbk5pwd: "
					 "<%s> only meaningful "
					 "when compiled with -DDO_SAMBA.\n",
					 c->log, c->argv[ 0 ], 0 );
		return 1;
#endif /* ! DO_SAMBA */
		break;

	case PC_SMB_CAN_CHANGE:
#ifdef DO_SAMBA
		if ( c->value_int < 0 ) {
			Debug( LDAP_DEBUG_ANY, "%s: smbk5pwd: "
						 "<%s> invalid negative value \"%d\".",
						 c->log, c->argv[ 0 ], 0 );
			return 1;
		}
		pi->smb_can_change = c->value_int;
#else /* ! DO_SAMBA */
		Debug( LDAP_DEBUG_ANY, "%s: smbk5pwd: "
					 "<%s> only meaningful "
					 "when compiled with -DDO_SAMBA.\n",
					 c->log, c->argv[ 0 ], 0 );
		return 1;
#endif /* ! DO_SAMBA */
		break;

	case PC_SMB_ENABLE: {
		slap_mask_t	mode = pi->mode, m;

		rc = verbs_to_mask( c->argc, c->argv, smbk5pwd_modules, &m );
		if ( rc > 0 ) {
			Debug( LDAP_DEBUG_ANY, "%s: smbk5pwd: "
						 "<%s> unknown module \"%s\".\n",
						 c->log, c->argv[ 0 ], c->argv[ rc ] );
			return 1;
		}

		/* we can hijack the smbk5pwd_t structure because
		 * from within the configuration, this is the only
		 * active thread. */
		pi->mode |= m;

#ifndef DO_KRB5
		if ( SMBK5PWD_DO_KRB5( pi ) ) {
			Debug( LDAP_DEBUG_ANY, "%s: smbk5pwd: "
						 "<%s> module \"%s\" only allowed when compiled with -DDO_KRB5.\n",
						 c->log, c->argv[ 0 ], c->argv[ rc ] );
			pi->mode = mode;
			return 1;
		}
#endif /* ! DO_KRB5 */

#ifndef DO_SAMBA
		if ( SMBK5PWD_DO_SAMBA( pi ) ) {
			Debug( LDAP_DEBUG_ANY, "%s: smbk5pwd: "
						 "<%s> module \"%s\" only allowed when compiled with -DDO_SAMBA.\n",
						 c->log, c->argv[ 0 ], c->argv[ rc ] );
			pi->mode = mode;
			return 1;
		}
#endif /* ! DO_SAMBA */

		{
			BackendDB	db = *c->be;

			/* Re-initialize the module, because
			 * the configuration might have changed */
			db.bd_info = (BackendInfo *)on;
			rc = smbk5pwd_modules_init( pi );
			if ( rc ) {
				pi->mode = mode;
				return 1;
			}
		}

	} break;

	default:
		assert( 0 );
		return 1;
	}
	return rc;
}

static int
smbk5pwd_modules_init( smbk5pwd_t *pi )
{
	static struct {
		const char		*name;
		AttributeDescription	**adp;
	}
#ifdef DO_KRB5
	krb5_ad[] = {
		{ "krb5Key",			&ad_krb5Key },
		{ "krb5KeyVersionNumber",	&ad_krb5KeyVersionNumber },
		{ "krb5PrincipalName",		&ad_krb5PrincipalName },
		{ NULL }
	},
#endif /* DO_KRB5 */
#ifdef DO_SAMBA
		samba_ad[] = {
			{ "sambaLMPassword",		&ad_sambaLMPassword },
			{ "sambaNTPassword",		&ad_sambaNTPassword },
			{ "sambaPwdLastSet",		&ad_sambaPwdLastSet },
			{ "sambaPwdMustChange",		&ad_sambaPwdMustChange },
			{ "sambaPwdCanChange",					&ad_sambaPwdCanChange },
			{ NULL }
		},
#endif /* DO_SAMBA */
			dummy_ad;

			/* this is to silence the unused var warning */
			dummy_ad.name = NULL;

#ifdef DO_KRB5
			if ( SMBK5PWD_DO_KRB5( pi ) && oc_krb5KDCEntry == NULL ) {
				krb5_error_code	ret;
				extern HDB	*_kadm5_s_get_db(void *);

				int		i, rc;

				/* Make sure all of our necessary schema items are loaded */
				oc_krb5KDCEntry = oc_find( "krb5KDCEntry" );
				if ( !oc_krb5KDCEntry ) {
					Debug( LDAP_DEBUG_ANY, "smbk5pwd: "
								 "unable to find \"krb5KDCEntry\" objectClass.\n",
								 0, 0, 0 );
					return -1;
				}

				for ( i = 0; krb5_ad[ i ].name != NULL; i++ ) {
					const char	*text;

					*(krb5_ad[ i ].adp) = NULL;

					rc = slap_str2ad( krb5_ad[ i ].name, krb5_ad[ i ].adp, &text );
					if ( rc != LDAP_SUCCESS ) {
						Debug( LDAP_DEBUG_ANY, "smbk5pwd: "
									 "unable to find \"%s\" attributeType: %s (%d).\n",
									 krb5_ad[ i ].name, text, rc );
						oc_krb5KDCEntry = NULL;
						return rc;
					}
				}

				/* Initialize Kerberos context */
				ret = krb5_init_context(&context);
				if (ret) {
					Debug( LDAP_DEBUG_ANY, "smbk5pwd: "
								 "unable to initialize krb5 context (%d).\n",
								 ret, 0, 0 );
					oc_krb5KDCEntry = NULL;
					return -1;
				}

				ret = kadm5_s_init_with_password_ctx( context,
																							KADM5_ADMIN_SERVICE,
																							NULL,
																							KADM5_ADMIN_SERVICE,
																							&conf, 0, 0, &kadm_context );
				if (ret) {
					char *err_str, *err_msg = "<unknown error>";
					err_str = krb5_get_error_string( context );
					if (!err_str)
						err_msg = krb5_get_err_text( context, ret );
					Debug( LDAP_DEBUG_ANY, "smbk5pwd: "
								 "unable to initialize krb5 admin context: %s (%d).\n",
								 err_str ? err_str : err_msg, ret, 0 );
					if (err_str)
						krb5_free_error_string( context, err_str );
					krb5_free_context( context );
					oc_krb5KDCEntry = NULL;
					return -1;
				}

				db = _kadm5_s_get_db( kadm_context );
			}
#endif /* DO_KRB5 */

#ifdef DO_SAMBA
			if ( SMBK5PWD_DO_SAMBA( pi ) && oc_sambaSamAccount == NULL ) {
				int		i, rc;

				oc_sambaSamAccount = oc_find( "sambaSamAccount" );
				if ( !oc_sambaSamAccount ) {
					Debug( LDAP_DEBUG_ANY, "smbk5pwd: "
								 "unable to find \"sambaSamAccount\" objectClass.\n",
								 0, 0, 0 );
					return -1;
				}

				for ( i = 0; samba_ad[ i ].name != NULL; i++ ) {
					const char	*text;

					*(samba_ad[ i ].adp) = NULL;

					rc = slap_str2ad( samba_ad[ i ].name, samba_ad[ i ].adp, &text );
					if ( rc != LDAP_SUCCESS ) {
						Debug( LDAP_DEBUG_ANY, "smbk5pwd: "
									 "unable to find \"%s\" attributeType: %s (%d).\n",
									 samba_ad[ i ].name, text, rc );
						oc_sambaSamAccount = NULL;
						return rc;
					}
				}
			}
#endif /* DO_SAMBA */

			return 0;
}

static int
smbk5pwd_db_init(BackendDB *be, ConfigReply *cr)
{
	slap_overinst	*on = (slap_overinst *)be->bd_info;
	smbk5pwd_t	*pi;

	pi = ch_calloc( 1, sizeof( smbk5pwd_t ) );
	if ( pi == NULL ) {
		return 1;
	}
	on->on_bi.bi_private = (void *)pi;

	return 0;
}

static int
smbk5pwd_db_open(BackendDB *be, ConfigReply *cr)
{
	slap_overinst	*on = (slap_overinst *)be->bd_info;
	smbk5pwd_t	*pi = (smbk5pwd_t *)on->on_bi.bi_private;

	int	rc;

	if ( pi->mode == 0 ) {
		pi->mode = SMBK5PWD_F_ALL;
	}

	rc = smbk5pwd_modules_init( pi );
	if ( rc ) {
		return rc;
	}

	return 0;
}

static int
smbk5pwd_db_destroy(BackendDB *be, ConfigReply *cr)
{
	slap_overinst	*on = (slap_overinst *)be->bd_info;
	smbk5pwd_t	*pi = (smbk5pwd_t *)on->on_bi.bi_private;

	if ( pi ) {
		ch_free( pi );
	}

	return 0;
}

static int smbk5pwd_add( Operation *op, SlapReply *rs )
{
	Entry* to_add = NULL;
	AttributeDescription* ad = NULL;
	Attribute* attr = NULL;
	struct berval cleartext = BER_BVNULL;
	struct berval *keys;
	ber_len_t j,l;
	wchar_t *wcs, wc;
	char *c, *d;
	struct berval pwd;
  char lastset[32];
	struct berval lastsetbv;
	int rc;

	to_add = op->oq_add.rs_e;

	/* if the user doesn't have access, fall through to the normal ADD */
	if(!access_allowed( op, to_add, slap_schema.si_ad_entry,
											NULL, ACL_WRITE, NULL )) {
		return SLAP_CB_CONTINUE;
	}
	
	if (!is_entry_objectclass( (to_add), oc_sambaSamAccount, 0) )
		return SLAP_CB_CONTINUE;

	for ( attr = to_add->e_attrs; attr; attr = attr->a_next )
		{
			if (!strcmp( attr->a_desc->ad_cname.bv_val, "userPassword" )) {
				cleartext = attr->a_vals[0];
				Debug(LDAP_DEBUG_ANY, "%s: entry password is %s\n",
							smbk5pwd.on_bi.bi_type, attr->a_vals[0].bv_val, 0);
				break;
			}
		}

	/* Expand incoming UTF8 string to UCS4 */
	l = ldap_utf8_chars(cleartext.bv_val);
	wcs = ch_malloc((l+1) * sizeof(wchar_t));

	ldap_x_utf8s_to_wcs( wcs, cleartext.bv_val, l );
		
	/* Truncate UCS4 to UCS2 */
	c = (char *)wcs;
	for (j=0; j<l; j++) {
		wc = wcs[j];
		*c++ = wc & 0xff;
		*c++ = (wc >> 8) & 0xff;
	}
	*c++ = 0;
	pwd.bv_val = (char *)wcs;
	pwd.bv_len = l * 2;

	keys = ch_malloc( 2 * sizeof(struct berval) );
	BER_BVZERO( &keys[1] );
	nthash( &pwd, keys );
	attr_merge( to_add, ad_sambaNTPassword, keys, NULL );
  ch_free(keys);

	/* Truncate UCS2 to 8-bit ASCII */
	c = pwd.bv_val+1;
	d = pwd.bv_val+2;
	for (j=1; j<l; j++) {
		*c++ = *d++;
		d++;
	}
	pwd.bv_len /= 2;
	pwd.bv_val[pwd.bv_len] = '\0';

	keys = ch_malloc( 2 * sizeof(struct berval) );
	BER_BVZERO( &keys[1] );
	lmhash( &pwd, keys );
	attr_merge( to_add, ad_sambaLMPassword, keys, NULL );
  ch_free(keys);

  sprintf( lastset, "%ld", slap_get_time());
	ber_str2bv( lastset, 0, 0, &lastsetbv );
	attr_merge_one( to_add, ad_sambaPwdLastSet, &lastsetbv, NULL );

	ch_free(wcs);
	return SLAP_CB_CONTINUE;
}

int
smbk5pwd_initialize(void)
{
	int		rc;

	smbk5pwd.on_bi.bi_type = "smbk5pwd";

	smbk5pwd.on_bi.bi_op_add = smbk5pwd_add;
	smbk5pwd.on_bi.bi_db_init = smbk5pwd_db_init;
	smbk5pwd.on_bi.bi_db_open = smbk5pwd_db_open;
	smbk5pwd.on_bi.bi_db_destroy = smbk5pwd_db_destroy;

	smbk5pwd.on_bi.bi_extended = smbk5pwd_exop_passwd;
		
#ifdef DO_KRB5
	smbk5pwd.on_bi.bi_op_bind = smbk5pwd_op_bind;

	lutil_passwd_add( (struct berval *)&k5key_scheme, k5key_chk, k5key_hash );
#endif

	smbk5pwd.on_bi.bi_cf_ocs = smbk5pwd_cfocs;

	rc = config_register_schema( smbk5pwd_cfats, smbk5pwd_cfocs );
	if ( rc ) {
		return rc;
	}

	return overlay_register( &smbk5pwd );
}

#if SLAPD_OVER_SMBK5PWD == SLAPD_MOD_DYNAMIC
int init_module(int argc, char *argv[]) {
	return smbk5pwd_initialize();
}
#endif

#endif /* defined(SLAPD_OVER_SMBK5PWD) */


/**
 * uidnumber.c 
 *
 * Copyright (C) 2008 Joel W. Reed
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * http://www.OpenLDAP.org/license.html.
 *
 * SEE LICENSE FOR MORE INFORMATION
 *
 * Author:	Joel W. Reed
 * Email:		joelwreed@gmail.com
 * Version: 0.3
 * Updated: 09.9.2008
 * 
 * uidnumber
 *
 * This is an OpenLDAP overlay that intercepts ADD requests.
 * 
 * When the posixAccount schema class is loaded and posixAccount entries
 * are added that do not have a uidNumber, the overlay adds a uidNumber 
 * attribute with the next available uidNumber.
 *
 * In addition, when the sambaSamAccount schema class is loaded and sambaSamAccount 
 * entries are added that do not have a sambaSID, the overlay adds a sambaSID 
 * attribute with the a value equal to 2*uidNumber+1000, the same algorithm
 * used by smbldap-tools.
 *
 * NOTE: the samba domain SID prefix (samba_domain_sid) is hardcoded to "S-1-0-0-"
 */

#include <stdlib.h>
#include "portable.h" 
#include "slap.h"
#include "config.h"

static int uidnumber_search_cb( Operation *op, SlapReply *rs );
static unsigned long uidnumber_next_available( Operation *op );

static slap_overinst uidnumber;
static ObjectClass *oc_posix_account;
static ObjectClass *oc_samba_account;
static char *uid_attr_name = "uidNumber";
static char *sid_attr_name = "sambaSID";
static char *samba_domain_sid = "S-1-0-0-";

typedef struct uidnumber_data {
	ldap_pvt_thread_mutex_t mutex;
  unsigned long max_uid_number;
} uidnumber_data;

void addUidNumber(Operation *op, Entry* to_add)
{
	AttributeDescription* ad = NULL;
	Attribute* attr = NULL;
	char uidstr[64];
	struct berval uidbv = BER_BVNULL;
  unsigned long uid;
  int rc;

	/* if already has a uidNumber, no further processing required */
	for ( attr = to_add->e_attrs; attr; attr = attr->a_next )
		{
			if (!strcmp( attr->a_desc->ad_cname.bv_val, uid_attr_name )) {
				Debug(LDAP_DEBUG_TRACE, "%s: entry %s already has a uidNumber\n",
							uidnumber.on_bi.bi_type, to_add->e_nname.bv_val, 0);
				return;
			}
		}

	uid = uidnumber_next_available(op);

	rc = slap_str2ad( uid_attr_name, &ad, &text );
	if(rc != LDAP_SUCCESS) {
		Debug( LDAP_DEBUG_ANY, "%s: failed to add uidNumber attribute to entry\n",
					 uidnumber.on_bi.bi_type, 0, 0 );
		return;
	}

	sprintf( uidstr, "%lu", uid );
	ber_str2bv( uidstr, 0, 0, &uidbv );
	attr_merge_one( to_add, ad, &uidbv, 0 );

	Debug( LDAP_DEBUG_TRACE, "%s: added uidNumber %s to entry\n",
				 uidnumber.on_bi.bi_type, uidstr, 0 );
}

void addSambaSID(Operation *op, Entry* to_add)
{
	AttributeDescription* ad = NULL;
	Attribute* attr = NULL;
	char uidstr[128];
	struct berval uidbv = BER_BVNULL;
  unsigned long uid;
	const char* text;
  int rc;

	/* if already has a sambaSid, no further processing required */
	for ( attr = to_add->e_attrs; attr; attr = attr->a_next )
		{
			if (!strcmp( attr->a_desc->ad_cname.bv_val, uid_attr_name )) {
        uid = strtoul (attr->a_vals[0].bv_val, NULL, 10 );
      }

			if (!strcmp( attr->a_desc->ad_cname.bv_val, sid_attr_name )) {
				Debug(LDAP_DEBUG_TRACE, "%s: entry %s already has a sambaSID\n",
							uidnumber.on_bi.bi_type, to_add->e_nname.bv_val, 0);
				return;
			}
		}

	rc = slap_str2ad( sid_attr_name, &ad, &text );
	if(rc != LDAP_SUCCESS) {
		Debug( LDAP_DEBUG_ANY, "%s: failed to add sambaSID attribute to entry\n",
					 uidnumber.on_bi.bi_type, 0, 0 );
		return;
	}

  // use the same algorithm as smbldap-tools for generating a sambaSID
	sprintf( uidstr, "%s%lu", samba_domain_sid, (uid*2+1000) );
	ber_str2bv( uidstr, 0, 0, &uidbv );
	attr_merge_one( to_add, ad, &uidbv, 0 );

	Debug( LDAP_DEBUG_TRACE, "%s: added sambaSID %s to entry\n",
				 uidnumber.on_bi.bi_type, uidstr, 0 );
}

/**
 *	The meat of the overlay. Look for posixAccount adds with no uidNumber, and 
 *	add in the next available uidNumber as needed.
 */
static int uidnumber_add( Operation *op, SlapReply *rs )
{
	Entry* to_add = NULL;

	to_add = op->oq_add.rs_e;

	/* if the user doesn't have access, fall through to the normal ADD */
	if(!access_allowed( op, to_add, slap_schema.si_ad_entry,
											NULL, ACL_WRITE, NULL )) {
		return SLAP_CB_CONTINUE;
	}
	
  // optionally manage uidNumbers as needed
	if (oc_posix_account && is_entry_objectclass( (to_add), oc_posix_account, 0) )
    addUidNumber(op, to_add);

  // optionally manage sambaSIDs as needed
  if (oc_samba_account && is_entry_objectclass( (to_add), oc_samba_account, 0) )
    addSambaSID(op, to_add);
 
  return SLAP_CB_CONTINUE;
}

static unsigned long uidnumber_next_available(Operation *op)
{
	slap_overinst* on = (slap_overinst *)op->o_bd->bd_info;
	uidnumber_data* ad = on->on_bi.bi_private;

	Operation nop = *op;
	SlapReply nrs = { REP_RESULT };
	Filter* filter = NULL;
	slap_callback cb = { NULL, uidnumber_search_cb, NULL, ad };
	struct berval fstr = BER_BVNULL;
	struct berval rootstr = BER_BVNULL;
  int rc;

	/* if we already know the max uid, don't bother searching the tree */
	if(ad->max_uid_number == 0) {

		nop.o_callback = &cb;
		op->o_bd->bd_info = (BackendInfo *) on->on_info;
		nop.o_tag = LDAP_REQ_SEARCH;
		nop.o_ctrls = NULL;
		
		filter = str2filter( "(uidNumber=*)" );
		filter2bv( filter, &fstr );

		nop.ors_scope = LDAP_SCOPE_SUBTREE;
		nop.ors_deref = LDAP_DEREF_NEVER;
		nop.ors_slimit = -1;//SLAP_NO_LIMIT;
		nop.ors_tlimit = -1;//SLAP_NO_LIMIT;
		nop.ors_attrsonly = 1;
		nop.ors_attrs = slap_anlist_no_attrs;
		nop.ors_filter = filter;
		nop.ors_filterstr = fstr;

		memset( &nrs, 0, sizeof(nrs) );
		nrs.sr_type = REP_RESULT;
		nrs.sr_err = LDAP_SUCCESS;
		nrs.sr_entry = NULL;
		nrs.sr_flags |= REP_ENTRY_MUSTBEFREED;
		nrs.sr_text = NULL;

		nop.o_req_dn = rootstr;
		nop.o_req_ndn = rootstr;

		if(nop.o_bd->be_search) {
			rc = nop.o_bd->be_search( &nop, &nrs );
			Debug( LDAP_DEBUG_TRACE, "%s: searched for entries with uidNumber attribute\n",
						 uidnumber.on_bi.bi_type,0,0 );
		}
		else {
			Debug( LDAP_DEBUG_ANY, "%s: backend missing search function\n",
						 uidnumber.on_bi.bi_type,0,0 );
		}

		if(filter)
			filter_free( filter );
		if(fstr.bv_val)
			ch_free( fstr.bv_val );
	}

	return ++(ad->max_uid_number);
}

static int uidnumber_search_cb( Operation *op, SlapReply *rs )
{
	uidnumber_data* ad = op->o_callback->sc_private;
	Entry *entry = NULL;

	if( rs->sr_type != REP_SEARCH ) return 0;
				
	if( rs->sr_entry ) {
		Debug( LDAP_DEBUG_TRACE, "%s: dn found: %s\n",
					 uidnumber.on_bi.bi_type, rs->sr_entry->e_nname.bv_val, 0 );

		entry = rs->sr_entry;

		Attribute *attr = NULL;
		for (attr = entry->e_attrs; attr; attr = attr->a_next)
			{
				if(!strcmp( attr->a_desc->ad_cname.bv_val, uid_attr_name ))	{
					if(attr->a_numvals > 0 ) {
						unsigned long tmp = strtoul( attr->a_vals[0].bv_val, 0, 0 );
						Debug( LDAP_DEBUG_TRACE, "%s: uidNumber found: %lu\n",
									 uidnumber.on_bi.bi_type, tmp, 0 );
						if( tmp >= ad->max_uid_number ) ad->max_uid_number = tmp;
					}
				}
			}
	}

	return 0;
}

static int uidnumber_db_init(BackendDB *be,	ConfigReply *cr)
{
	slap_overinst *on = (slap_overinst *)be->bd_info;
	uidnumber_data *ad = ch_calloc( 1, sizeof(uidnumber_data) );

	on->on_bi.bi_private = ad;
	ldap_pvt_thread_mutex_init( &ad->mutex );
  ad->max_uid_number = 0;

	return 0;
}

static int uidnumber_db_destroy(BackendDB *be, ConfigReply *cr)
{
	slap_overinst *on = (slap_overinst *)be->bd_info;
	uidnumber_data *ad = on->on_bi.bi_private;

	ldap_pvt_thread_mutex_destroy( &ad->mutex );
	free( ad );

	return 0;
}

int uidnumber_init() 
{
	uidnumber.on_bi.bi_type = "uidnumber";
	uidnumber.on_bi.bi_op_add = uidnumber_add;
	uidnumber.on_bi.bi_db_init = uidnumber_db_init;
	uidnumber.on_bi.bi_db_destroy = uidnumber_db_destroy;

	oc_posix_account = oc_find( "posixAccount" );
	if(oc_posix_account == NULL) {
		Debug( LDAP_DEBUG_TRACE, "%s: unable to find default ObjectClass \"posixAccount\".\n",
					 uidnumber.on_bi.bi_type, 0, 0 );
	}

	oc_samba_account = oc_find( "sambaSamAccount" );
	if(oc_samba_account == NULL) {
		Debug( LDAP_DEBUG_TRACE, "%s: unable to find default ObjectClass \"sambaSamAccount\".\n",
					 uidnumber.on_bi.bi_type, 0, 0 );
	}

	return ( overlay_register(&uidnumber) );
}

int init_module( int argc, char *argv[] ) 
{
	return uidnumber_init();
}