#include "ldap_plugin.h"
#include <sasl/sasl.h>

int lutil_sasl_interact(
        LDAP *ld,
        unsigned flags,
        lutilSASLdefaults *defaults,
        void *in )
{
        sasl_interact_t *interact = in;

        if( ld == NULL ) return LDAP_PARAM_ERROR;

        while( interact->id != SASL_CB_LIST_END ) {
		switch( interact->id ) {
			case SASL_CB_GETREALM:
				interact->result = (defaults->realm && *defaults->realm) ? defaults->realm : "";
                		interact->len = strlen( interact->result );
				break;
			case SASL_CB_AUTHNAME:
				interact->result = (defaults->authcid && *defaults->authcid) ? defaults->authcid : "";
                		interact->len = strlen( interact->result );
				break;
			case SASL_CB_PASS:
				interact->result = (defaults->passwd && *defaults->passwd) ? defaults->passwd : "";
                		interact->len = strlen( interact->result );
				break;
			case SASL_CB_USER:
				interact->result = (defaults->authzid && *defaults->authzid) ? defaults->authzid : "";
                		interact->len = strlen( interact->result );
				break;
		}
                interact++;
        }

        return LDAP_SUCCESS;
}

lutilSASLdefaults *lutil_sasl_defaults(LDAP *ld, char *mech, char *realm, char *authcid, char *passwd, char *authzid)
{
        lutilSASLdefaults *defaults;
        defaults = ber_memalloc( sizeof( lutilSASLdefaults ) );

        if( defaults == NULL ) return NULL;

        defaults->mech = mech ? ber_strdup(mech) : NULL;
        defaults->realm = realm ? ber_strdup(realm) : NULL;
        defaults->authcid = authcid ? ber_strdup(authcid) : NULL;
        defaults->passwd = passwd ? ber_strdup(passwd) : NULL;
        defaults->authzid = authzid ? ber_strdup(authzid) : NULL;


        if( defaults->mech == NULL ) {
                ldap_get_option( ld, LDAP_OPT_X_SASL_MECH, &defaults->mech );
        }
        if( defaults->realm == NULL ) {
                ldap_get_option( ld, LDAP_OPT_X_SASL_REALM, &defaults->realm );
        }
        if( defaults->authcid == NULL ) {
                ldap_get_option( ld, LDAP_OPT_X_SASL_AUTHCID, &defaults->authcid );
        }
        if( defaults->authzid == NULL ) {
                ldap_get_option( ld, LDAP_OPT_X_SASL_AUTHZID, &defaults->authzid );
        }
        defaults->resps = NULL;
        defaults->nresps = 0;

        return defaults;
}

/*Connect and bind to a ldap server*/
int ldap_start(ldap_connection *conn)
{
	int ldap_vers = LDAP_VERSION3;

	ldap_debug(conn, 2, "Connecting to %s", conn->servername);

	if (ldap_is_ldap_url(conn->servername) || ldap_is_ldaps_url(conn->servername)) {
		ldap_initialize(&conn->ld, conn->servername);
		if( conn->ld == NULL ) {
			ldap_debug(conn, 0, "Could not connect to %s", conn->servername);
			return 1;
		}
	} else {
		conn->ld = ldap_init(conn->servername, conn->serverport);
		if( conn->ld == NULL ) {
			ldap_debug(conn, 0, "Could not connect to %s on %i", conn->servername, conn->serverport);
			return 1;
		}
	}

	return 0;
}

int ldap_set_version(ldap_connection *conn)
{
	int *ldap_version = 0;
	if (ldap_set_option(conn->ld, LDAP_OPT_PROTOCOL_VERSION, &conn->ldap_version) != LDAP_SUCCESS) {
		ldap_get_option(conn->ld, LDAP_OPT_PROTOCOL_VERSION, ldap_version);
		ldap_debug(conn, 1, "Could not set Ldap Version to %i using %i", conn->ldap_version, *ldap_version);
		conn->ldap_version = *ldap_version;
		return 1;
	}
	return 0;
}

int ldap_encrypt(ldap_connection *conn)
{
	if (ldap_start_tls_s(conn->ld, NULL, NULL) != LDAP_SUCCESS) {
		ldap_debug(conn, 1, "Could not start encryption");
		return 1;
	}
	return 0;
}

