[Commits] ldap-plugin.c 1.8 1.9

miras at claws-mail.org miras at claws-mail.org
Mon Nov 28 08:34:52 CET 2011


Update of /home/claws-mail/contacts/plugins/ldap
In directory claws-mail:/tmp/cvs-serv28372/plugins/ldap

Modified Files:
	ldap-plugin.c 
Log Message:
2011-11-28 [mir]	0.6.0cvs28

	* plugins/ldap/ldap-plugin.c
	* src/callbacks.c
	* src/contactwindow.c
	* src/printing.c
	* src/utils.c
	* src/utils.h
	    -Fix lots of bugs
	    -Improfements
	    -Refactoring
	    -And last: Complete LDAP plugin with full support
	     for write/update. Testing is adviceable. 

Index: ldap-plugin.c
===================================================================
RCS file: /home/claws-mail/contacts/plugins/ldap/ldap-plugin.c,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -d -r1.8 -r1.9
--- ldap-plugin.c	22 Nov 2011 00:10:53 -0000	1.8
+++ ldap-plugin.c	28 Nov 2011 07:34:49 -0000	1.9
@@ -79,6 +79,11 @@
 static GList* abooks = NULL;
 /* List of closed AddressBook */
 static GList* closed_books = NULL;
+
+/* Object classes used by the plugin */
+static const gchar*  objectclass[] = {
+	"person", "organizationalPerson", "inetOrgPerson", NULL};
+static const gchar* required_attributes[] = {"cn", "sn", NULL};
 /*
  * HashTable containing names of supported attributes.
  * Each keys holds an AttribDef.
@@ -634,6 +639,29 @@
 		return attr;
 }
 
+static gchar** ldap2native_list(const gchar** list) {
+	gchar **new_list, **tmp, **head;
+	guint len;
+	
+	if (! list)
+		return list;
+	
+	len = g_strv_length(list);
+	new_list = g_new0(gchar *, len + 1);
+	
+	if (len < 1)
+		return new_list;
+	
+	tmp = list;
+	head = new_list;
+	while (*tmp) {
+		debug_print("%s : %s\n", *tmp, native2ldap(*tmp));
+		*new_list++ = g_strdup(native2ldap(*tmp++));
+	}
+	
+	return head;
+}
+
 static gchar** attriblist2ldap() {
 	GHashTableIter iter;
 	gpointer key, value;
@@ -753,39 +781,38 @@
     return g_strconcat("(", token, "=", search, ")", NULL);
 }
 
-#define FILTER "(objectClass=*)"
-static gchar* filter_build(Contact* contact, gboolean intersection) {
+static gchar* filter_string_object(const gchar** list, const gchar* object) {
 	GString* filter;
-	//gchar* token;
-	
-	if (contact) {
-		//if (i > 0 && !(i % 2)) {
-		//    if (intersection)
-		//    	filter = g_string_prepend(filter, "(&");
-		//    else
-		//    	filter = g_string_prepend(filter, "(|");
-		//    filter = g_string_append(filter, token);
-		//    filter = g_string_append(filter, ")");
-		//}
-		//else
-		//	filter = g_string_append(filter, token);
-		//i++;
-	}
-	else {
-		filter = g_string_new(FILTER);
+	gchar *token, **head;
+	gboolean multi = FALSE;
+
+	filter = g_string_new("");
+	head = list;
+	while (head && *head) {
+		token = filter_token_add(object, *head++);
+		if (multi) {
+	    	filter = g_string_prepend(filter, "(|");
+		    filter = g_string_append(filter, token);
+		    filter = g_string_append(filter, ")");
+		}
+		else
+			filter = g_string_append(filter, token);
+		multi = TRUE;
+		g_free(token);
 	}
 	
 	return g_string_free(filter, FALSE);
 }
 
-static gchar* filter_build_standard() {
-	gchar** list = standard_attribs;
-	gchar* token;
-	GString* filter = g_string_new("");
+static gchar* filter_string_value(const gchar** list, const gchar* value) {
+	GString* filter;
+	gchar *token, **head;
 	gboolean multi = FALSE;
 
-	while(*list) {
-		token = filter_token_add(native2ldap(*list++), "*");
+	filter = g_string_new("");
+	head = list;
+	while (head && *head) {
+		token = filter_token_add(*head++, value);
 		if (multi) {
 	    	filter = g_string_prepend(filter, "(|");
 		    filter = g_string_append(filter, token);
@@ -800,6 +827,42 @@
 	return g_string_free(filter, FALSE);
 }
 
+static gchar* filter_build(Contact* contact, gboolean intersection) {
+	GString* filter = g_string_new("");
+	gchar* token;
+	gboolean multi = FALSE;
+
+	token = filter_string_object(objectclass, "objectClass");
+	filter = g_string_prepend(filter, token);
+	g_free(token);
+
+	if (contact) {
+		if (multi) {
+		    if (intersection)
+		    	filter = g_string_prepend(filter, "(&");
+		    else
+		    	filter = g_string_prepend(filter, "(|");
+		    filter = g_string_append(filter, token);
+		    filter = g_string_append(filter, ")");
+		}
+		else
+			filter = g_string_append(filter, token);
+	}
+	
+	return g_string_free(filter, FALSE);
+}
+
+static gchar* filter_build_standard() {
+	gchar **list;
+	gchar* filter;
+
+	list = ldap2native_list(standard_attribs);
+	filter = filter_string_value(list, "*");
+	g_strfreev(list);
+	
+	return filter;
+}
+
 static void gslist_free_ldapmessage(GSList** list) {
 	GSList* cur;
 	
@@ -887,6 +950,8 @@
         contact = contact_new();
         value = ldap_get_dn(server->ldap, entry);
         debug_print("Found: DN->%s\n", value);
+		AttribDef* attr = pack_data(ATTRIB_TYPE_STRING, "dn", value);
+		g_hash_table_insert(contact->data, g_strdup("dn"), attr);
         ldap_memfree(value);
         /* Process attributes */
         for (attribute = ldap_first_attribute(server->ldap, entry, &ber);
@@ -1226,6 +1291,205 @@
 	return contacts;
 }
 