int ldap_makebind(ldap_connection *conn)
{
	unsigned sasl_flags = LDAP_SASL_AUTOMATIC;
	struct berval passwd = { 0, NULL };
	lutilSASLdefaults *defaults;
	char *authmech, *binddn, *pwd;

	if (!conn->anonymous) {
		binddn = conn->binddn;
		pwd = conn->pwd;
		authmech = conn->authmech;
	} else {
		authmech = "SIMPLE";
		binddn = "";
		pwd = "";
	}

	if (!strcmp(authmech, "SIMPLE")) {
		ldap_debug(conn, 2, "Simple auth selected");
		if (ldap_simple_bind_s(conn->ld, binddn, pwd)) {
			ldap_debug(conn, 0, "Unable to connect and bind to %s as %s", conn->servername, binddn);
			return 1;
		}
	} else {
		ldap_debug(conn, 2, "Sasl auth selected");
		passwd.bv_val = ber_strdup(conn->pwd);
		passwd.bv_len = strlen( passwd.bv_val );

		defaults = lutil_sasl_defaults(conn->ld, ber_strdup(conn->authmech), NULL, ber_strdup(conn->binddn), passwd.bv_val, NULL);
		if (ldap_sasl_interactive_bind_s(conn->ld, NULL, ber_strdup(conn->authmech), NULL, NULL, sasl_flags, (LDAP_SASL_INTERACT_PROC *)lutil_sasl_interact, (void *)defaults ) != LDAP_SUCCESS) {
			ldap_debug(conn, 0, "Unable to connect and sasl bind to %s as %s", conn->servername, conn->binddn);
			return 1;
		}
	}
	return 0;
}


int ldap_check_evolution(ldap_connection *conn)
{
	LDAPMessage *res, *res2;
	int i = 0;
	char *attrs[] = {"objectClasses", NULL};
	char **ldapvals = NULL;

	//Search all entries that apply to the filter. modifyTimestamp
	if (ldap_search_s(conn->ld, "cn=Subschema", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, 0, &res)) {
		ldap_debug(conn, 0, "Unable to search for evolution support");
		return 1;
	}

	res2 = ldap_first_entry(conn->ld, res);
	if (!res2) {
		ldap_debug(conn, 0, "No objectclass entries found");
		return 1;
	}

	ldapvals = ldap_get_values(conn->ld, res2, "objectClasses");

	while (ldapvals[i]) {
		if (strstr(ldapvals[i], "evolutionPerson")) {
			ldap_value_free(ldapvals);
			return 0;
		}
		i++;
	}

	return 1;
}
/*
struct data_entry *ldap_get_entry(ldap_connection *conn, char *uid)
{
	LDAPMessage *res, *res2;
	struct data_entry *entry = NULL;
	char *attrs[] = {"modifyTimestamp", NULL};
	entry = g_malloc0(sizeof(struct data_entry));
	char filter[1024];
	uid = quoted_decode(uid);

	ldap_debug(conn, 2, "Loading single head data for %s", ldap_explode_dn(uid, 0)[0]);

	//Search all entries that apply to the filter. get modifyTimestamp
	sprintf(filter, "(%s)", ldap_explode_dn(uid, 0)[0]);
	if (ldap_search_s(conn->ld, conn->searchbase, LDAP_SCOPE_ONELEVEL, filter, attrs, 0, &res)) {
		ldap_debug(conn, 0, "Unable to search on %s", conn->searchbase);
		return NULL;
	}

	res2 = ldap_first_entry(conn->ld, res);
	if (!res2) {
		ldap_debug(conn, 2, "No entries found");
		return NULL;
	}

	do {
		if (!strcmp(ldap_get_dn(conn->ld, res2), uid)) {
			entry->modifyTimestamp = (ldap_get_values(conn->ld, res2, "modifyTimestamp"))[0];
			entry->uid = quoted_encode(ldap_get_dn(conn->ld, res2));
			ldap_debug(conn, 2, "Loaded entry: %s, %s", entry->modifyTimestamp, entry->uid);
			break;
		}
		res2 = ldap_next_entry(conn->ld, res2);
	} while (res2);

	ldap_debug(conn, 3, "done: ldap_get_entry");
	return entry;
}*/

/*load the head data from the ldap server*/
GList *load_ldap_entries(ldap_connection *conn)
{
	LDAPMessage *res, *res2;
	GList *headlist = NULL;
	struct data_entry *entry = NULL;
	char *attrs[] = {"modifyTimestamp", NULL};
	char **ldapvals = NULL;
	char filter[1024];

	ldap_debug(conn, 2, "Loading head data from ldap");

	//Search all entries that apply to the filter. get modifyTimestamp
	sprintf(filter, "(&(objectClass=*)%s)", conn->filter);
	if (ldap_search_s(conn->ld, conn->searchbase, conn->scope, filter, attrs, 0, &res)) {
		ldap_debug(conn, 0, "Unable to search on %s with filter %s", conn->searchbase, filter);
		return NULL;
	}

	res2 = ldap_first_entry(conn->ld, res);
	if (!res2) {
		ldap_debug(conn, 2, "No entries found");
		return NULL;
	}

	do {
		//We have a new entry
		entry = g_malloc0(sizeof(struct data_entry));

		ldapvals = ldap_get_values(conn->ld, res2, "modifyTimestamp");
		if (ldapvals) {
			entry->modifyTimestamp = strdup(ldapvals[0]);
			ldap_value_free(ldapvals);
			entry->uid = quoted_encode(ldap_get_dn(conn->ld, res2));
			ldap_debug(conn, 3, "Loaded entry: %s, %s", entry->modifyTimestamp, entry->uid);

			headlist = g_list_append(headlist, entry);
		} else {
			ldap_debug(conn, 0, "Loaded entry %s missing modifyTimestamp. Impossible to sync that. don't use slapadd, use ldapadd!", ldap_get_dn(conn->ld, res2));
		}

		res2 = ldap_next_entry(conn->ld, res2);
	} while (res2);

	ldap_debug(conn, 3, "end: load_ldap_entries");
	return headlist;
}

/*Get the complete data for the given head entry*/
/*returns them as LDAPMod*/
void get_ldap_data(ldap_connection *conn, struct data_entry *ldapentry)
{
	char filter[1024];
	LDAPMessage *res = NULL, *res2 = NULL, *res3 = NULL;
	char *attrs[] = {"*", NULL};
	int count = 0, n = 0;
	char **ret = NULL;
	BerElement *berptr = NULL;
	char *attribute = NULL;

	ldap_debug(conn, 2, "Loading full data for: %s", ldapentry->uid);

	ldapentry->ldapdata = g_malloc0(1024 * sizeof(LDAPMod *));

	sprintf(filter, "(&(objectClass=*)(%s))", ldap_explode_dn(quoted_decode(ldapentry->uid), 0)[0]);
	if (ldap_search_s(conn->ld, conn->searchbase, conn->scope, filter, attrs, 0, &res)) {
		ldap_debug(conn, 0, "Unable to search with filter %s", filter);
		return;
	}

	res2 = ldap_first_entry(conn->ld, res);
	if (!res2) {
		ldap_debug(conn, 0, "No ldap entry returned!: %s", ldapentry->uid);
		return;
	}

	attribute = ldap_first_attribute(conn->ld, res2, &berptr);
	count = 0;
	while (attribute) {
		//Found a new attribute
		ldapentry->ldapdata[count] = malloc(sizeof(LDAPMod));
		ldapentry->ldapdata[count]->mod_values = ldap_get_values(conn->ld, res2, attribute);
		ldapentry->ldapdata[count]->mod_type = attribute;
		ldap_debug(conn, 4, "Attribute: %s=%s", attribute, ldapentry->ldapdata[count]->mod_values[0]);

		attribute = ldap_next_attribute(conn->ld, res2, berptr);
		count++;
	}
	ldap_debug(conn, 3, "end: get_ldap_data");
}

/*Add a ldap_data struct to the ldap server*/
int ldap_add_entry(ldap_connection *conn, LDAPMod **data, char *uidret, int duplicate)
{
	int i = 0, x = 0, n = 0, result = 0;
	char dn[1024];
	ldap_debug(conn, 3, "start: ldap_add_entry");

	for (i = 0; data[i]; i++) {
		//Add another attribute
		data[i]->mod_op = LDAP_MOD_ADD;
		if (!strcmp(data[i]->mod_type, "cn") && !duplicate) {
			sprintf(dn, "cn=3D%s,%s", quoted_encode(data[i]->mod_values[0]), quoted_encode(conn->searchbase));
			strcpy(uidret, dn);
		}
	}

	if (duplicate) {
		sprintf(dn, "uid=3Dduplicate%d,%s",  rand(), quoted_encode(conn->searchbase));
		strcpy(uidret, dn);
		data[i] = g_malloc0(sizeof(LDAPMod));
		data[i]->mod_values = g_malloc0(2 * sizeof(char *));
		data[i]->mod_type = "uid";
		data[i]->mod_values[0] = ldap_explode_dn(quoted_decode(dn), 1)[0];
		data[i]->mod_values[1] = NULL;
		data[i]->mod_op = LDAP_MOD_ADD;
		data[i + 1] = NULL;
	}

	ldap_debug(conn, 2, "Adding: %s", quoted_decode(dn));
	if (result = ldap_add_s(conn->ld, quoted_decode(dn), data)) {
		if (result == 68 && !duplicate) {
			//Multisync wants to duplicate item
			ldap_debug(conn, 2, "Duplicating Entries");
			result = ldap_add_entry(conn, data, uidret, 1);
			return result;
		}
		ldap_debug(conn, 1, "Add result: %i: %s", result, ldap_err2string(result));
		return -1;
	}

	ldap_debug(conn, 3, "end: ldap_add_entry");
	return 0;
}

int ldap_modify_entry(ldap_connection *conn, LDAPMod **data)
{
	int i = 0, x = 0;
	ldap_debug(conn, 3, "start: ldap modify entry");
	for (i = 0; data[i]; i++) {
		for (x = 0; data[i]->mod_values[x]; x++) {
			ldap_debug(conn, 3, "%s=%s", data[i]->mod_type, data[i]->mod_values[x]);
		}
	}
	ldap_debug(conn, 3, "end: ldap modify entry");
	return 0;
}

int ldap_delete_entry(ldap_connection *conn, char *cn)
{
	int result = 0;
	ldap_debug(conn, 2, "Deleting %s", cn);

	if (result = ldap_delete_s(conn->ld, cn)) {
		ldap_debug(conn, 1, "Delete result: %s", ldap_err2string(result));
		return -1;
	}
	ldap_debug(conn, 3, "end: ldap_delete_entry");
	return 0;
}