+static gboolean contact_has_dn(Contact* contact) {
+	gpointer key, val;
+	
+	return g_hash_table_lookup_extended(contact->data, "dn", &key, &val);
+}
+
+static gchar* contact_get_dn(AbookConnection* ac) {
+	gchar* dn = NULL;
+	GSList* cur;
+		
+	if (! ac->contact)
+		dn = create_dummy_dn(ac->server->dn);
+	else if (! contact_has_dn(ac->contact)) {
+		cur = ac->contact->emails;
+		while(cur && ! dn) {
+			Email* e = (Email *) cur->data;
+			if (e->email)
+				dn = g_strconcat("mail=", e->email, ",", ac->server->dn, NULL);
+			else
+				cur = cur->next;
+		}
+		if (! dn) {
+			gchar* tmp = create_dummy_dn(NULL);
+			dn = g_strconcat("mail=", tmp, ",", ac->server->dn, NULL);
+			Email* a = g_new0(Email, 1);
+			a->email = g_strdup(tmp);
+			g_free(tmp);
+			ac->contact->emails = g_slist_append(ac->contact->emails, a);
+		}
+	}
+	else {
+		extract_data(ac->contact->data, "dn", (gpointer) &dn);
+	}
+	
+	return dn;
+}
+
+static void print_ldapmod(LDAPMod *mods[]) {
+    gchar *mod_op;
+    int i;
+
+    for (i = 0; NULL != mods[i]; i++) {
+		LDAPMod *mod = (LDAPMod *) mods[i];
+		gchar **vals;
+		switch (mod->mod_op) {
+            case LDAP_MOD_ADD:
+                mod_op = g_strdup("ADD");
+                break;
+            case LDAP_MOD_REPLACE:
+                mod_op = g_strdup("MODIFY");
+                break;
+            case LDAP_MOD_DELETE:
+                mod_op = g_strdup("DELETE");
+                break;
+            default: mod_op = g_strdup("UNKNOWN");
+	    }
+        debug_print("Operation: %s\tType:%s\nValues:\n",
+                mod_op, mod->mod_type);
+        g_free(mod_op);
+        vals = mod->mod_vals.modv_strvals;
+        while (*vals) {
+            debug_print("\t%s\n", *vals++);
+        }
+   	}
+}
+
+static void init_mods(gchar* mods[][2], guint size) {
+	guint i;
+	
+	for (i = 0; i < size; i++) {
+		mods[i][0] = mods[i][1] = NULL;
+	}
+}
+
+static gchar** get_object_classes() {
+	gchar** obj;
+	guint size = g_strv_length(objectclass) + 1;
+	int i;
+	
+	obj = g_new0(gchar *, size);
+	for (i = 0; i < size; i++)
+		obj[i] = g_strdup(objectclass[i]);
+		
+	return obj;
+}
+
+static void mods_free(LDAPMod *mods[]) {
+    int i;
+	gchar** vals;
+
+    for (i = 0; NULL != mods[i]; i++) {
+		LDAPMod* mod = (LDAPMod *) mods[i];
+		g_free(mod->mod_type);
+        vals = mod->mod_vals.modv_strvals;
+        while (*vals) {
+            g_free(*vals++);
+        }
+	}
+}
+
+static gboolean has_required_info(Contact* contact) {
+	gboolean has = TRUE;
+	gchar* attr = NULL;
+	const gchar** required = required_attributes;
+
+	while (*required && has) {
+		const gchar* key = ldap2native(*required++);
+		extract_data(contact->data, key, (gpointer) &attr);
+		if (!attr || strlen(attr) < 1)
+			has = FALSE;
+		g_free(attr);
+		attr = NULL;
+	}	
+	
+	return has;
+}
+
+static Contact* address_book_contact_get(AddressBook* abook, const gchar* dn) {
+	GList* cur;
+	Contact* contact = NULL;
+	gchar* uid;
+	
+	if (! abook || ! dn)
+		return NULL;
+		
+	cur = abook->contacts;
+	while (cur && ! contact) {
+		contact = (Contact *) cur->data;
+		extract_data(contact->data, "dn", (gpointer) &uid);
+		if (! uid || utf8_collate(dn, uid) != 0) {
+			contact = NULL;
+			cur = cur->next;
+		}
+		g_free(uid);
+	}
+	
+	return contact;
+}
+
+static gchar* extract_email_from_dn(const gchar* dn) {
+	gchar *tmp1, *tmp2;
+	
+	if (! dn)
+		return NULL;
+		
+	tmp1 = strchr(dn, '=');
+	tmp1 += 1;
+	tmp2 = strchr(tmp1, ',');
+	
+	return g_strndup(tmp1, tmp2 - tmp1);
+}
+
+static int ldap_set_dn(Server* server, GSList* list, gchar** dn) {
+	gchar* new_dn = NULL;
+	gchar *rdn = NULL;
+	Email* email;
+	int rc = 0;
+	GSList* cur;
+	gboolean found = FALSE;
+	
+	if (! dn || ! *dn)
+		return rc;
+	
+	rdn = extract_email_from_dn(*dn);
+	cur = list;
+	while (cur && ! found) {
+		ContactChange* c = (ContactChange *) cur->data;
+		if (c->type == CONTACT_CHANGE_EMAIL) {
+			email = (Email *) c->value;	
+			if (utf8_collate(email->email, rdn) == 0)
+				found = TRUE;
+			else {
+				if (! new_dn)
+					new_dn = g_strconcat("mail=", email->email, NULL);
+			}
+		}
+		cur = cur->next;
+	}
+	
+	if (! found) {
+		if (! new_dn) {
+			gchar* tmp = create_dummy_dn(NULL);
+			new_dn = g_strconcat("mail=", tmp, NULL);
+			g_free(tmp);
+		}
+		g_free(rdn);
+		debug_print("Old DN: %s -> New DN: %s\n", *dn, new_dn);
+		rc = ldap_rename_s(server->ldap, *dn, new_dn, NULL, 1, NULL, NULL);
+		if (! rc) {
+			g_free(*dn);
+			*dn = g_strconcat(new_dn, ",", server->dn, NULL);
+		}
+	}
+
+	g_free(new_dn);
+	
+	return rc;
+}
+
 /**
  * Save a contact in AddressBook.
  * @param abook Pointer to AddressBook
@@ -1235,12 +1499,118 @@
  */
 gboolean plugin_set_contact(
 		AddressBook* abook, const Contact* contact, gchar** error) {
+	gchar* dn = NULL;
+	AbookConnection* ac;
+	Server* server;
+	guint row = 0;
+	GSList *cur, *attributes;
+	guint size = g_strv_length(standard_attribs) + g_strv_length(other_attributes);
+    LDAPMod *mods[size + 2]; /* including NULL and objects */
+    LDAPMod modarr[size + 2]; /* including NULL and objects */
+	gchar* attr[size + 1][2];
+	gchar** obj;
+	int rc;
+	gchar** emails = NULL;
 	
 	/* Save contact */
+	if (! abook || ! contact) {
+		if (error)
+			*error = g_strdup(_("Missing address book or contact"));
+		return TRUE;
+	}
 	
-	abook->dirty = TRUE;
+	if (! has_required_info(contact)) {
+		if (error)
+			*error = g_strdup(_("Missing mandatory attributes"));
+		return TRUE;
+	}
+	
+	ac = get_abook_connection(abook);
+	if (! ac) {
+		if (error)
+			*error = g_strdup(_("Missing address book connection"));
+		return TRUE;
+	}
+	server = ac->server;
+	ac->contact = contact;
+	
+	dn = contact_get_dn(ac);
+	if (! dn) {
+		debug_print("LDAP Add: Unresolved error");
+		if (*error)
+			*error = g_strdup(_("LDAP Add: Unresolved error"));
+		abook->dirty = FALSE;
+		return TRUE;
+	}
 
-	return FALSE;
+	/* Initialize structure */
+	init_mods(attr, size + 2);
+	obj = get_object_classes();
+
+	if (! contact) {
+		Email* a = g_new0(Email, 1);
+		a->email = extract_email_from_dn(dn);
+		contact->emails = g_slist_append(contact->emails, a);
+	}
+	
+	cur = contact->emails;
+	/* First email is used as dn and will therefore be
+	 * added automatically
+	 */
+	if (cur)
+		cur = cur->next;
+	rc = 0;
+	while(cur) {
+		Email* e = (Email *) cur->data;
+		if (e->email) {
+			emails = g_renew(gchar *, emails, rc + 1);
+			emails[rc] = g_memdup(e->email, strlen(e->email) + 1);
+		}
+		cur = cur->next;
+		rc++;
+	}
+	if (rc) {
+		emails = g_renew(gchar *, emails, rc + 1);
+		emails[rc] = NULL;
+		SETMODS(mods[row], modarr[row], LDAP_MOD_ADD, g_strdup("mail"), emails);
+		row++;
+	}
+		
+	debug_print("Adding: %s\n", dn);
+	SETMODS(mods[row], modarr[row], LDAP_MOD_ADD, g_strdup("objectClass"), obj);
+	row++;
+	
+	attributes = self->attrib_list();
+	for (cur = attributes; cur; cur = g_slist_next(cur)) {
+		AttribDef* def = (AttribDef *) cur->data;
+		AttribDef* res = (AttribDef *) g_hash_table_lookup(contact->data, def->attrib_name);
+		if (res) {
+			const gchar* value = res->value.string;
+			SETMOD(mods[row], modarr[row], LDAP_MOD_ADD, \
+				g_strdup(native2ldap(def->attrib_name)), attr[row], g_strdup(value));
+			row++;
+		}
+	}
+	gslist_free(&attributes, attrib_def_free);
+	mods[row] = NULL;
+	if (debug_get_mode())
+		print_ldapmod(mods);
+	
+	rc = ldap_add_ext_s(server->ldap, dn, mods, NULL, NULL);
+	g_free(dn);
+	mods_free(mods);
+	if (rc) {
+		debug_print("LDAP ADD: %s\n", ldap_err2string(rc));
+		if (error)
+			*error = g_strdup(ldap_err2string(rc));
+		abook->dirty = FALSE;
+	}
+	else {
+		abook->dirty = TRUE;
+		return FALSE;
+	}
+
+	return TRUE;
 }
 
 /**
@@ -1252,8 +1622,46 @@
  */
 gboolean plugin_delete_contact(
 		AddressBook* abook, const Contact* contact, gchar** error) {
-	
+	gchar* dn;
+	AbookConnection* ac;
+	Server* server;
+	int rc;
+			
 	/* Delete contact */
+	if (! abook || ! contact) {
+		if (error)
+			*error = g_strdup(_("Missing address book or contact"));
+		return TRUE;
+	}
+	
+	ac = get_abook_connection(abook);
+	if (! ac) {
+		if (error)
+			*error = g_strdup(_("Missing address book connection"));
+		return TRUE;
+	}
+	server = ac->server;
+	ac->contact = contact;
+
+	dn = contact_get_dn(ac);
+    if (dn) {
+        rc = ldap_delete_ext_s(server->ldap, dn, NULL, NULL);
+        g_free(dn);
+        if (rc) {
+			debug_print("LDAP Delete: %s\n", ldap_err2string(rc));
+			if (*error)
+            	*error = g_strdup(ldap_err2string(rc));
+            abook->dirty = FALSE;
+            return TRUE;
+        }
+    }
+    else {
+        debug_print("LDAP Delete: No such contact\n");
+		if (*error)
+        	*error = g_strdup(_("No such contact"));
+        abook->dirty = FALSE;
+        return TRUE;
+    }
 			
 	abook->dirty = TRUE;
 
@@ -1269,8 +1677,175 @@
  */
 gboolean plugin_update_contact(
 		AddressBook* abook, const Contact* contact, gchar** error) {
+	gchar* dn = NULL;
+	gchar* old_dn = NULL;
+	AbookConnection* ac;
+	Contact* old;
+	GSList *diff, *cur;
+	AttribDef* attrib;
+	Email* email;
+	Server* server;
+	guint row = 0;
+	guint num = 0;
+	guint size = g_strv_length(standard_attribs) + g_strv_length(other_attributes);
+    LDAPMod *mods[size + 2]; // including NULL and objects
+    LDAPMod modarr[size + 2]; // including NULL and objects
+	gchar* attr[size + 1][2];
+	int rc;
+	gchar** emails = NULL;
 	
 	/* Update contact */
+	if (! abook || ! contact) {
+		if (error)
+			*error = g_strdup(_("Missing address book or contact"));
+		return TRUE;
+	}
+	
+	if (! has_required_info(contact)) {
+		if (error)
+			*error = g_strdup(_("Missing mandatory attributes"));
+		return TRUE;
+	}
+
+	if (debug_get_mode())
+		contact_dump(contact, stderr);
+	
+	ac = get_abook_connection(abook);
+	if (! ac) {
+		if (error)
+			*error = g_strdup(_("Missing address book connection"));
+		return TRUE;
+	}
+	server = ac->server;
+
+	ac->contact = contact;
+	dn = contact_get_dn(ac);
+	if (dn) {
+		/* Initialize structure */
+		init_mods(attr, size + 2);
+
+		debug_print("Updating: %s\n", dn);
+
+		old = address_book_contact_get(abook, dn);
+		if (old) {
+			diff = contact_diff(old, contact);
+			cur = diff;
+			old_dn = g_strdup(dn);
+			rc = ldap_set_dn(server, cur, &dn);
+			if (utf8_collate(dn, old_dn) != 0) {
+				AttribDef* attr = pack_data(ATTRIB_TYPE_STRING, "dn", (gpointer) dn);
+				g_hash_table_replace(contact->data, g_strdup("dn"), attr);
+			}
+			g_free(old_dn);
+				
+			while (cur && !rc) {
+				ContactChange* c = (ContactChange *) cur->data;
+				switch (c->type) {
+					case CONTACT_CHANGE_ATTRIB:
+						attrib = (AttribDef *) c->value;
+						/*
+						 * if c->action == ATTRIBUTE_MODIFY && c->value == ""
+						 * then change c->action to ATTRIBUTE_DELETE since
+						 * an empty string is to be treated as a missing attribute
+						 */
+						if (c->action == ATTRIBUTE_MODIFY && attrib->type == ATTRIB_TYPE_STRING
+							&& attrib->value.string && strlen(attrib->value.string) < 1)
+							c->action = ATTRIBUTE_DELETE;
+						break;
+					case CONTACT_CHANGE_EMAIL:
+						email = (Email *) c->value;
+						break;
+				}
+				
+				switch (c->action) {
+					case ATTRIBUTE_ADD:
+						if (c->type == CONTACT_CHANGE_EMAIL) {
+							/* Ignore - emails always modify */
+						}
+						else {
+							SETMOD(mods[row], modarr[row], LDAP_MOD_ADD, \
+								g_strdup(native2ldap(attrib->attrib_name)), \
+								attr[row], g_strdup(attrib->value.string));
+							row++;
+						}
+						break;
+					case ATTRIBUTE_DELETE:
+						if (c->type == CONTACT_CHANGE_EMAIL) {							
+							/* email equal dn (make dummy) */
+							SETMOD(mods[row], modarr[row], LDAP_MOD_REPLACE, \
+								g_strdup("mail"), attr[row], extract_email_from_dn(dn));
+							row++;
+						}
+						else {
+							SETMOD(mods[row], modarr[row], LDAP_MOD_DELETE, \
+								g_strdup(native2ldap(attrib->attrib_name)), \
+								attr[row], NULL);
+							row++;
+						}
+						/* check if dn is to be deleted */
+						break;
+					case ATTRIBUTE_MODIFY:
+						if (c->type == CONTACT_CHANGE_EMAIL) {
+							/* check if dn is to be modified */
+							emails = g_renew(gchar *, emails, num + 1);
+							emails[num] = g_memdup(email->email, strlen(email->email) + 1);
+							num++;
+						}
+						else {
+							SETMOD(mods[row], modarr[row], LDAP_MOD_REPLACE, \
+								g_strdup(native2ldap(attrib->attrib_name)), \
+								attr[row], g_strdup(attrib->value.string));
+							row++;
+						}
+						break;
+				}
+				cur = cur->next;
+			}
+			if (! rc) {
+				if (emails) {
+					emails = g_renew(gchar *, emails, num + 1);
+					emails[num] = NULL;
+					SETMODS(mods[row], modarr[row], LDAP_MOD_REPLACE, \
+						g_strdup("mail"), emails);
+					row++;
+				}
+				mods[row] = NULL;
+				if (debug_get_mode())
+					print_ldapmod(mods);
+				
+				rc = ldap_modify_ext_s(server->ldap, dn, mods, NULL, NULL);
+				mods_free(mods);
+			}
+			g_free(dn);
+			if (rc) {
+				debug_print("LDAP MODIFY: %s\n", ldap_err2string(rc));
+				if (error)
+					*error = g_strdup(ldap_err2string(rc));
+				abook->dirty = FALSE;
+				return TRUE;
+			}
+			else {
+				abook->dirty = TRUE;
+				return FALSE;
+			}
+			gslist_free(&diff, contact_change_free);
+		}
+		else {
+			g_free(dn);
+			dn = NULL;
+			if (self->set_contact(abook, contact, error)) {
+				abook->dirty = FALSE;
+				return TRUE;
+			}
+		}
+	}
+	else {
+		debug_print("LDAP Update: Unresolved error");
+		if (*error)
+			*error = g_strdup(_("LDAP Update: Unresolved error"));
+		abook->dirty = FALSE;
+		return TRUE;
+	}
 	
 	abook->dirty = TRUE;
 
@@ -1662,8 +2237,8 @@
 		return NULL;
 	}
 
-	/*feature->support = PLUGIN_READ_WRITE | PLUGIN_ADVANCED_SEARCH;*/
-	feature->support = PLUGIN_READ_ONLY | PLUGIN_ADVANCED_SEARCH;
+	feature->support = PLUGIN_READ_WRITE | PLUGIN_ADVANCED_SEARCH;
+	//feature->support = PLUGIN_READ_ONLY | PLUGIN_ADVANCED_SEARCH;
 	feature->subtype = subtype;
 
 	return feature;
@@ -1690,7 +2265,8 @@
 			 "If port is not specified 389 is assumed for ldap\n"
 			 "and 636 is assumed for ldaps.\n"
 			 "If ldap is used for schema then TLS will be tried\n"
-			 "automatically before using plain text connection.");
+			 "automatically before using plain text connection.\n"
+			 "Display name and Lastname are mandatory attributes.");
 }
 
 /**



More information about the Commits mailing list