2007-05-30 [colin] 2.9.2cvs17
[claws.git] / src / ldapupdate.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2003-2007 Michael Rasmussen and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 /*
21  * Functions necessary to access LDAP servers.
22  */
23
24 /*
25  * Outstanding bugs
26  * 1) When adding a contact to an empty addressbook from the pop-up menu
27  * when right-clicking on an email address causes claws-mail to crash in
28  * addritem.c line 965. Severity: Show stopper.
29  * 2) Updating a contact gets lost if the user makes a new search on the
30  * same LdapServer. Severity: Medium.
31  * 3) After adding a new contact the displayName for the contact is empty
32  * until the user makes a reread from the LdapServer. Severity: minor.
33  */
34
35 #ifdef HAVE_CONFIG_H
36 #  include "config.h"
37 #endif
38
39 #ifdef USE_LDAP
40
41 #include <glib.h>
42 #include <glib/gi18n.h>
43 #include <sys/time.h>
44 #include <string.h>
45 #include <ldap.h>
46 #include <lber.h>
47
48 #include "ldapupdate.h"
49 #include "mgutils.h"
50 #include "addritem.h"
51 #include "addrcache.h"
52 #include "ldapctrl.h"
53 #include "ldapquery.h"
54 #include "ldapserver.h"
55 #include "ldaputil.h"
56 #include "utils.h"
57 #include "adbookbase.h"
58
59 /**
60  * Structure to hold user defined attributes
61  * from contacts
62  */
63 typedef struct _AttrKeyValue AttrKeyValue;
64 struct _AttrKeyValue {
65         gchar *key;
66         gchar *value;
67 };
68
69 /**
70  * Structure to hold contact information.
71  * Each addressbook will have 0..N contacts.
72  */
73 typedef struct _EmailKeyValue EmailKeyValue;
74 struct _EmailKeyValue {
75         gchar *mail;
76         gchar *alias;
77         gchar *remarks;
78 };
79
80 /**
81  * Structure to hold information about RDN.
82  */
83 typedef struct _Rdn Rdn;
84 struct _Rdn {
85         gchar *attribute;
86         gchar *value;
87         gchar *new_dn;
88 };
89
90 /**
91  * Retrieve address group item for update.
92  * \param group  Group to print.
93  * \param array  GHashTAble of item_group, or <i>NULL</i> if none created.
94  */
95 void ldapsvr_retrieve_item_group(ItemGroup *group, GHashTable *array) {
96         /* Not implemented in this release */
97         g_return_if_fail(group != NULL);
98 }
99
100 /**
101  * Create an initial EmailKeyValue structure
102  * \return empty structure
103  */
104 EmailKeyValue *emailkeyvalue_create() {
105         EmailKeyValue *buf;
106
107         buf = g_new0(EmailKeyValue, 1);
108         buf->alias = NULL;
109         buf->mail = NULL;
110         buf->remarks = NULL;
111         return buf;
112 }
113
114 /**
115  * Create an initial AttrKeyValue structure
116  * \return empty structure
117  */
118 AttrKeyValue *attrkeyvalue_create() {
119         AttrKeyValue *buf;
120
121         buf = g_new0(AttrKeyValue, 1);
122         buf->key = NULL;
123         buf->value = NULL;
124         return buf;
125 }
126
127 /**
128  * Retrieve E-Mail address object for update.
129  * \param item   ItemEmail to update.
130  * \return object, or <i>NULL</i> if none created.
131  */
132 EmailKeyValue *ldapsvr_retrieve_item_email(ItemEMail *item) {
133         EmailKeyValue *newItem;
134         g_return_val_if_fail(item != NULL, NULL);
135         newItem = emailkeyvalue_create();               
136         newItem->alias = g_strdup(ADDRITEM_NAME(item));
137         newItem->mail = g_strdup(item->address);
138         newItem->remarks = g_strdup(item->remarks);
139         return newItem;
140 }
141
142 /**
143  * Retrieve user attribute object for update.
144  * \param item   UserAttribute to update.
145  * \return object, or <i>NULL</i> if none created.
146  */
147 AttrKeyValue *ldapsvr_retrieve_attribute(UserAttribute *item) {
148         AttrKeyValue *newItem;
149         g_return_val_if_fail(item != NULL, NULL);
150         newItem = attrkeyvalue_create();
151         newItem->key = g_strdup(item->name);
152         newItem->value = g_strdup(item->value);
153         return newItem;
154 }
155
156 /**
157  * Retrieve person object for update.
158  * \param person ItemPerson to update.
159  * \param array GHashTable with user input.
160  * \return false if update is not needed, or true if update is needed.
161  */
162 gboolean ldapsvr_retrieve_item_person(ItemPerson *person, GHashTable *array) {
163         GList *node, *attr;
164
165         g_return_val_if_fail(person != NULL, FALSE);
166         switch (person->status) {
167                 case NONE: return FALSE;
168                 case ADD_ENTRY: g_hash_table_insert(array, "status", "new"); break;
169                 case UPDATE_ENTRY: g_hash_table_insert(array, "status", "update"); break;
170                 case DELETE_ENTRY: g_hash_table_insert(array, "status", "delete"); break;
171                 default: g_critical(_("ldapsvr_retrieve_item_person->Unknown status: %d"), person->status);
172         }
173         g_hash_table_insert(array, "uid", ADDRITEM_ID(person));
174         g_hash_table_insert(array, "cn", ADDRITEM_NAME(person));
175         g_hash_table_insert(array, "givenName", person->firstName);
176         g_hash_table_insert(array, "sn", person->lastName);
177         g_hash_table_insert(array, "nickName", person->nickName);
178         g_hash_table_insert(array, "dn", person->externalID);
179         node = person->listEMail;
180         attr = NULL;
181         while (node) {
182                 EmailKeyValue *newEmail = ldapsvr_retrieve_item_email(node->data);
183                 if (newEmail)
184                         attr = g_list_append(attr, newEmail);
185                 node = g_list_next(node);
186         }
187         g_hash_table_insert(array, "mail", attr);
188 /* Not implemented in this release.
189         node = person->listAttrib;
190         attr = NULL;
191         while (node) {
192                 AttrKeyValue *newAttr = ldapsvr_retrieve_attribute(node->data)
193                 if (newAttr)
194                         attr = g_list_append(attr, newAttr);
195                 node = g_list_next(node);
196         }
197         g_hash_table_insert(array, "attribute", attr);
198 */
199         return TRUE;
200 }
201
202 /**
203  * Print contents of contacts hashtable for debug.
204  * This function must be called with g_hash_table_foreach.
205  * \param key Key to process.
206  * \param data Data to process.
207  * \param fd Output stream.
208  */
209 void ldapsvr_print_contacts_hashtable(gpointer key, gpointer data, gpointer fd) {
210         gchar *keyName = (gchar *) key;
211         GList *node;
212
213         if (g_ascii_strcasecmp("mail", keyName) == 0) {
214                 node = (GList *) data;
215                 while (node) {
216                         EmailKeyValue *item = node->data;
217                         if (debug_get_mode()) {
218                                 debug_print("\t\talias = %s\n", item->alias);
219                                 debug_print("\t\tmail = %s\n", item->mail);
220                                 debug_print("\t\tremarks = %s\n", item->remarks);
221                         }
222                         else if (fd) {
223                                 FILE *stream = (FILE *) fd;
224                                 fprintf(stream, "\t\talias = %s\n", item->alias);
225                                 fprintf(stream, "\t\tmail = %s\n", item->mail);
226                                 fprintf(stream, "\t\tremarks = %s\n", item->remarks);
227                         }
228                         node = g_list_next(node);
229                 }
230         }
231         else if (g_ascii_strcasecmp("attribute", keyName) == 0) {
232                 node = (GList *) data;
233                 while (node) {
234                         AttrKeyValue *item = node->data;
235                         if (debug_get_mode()) {
236                                 debug_print("\t\t%s = %s\n", item->key, item->value);
237                         }
238                         else if (fd) {
239                                 FILE *stream = (FILE *) fd;
240                                 fprintf(stream, "\t\t%s = %s\n", item->key, item->value);
241                         }
242                         node = g_list_next(node);
243                 }
244         }
245         else {
246                 if (debug_get_mode())
247                         debug_print("\t\t%s = %s\n", keyName, (gchar *) data);
248                 else if (fd) {
249                         FILE *stream = (FILE *) fd;
250                         fprintf(stream, "\t\t%s = %s\n", keyName, (gchar *) data);
251                 }
252         }
253 }
254
255 /**
256  * Free list of changed contacts
257  *
258  * \param list List of GHashTable
259  */
260 void ldapsvr_free_hashtable(GList *list) {
261         while (list) {
262                 g_hash_table_destroy(list->data);
263                 list = g_list_next(list);
264         }
265         g_list_free(list);
266 }
267
268 /**
269  * Get person object from cache
270  *
271  * \param server Resource to LDAP
272  * \param uid PersonID in cache
273  * \return person object, or <i>NULL</i> if fail
274  */
275 ItemPerson *ldapsvr_get_contact(LdapServer *server, gchar *uid) {
276         AddrItemObject *aio;
277         g_return_val_if_fail(server != NULL || uid != NULL, NULL);
278         aio = addrcache_get_object(server->addressCache, uid);
279         if (aio) {
280                 if(aio->type == ITEMTYPE_PERSON) {
281                         return (ItemPerson *) aio;
282                 }
283         }
284         return NULL;
285 }
286
287 /**
288  * Connect to LDAP server.
289  * \param  ctl Control object to process.
290  * \return LDAP Resource to LDAP.
291  */
292 LDAP *ldapsvr_connect(LdapControl *ctl) {
293         LDAP *ld = NULL;
294         gint rc;
295         gint version;
296         gchar *uri = NULL;
297
298         g_return_val_if_fail(ctl != NULL, NULL);
299
300         uri = g_strdup_printf("ldap%s://%s:%d",
301                                 ctl->enableSSL?"s":"",
302                                 ctl->hostName, ctl->port);
303         ldap_initialize(&ld, uri);
304         g_free(uri);
305
306         if (ld == NULL)
307                 return NULL;
308
309         debug_print("connected to LDAP host %s on port %d\n", ctl->hostName, ctl->port);
310
311 #ifdef USE_LDAP_TLS
312         /* Handle TLS */
313         version = LDAP_VERSION3;
314         rc = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
315         if (rc == LDAP_OPT_SUCCESS) {
316                 ctl->version = LDAP_VERSION3;
317         }
318
319         if (ctl->version == LDAP_VERSION3) {
320                 if (ctl->enableTLS && !ctl->enableSSL) {
321                         rc = ldap_start_tls_s(ld, NULL, NULL);
322                         
323                         if (rc != LDAP_SUCCESS) {
324                                 fprintf(stderr, "LDAP Error(tls): ldap_simple_bind_s: %s\n",
325                                         ldap_err2string(rc));
326                                 return NULL;
327                         }
328                 }
329         }
330 #endif
331
332         /* Bind to the server, if required */
333         if (ctl->bindDN) {
334                 if (* ctl->bindDN != '\0') {
335                         rc = claws_ldap_simple_bind_s(ld, ctl->bindDN, ctl->bindPass);
336                         if (rc != LDAP_SUCCESS) {
337                                 fprintf(stderr, "bindDN: %s, bindPass: %s\n", ctl->bindDN, ctl->bindPass);
338                                 fprintf(stderr, "LDAP Error(bind): ldap_simple_bind_s: %s\n",
339                                         ldap_err2string(rc));
340                                 return NULL;
341                         }
342                 }
343         }
344         return ld;
345 }
346
347 /**
348  * Disconnect to LDAP server.
349  * \param ld Resource to LDAP.
350  */
351 void ldapsvr_disconnect(LDAP *ld) {
352         /* Disconnect */
353         g_return_if_fail(ld != NULL);
354         ldap_unbind_ext(ld, NULL, NULL);
355 }
356
357 /**
358  * Create an initial Rdn structure
359  *
360  * \return empty structure
361  */
362 Rdn *rdn_create() {
363         Rdn *buf;
364
365         buf = g_new0(Rdn, 1);
366         buf->attribute = NULL;
367         buf->value = NULL;
368         buf->new_dn = NULL;
369         return buf;
370 }
371
372 /**
373  * update Rdn structure
374  *
375  * \param rdn Rdn structure to update
376  * \param head Uniq part of dn
377  * \param tail Common part of dn
378  */
379 void update_rdn(Rdn *rdn, gchar *head, gchar *tail) {
380         rdn->value = g_strdup(head);
381         rdn->new_dn = g_strdup_printf("mail=%s%s", head, tail);
382 }
383
384 /**
385  * Deside if dn needs to be changed
386  *
387  * \param hash GHashTable with user input.
388  * \param dn dn for current object
389  * \return Rdn structure
390  */
391 Rdn *ldapsvr_modify_dn(GHashTable *hash, gchar *dn) {
392         Rdn *rdn;
393         gchar *pos, *compare;
394         gchar *rest;
395         gchar *val;
396         g_return_val_if_fail(hash != NULL || dn != NULL, NULL);
397         
398         pos = g_strstr_len(dn, strlen(dn), "=");
399         compare = g_strndup(dn, pos - dn);
400
401         if (!pos)
402                 return NULL;
403         pos++;
404         rest = g_strstr_len(pos, strlen(pos), ",");
405         val = g_strndup(pos, rest - pos);
406         if (val == NULL) {
407                 if (compare)
408                         g_free(compare);
409                 return NULL;
410         }
411         rdn = rdn_create();
412         rdn->value = g_strdup(val);
413         rdn->attribute = g_strdup(compare);
414         g_free(val);
415         if (strcmp("mail", rdn->attribute) == 0) {
416                 GList *list = g_hash_table_lookup(hash, rdn->attribute);
417                 while (list) {
418                         EmailKeyValue *item = list->data;
419                         compare = g_strdup((gchar *) item->mail);
420                         if (strcmp(compare, rdn->value) == 0) {
421                                 update_rdn(rdn, compare, rest);
422                                 g_free(compare);
423                                 return rdn;
424                         }
425                         list = g_list_next(list);
426                 }
427                 /* if compare and rdn->attribute are equal then last email removed/empty  */
428                 if (strcmp(compare, rdn->attribute) != 0) {
429                         /* RDN changed. Find new */
430                         update_rdn(rdn, compare, rest);
431                         g_free(compare);
432                         return rdn;
433                 }
434                 else {
435                         /* We cannot remove dn */
436                         g_free(compare);
437                         return NULL;
438                 }
439         }
440         else {
441                 compare = g_hash_table_lookup(hash, rdn->attribute);
442                 /* if compare and rdn->attribute are equal then dn removed/empty */
443                 if (strcmp(compare, rdn->attribute) != 0) {
444                         update_rdn(rdn, compare, rest);
445                         return rdn;
446                 }
447                 else {
448                         /* We cannot remove dn */
449                         return NULL;
450                 }
451         }
452         return NULL;
453 }
454
455 /**
456  * This macro is borrowed from the Balsa project
457  * Creates a LDAPMod structure
458  *
459  * \param mods Empty LDAPMod structure
460  * \param modarr Array with values to insert
461  * \param op Operation to perform on LDAP
462  * \param attr Attribute to insert
463  * \param strv Empty array which is NULL terminated
464  * \param val Value for attribute
465  */
466 #define SETMOD(mods,modarr,op,attr,strv,val) \
467    do { (mods) = &(modarr); (modarr).mod_type=attr; (modarr).mod_op=op;\
468         (strv)[0]=(val); (modarr).mod_values=strv; \
469       } while(0)
470
471 /**
472  * Creates a LDAPMod structure
473  *
474  * \param mods Empty LDAPMod structure
475  * \param modarr Array with values to insert
476  * \param op Operation to perform on LDAP
477  * \param attr Attribute to insert
478  * \param strv Array with values to insert. Must be NULL terminated
479  */
480 #define SETMODS(mods,modarr,op,attr,strv) \
481    do { (mods) = &(modarr); (modarr).mod_type=attr; \
482                 (modarr).mod_op=op; (modarr).mod_values=strv; \
483       } while(0)
484 #define MODSIZE 10
485
486 /**
487  * Clean up, close LDAP connection, and refresh cache
488  *
489  * \param ld Resource to LDAP
490  * \param server AddressBook resource
491  * \param contact GHashTable with current changed object
492  */
493 void clean_up(LDAP *ld, LdapServer *server, GHashTable *contact) {
494         ItemPerson *person = 
495                 ldapsvr_get_contact(server, g_hash_table_lookup(contact , "uid"));
496         if (person) {
497                 gchar *displayName;
498                 person->status = NONE;
499                 displayName = g_hash_table_lookup(contact, "displayName");
500                 if (displayName)
501                         person->nickName = g_strdup(displayName);
502         }
503         if (ld)
504                 ldapsvr_disconnect(ld);
505 /*      ldapsvr_force_refresh(server);
506         ldapsvr_free_all_query(server);*/
507 }
508
509 /**
510  * Get cn attribute from dn
511  *
512  * \param dn Distinguesh Name for current object
513  * \return AttrKeyValue, or <i>NULL</i> if none created
514  */
515 AttrKeyValue *get_cn(gchar *dn) {
516         AttrKeyValue *cn;
517         gchar *start;
518         gchar *end;
519         gchar *item;
520         gchar **key_value;
521         g_return_val_if_fail(dn != NULL, NULL);
522         
523         cn = attrkeyvalue_create();
524         start = g_strstr_len(dn, strlen(dn), "cn");
525         if (start == NULL)
526                 return NULL;
527         end = g_strstr_len(start, strlen(start), ",");
528         item = g_strndup(start, end - start);
529         if (item == NULL)
530                 return NULL;
531         key_value = g_strsplit(item, "=", 2);
532         cn->key = g_strdup(key_value[0]);
533         cn->value = g_strdup(key_value[1]);
534         g_strfreev(key_value);
535         g_free(item);
536         return cn;
537 }
538
539 /**
540  * Get ou or o attribute from dn
541  *
542  * \param dn Distinguesh Name for current object
543  * \return AttrKeyValue, or <i>NULL</i> if none created
544  */
545 AttrKeyValue *get_ou(gchar *dn) {
546         AttrKeyValue *ou;
547         gchar *start;
548         gchar *end;
549         gchar *item;
550         gchar **key_value;
551         
552         g_return_val_if_fail(dn != NULL, NULL);
553         ou = attrkeyvalue_create();
554         start = g_strstr_len(dn, strlen(dn), ",o=");
555         if (start == NULL)
556                 start = g_strstr_len(dn, strlen(dn), ",ou=");
557         if (start == NULL)
558                 return NULL;
559         start++;
560         end = g_strstr_len(start, strlen(start), ",");
561         item = g_strndup(start, end - start);
562         if (item == NULL)
563                 return NULL;
564         key_value = g_strsplit(item, "=", 2);
565         ou->key = g_strdup(key_value[0]);
566         ou->value = g_strdup(key_value[1]);
567         g_strfreev(key_value);
568         g_free(item);
569         return ou;
570 }
571
572 /**
573  * Print the contents of a LDAPMod structure for debuging purposes
574  *
575  * \param mods LDAPMod structure
576  */
577 void ldapsvr_print_ldapmod(LDAPMod *mods[]) {
578         gchar *mod_op;
579         int i;
580
581         g_return_if_fail(mods != NULL);
582         fprintf( stderr, "Type\n");
583         for (i = 0; NULL != mods[i]; i++) {
584                 LDAPMod *mod = (LDAPMod *) mods[i];
585                 gchar **vals;
586                 switch (mod->mod_op) {
587                         case LDAP_MOD_ADD: mod_op = g_strdup("ADD"); break;
588                         case LDAP_MOD_REPLACE: mod_op = g_strdup("MODIFY"); break;
589                         case LDAP_MOD_DELETE: mod_op = g_strdup("DELETE"); break;
590                         default: mod_op = g_strdup("UNKNOWN");
591                 }
592                 fprintf( stderr, "Operation: %s\tType:%s\nValues:\n", mod_op, mod->mod_type);
593                 vals = mod->mod_vals.modv_strvals;
594                 while (*vals) {
595                         fprintf( stderr, "\t%s\n", *vals++);
596                 }
597         }
598 }
599
600 /**
601  * Make a compare for every new value we want to store in the
602  * directory with the current values. Great tool for debugging
603  * against invalid syntax in attributes
604  *
605  * \param ld AddressBook resource
606  * \param dn dn for the entry
607  * \param cnt Number of attributes to compare
608  * \param  mods LDAPMod structure
609  */
610 void ldapsvr_compare_attr(LDAP *ld, gchar *dn, gint cnt, LDAPMod *mods[]) {
611         int i, rc;
612         
613         g_return_if_fail(ld != NULL || dn != NULL || cnt >= 0 || mods != NULL);
614         for (i = 0; i < cnt; i++) {
615                 rc = ldap_compare_s(ld, dn, mods[i]->mod_type, mods[i]->mod_vals.modv_strvals[0]);
616                 fprintf(stderr, "ldap_compare for (%s:%s)\" failed[0x%x]: %s\n",
617                 mods[i]->mod_type, mods[i]->mod_vals.modv_strvals[0], rc, ldap_err2string(rc));
618         }
619 }
620
621 /**
622  * Add new contact to LDAP
623  *
624  * \param server AddressBook resource
625  * \param contact GHashTable with object to add
626  */
627 void ldapsvr_add_contact(LdapServer *server, GHashTable *contact) {
628         gchar *email = NULL, *param = NULL;
629         LDAP *ld = NULL;
630         LDAPMod *mods[MODSIZE];
631         LDAPMod modarr[7];
632         gint cnt = 0;
633         char *cn[] = {NULL, NULL};
634         char *displayName[] = {NULL, NULL};
635         char *givenName[] = {NULL, NULL};
636         char **mail = NULL;
637         char *sn[] = {NULL, NULL};
638         char *org[] = {NULL, NULL};
639         char *obj[] = {/*"top",*/ "person", "organizationalPerson", "inetOrgPerson", NULL}; 
640         int rc=0;
641         GList *node;
642         AttrKeyValue *ou, *commonName;
643         ItemPerson *person;
644         gchar *base_dn;
645         GList *mailList;
646
647         g_return_if_fail(server != NULL || contact != NULL);
648         node = g_hash_table_lookup(contact , "mail");
649         if (node) {
650                 EmailKeyValue *newEmail = node->data;
651                 email = g_strdup(newEmail->mail);
652         }
653         if (email == NULL) {
654                 server->retVal = LDAPRC_NODN;
655                 return;
656         }
657         base_dn = g_strdup_printf("mail=%s,%s", email, server->control->baseDN);
658         g_free(email);
659         person = 
660                 ldapsvr_get_contact(server, g_hash_table_lookup(contact , "uid"));
661         person->externalID = g_strdup(base_dn);
662         debug_print("dn: %s\n", base_dn);
663         ld = ldapsvr_connect(server->control);
664         if (ld == NULL) {
665                 clean_up(ld, server, contact);
666                 debug_print("no ldap found\n");
667                 return;
668         }
669         SETMODS(mods[cnt], modarr[cnt], LDAP_MOD_ADD, "objectClass", obj);
670         cnt++;
671         ou = get_ou(base_dn);
672         if (ou != NULL) {
673                 SETMOD(mods[cnt], modarr[cnt], LDAP_MOD_ADD, ou->key, org, ou->value);
674                 cnt++;
675         }
676         
677         commonName = get_cn(base_dn);
678         if (commonName == NULL) {
679                 param = g_hash_table_lookup(contact , "cn");
680                 if (param) {
681                         SETMOD(mods[cnt], modarr[cnt], LDAP_MOD_ADD, "cn", cn, param);
682                 }
683                 else {
684                         clean_up(ld, server, contact);
685                         debug_print("no CN found\n");
686                         return;
687                 }
688         }
689         else {
690                 SETMOD(mods[cnt], modarr[cnt], LDAP_MOD_ADD, commonName->key, cn, commonName->value);
691                 cnt++;
692                 param = g_hash_table_lookup(contact , "cn");
693                 SETMOD(mods[cnt], modarr[cnt], LDAP_MOD_ADD, "displayName", displayName, param);
694                 g_hash_table_insert(contact, "displayName", param);
695         }
696         cnt++;
697         param = g_hash_table_lookup(contact , "givenName");
698         if (param) {
699                 SETMOD(mods[cnt], modarr[cnt], LDAP_MOD_ADD, "givenName", givenName, param);
700                 cnt++;
701         }
702         mailList = g_hash_table_lookup(contact , "mail");
703         if (mailList) {
704                 char **tmp;
705                 mail = g_malloc(sizeof(*mail));
706                 tmp = g_malloc(sizeof(*tmp));
707                 mail = tmp;
708                 while (mailList) {
709                         EmailKeyValue *item = mailList->data;
710                         *tmp++ = g_strdup((gchar *) item->mail);
711                         mailList = g_list_next(mailList);
712                 }
713                 *tmp = NULL;
714                 SETMODS(mods[cnt], modarr[cnt], LDAP_MOD_ADD, "mail", mail);
715                 cnt++;
716         }
717         param = g_hash_table_lookup(contact, "sn");
718         if (param == NULL)
719                 param = g_strdup(N_("Some SN"));
720         SETMOD(mods[cnt], modarr[cnt], LDAP_MOD_ADD, "sn", sn, param);
721         cnt++;
722         mods[cnt] = NULL;
723         if (debug_get_mode()) {
724                 ldapsvr_print_ldapmod(mods);
725         }
726         server->retVal = LDAPRC_SUCCESS;
727         rc = ldap_add_ext_s(ld, base_dn, mods, NULL, NULL);
728         if (rc) {
729                 switch (rc) {
730                         case LDAP_ALREADY_EXISTS: 
731                                 server->retVal = LDAPRC_ALREADY_EXIST;
732                                 break;
733                         default:
734                                 fprintf(stderr, "ldap_modify for dn=%s\" failed[0x%x]: %s\n",base_dn, rc, ldap_err2string(rc));
735                                 if (rc == 0x8)
736                                         server->retVal = LDAPRC_STRONG_AUTH;
737                                 else
738                                         server->retVal = LDAPRC_NAMING_VIOLATION;
739                 }
740         }
741         g_free(base_dn);
742         clean_up(ld, server, contact);
743 }
744
745 /**
746  * Deside which kind of operation is required to handle
747  * updating the specified attribute
748  *
749  * \param ld AddressBook resource
750  * \param dn dn for the entry
751  * \param attr Attribute
752  * \param value New value
753  * \return int, return will be LDAP_MOD_ADD, LDAP_MOD_REPLACE, or LDAP_MOD_DELETE
754  */
755 int ldapsvr_deside_operation(LDAP *ld, char *dn, char *attr, char *value) {
756         int rc;
757         gboolean dummy = FALSE;
758
759         g_return_val_if_fail(ld != NULL || dn != NULL || attr != NULL, -1);
760         if (value == NULL)
761                 return -1;
762         /* value containing empty string cause invalid syntax. A bug in
763          * the LDAP library? Therefore we add a dummy value
764          */
765         if (strcmp(value,"") == 0) {
766                 value = g_strdup("thisisonlyadummy");
767                 dummy = TRUE;
768         }
769         rc = ldap_compare_s(ld, dn, attr, value);
770         debug_print("ldap_compare for (%s:%s)\" error_code[0x%x]: %s\n",
771         attr, value, rc, ldap_err2string(rc));
772         switch (rc) {
773                 case LDAP_COMPARE_FALSE: 
774                         if (dummy)
775                                 return LDAP_MOD_DELETE;
776                         else
777                                 return LDAP_MOD_REPLACE;
778                 case LDAP_COMPARE_TRUE: return -1;
779                 case LDAP_NO_SUCH_ATTRIBUTE: return LDAP_MOD_ADD;
780                 case LDAP_UNDEFINED_TYPE: return -2;
781                 case LDAP_INVALID_SYNTAX: return -2;
782                 default: return -2;
783         }
784 }
785
786 /**
787  * Update contact to LDAP
788  *
789  * \param server AddressBook resource
790  * \param contact GHashTable with object to update
791  */
792 void ldapsvr_update_contact(LdapServer *server, GHashTable *contact) {
793         LDAP *ld = NULL;
794         LDAPMod *mods[MODSIZE];
795         LDAPMod modarr[4];
796         gint cnt = 0;
797         gchar *param, *dn;
798         Rdn *NoRemove = NULL;
799         char *cn[] = {NULL, NULL};
800         char *givenName[] = {NULL, NULL};
801         char **mail = NULL;
802         char *sn[] = {NULL, NULL};
803         GList *mailList;
804         int mod_op;
805
806         g_return_if_fail(server != NULL || contact != NULL);
807         ld = ldapsvr_connect(server->control);
808         if (ld == NULL) {
809                 clean_up(ld, server, contact);
810                 return;
811         }
812         dn = g_hash_table_lookup(contact, "dn");
813         if (dn == NULL) {
814                 clean_up(ld, server, contact);
815                 return;
816         }
817         NoRemove = ldapsvr_modify_dn(contact, dn);
818         if (NoRemove) {
819                 /* We are trying to change RDN */
820                 gchar *newRdn = g_strdup_printf("%s=%s", NoRemove->attribute, NoRemove->value);
821                 int rc = ldap_modrdn2_s(ld, dn, newRdn, 1);
822                 if(rc != LDAP_SUCCESS) {
823                         if (rc ==  LDAP_ALREADY_EXISTS) {
824                                 /* We are messing with a contact with more than one listed email
825                                  * address and the email we are changing is not the one used for dn
826                                  */
827                                 /* It needs to be able to handle renaming errors to an already defined
828                                  * dn. For now we just refuse the update. It will be caught later on as
829                                  * a LDAPRC_NAMING_VIOLATION error.
830                                  */
831                         }
832                         else {
833                                 fprintf(stderr, "Current dn: %s\n", dn);
834                                 fprintf(stderr, "new dn: %s\n", newRdn);
835                                 fprintf(stderr, "LDAP Error(ldap_modrdn2_s) failed[0x%x]: %s\n", rc, ldap_err2string(rc));
836                                 g_free(newRdn);
837                                 return;
838                         }
839                 }
840                 else {
841                         g_free(newRdn);
842                         dn = g_strdup(NoRemove->new_dn);
843                 }
844         }
845         else {
846                 server->retVal = LDAPRC_NODN;
847                 clean_up(ld, server, contact);
848                 return;
849         }
850         param = g_hash_table_lookup(contact , "cn");
851         mod_op = ldapsvr_deside_operation(ld, dn, "displayName", param);
852         if (mod_op >= 0 && (strcmp(param, NoRemove->value) != 0 && strcmp("cn", NoRemove->attribute) != 0)) {
853                 if (mod_op == LDAP_MOD_DELETE) {
854                         /* Setting param to NULL instructs OpenLDAP to remove any
855                          * value stored for this attribute and remove the attribute
856                          * completely. Should multiple instances of an attribute be
857                          * allowed in the future param is required to have the value
858                          * store for the attribute which is going to be deleted
859                          */
860                         param = NULL;
861                 }
862                 SETMOD(mods[cnt], modarr[cnt], mod_op, "displayName", cn, param);
863                 cnt++;
864                 g_hash_table_insert(contact, "displayName", param);
865         }
866         param = g_hash_table_lookup(contact , "givenName");
867         mod_op = ldapsvr_deside_operation(ld, dn, "givenName", param);
868         if (mod_op >= 0 && (strcmp(param, NoRemove->value) != 0 && strcmp("givenName", NoRemove->attribute) != 0)) {
869                 if (mod_op == LDAP_MOD_DELETE) {
870                         /* Setting param to NULL instructs OpenLDAP to remove any
871                          * value stored for this attribute and remove the attribute
872                          * completely. Should multiple instances of an attribute be
873                          * allowed in the future param is required to have the value
874                          * store for the attribute which is going to be deleted
875                          */
876                         param = NULL;
877                 }
878                 SETMOD(mods[cnt], modarr[cnt], mod_op, "givenName", givenName, param);
879                 cnt++;
880         }
881         mailList = g_hash_table_lookup(contact , "mail");
882         if (mailList) {
883                 debug_print("# of mail: %d\n", g_list_length(mailList));
884                 if (!(strcmp("mail", NoRemove->attribute) == 0 && g_list_length(mailList) == 1)) {
885                         char **tmp;
886                         mail = g_malloc(sizeof(*mail));
887                         tmp = g_malloc(sizeof(*tmp));
888                         mail = tmp;
889                         while (mailList) {
890                                 EmailKeyValue *item = mailList->data;
891                                 *tmp++ = g_strdup((gchar *) item->mail);
892                                 mailList = g_list_next(mailList);
893                         }
894                         *tmp = NULL;
895                         /*
896                          * At least one email address is required
897                          * in which case it will always be a replace
898                          */
899                         SETMODS(mods[cnt], modarr[cnt], LDAP_MOD_REPLACE, "mail", mail);
900                         cnt++;
901                 }
902         }
903         else {
904                 /*
905                  * an error condition since at least one email adress
906                  * is required. Should never occur though.
907                  */
908         }
909         param = g_hash_table_lookup(contact , "sn");
910         mod_op = ldapsvr_deside_operation(ld, dn, "sn", param);
911         if (mod_op >= 0 && (strcmp(param, NoRemove->value) != 0 && strcmp("sn", NoRemove->attribute) != 0)) {
912                 if (mod_op == LDAP_MOD_DELETE) {
913                         /* Setting param to NULL instructs OpenLDAP to remove any
914                          * value stored for this attribute and remove the attribute
915                          * completely. Should multiple instances of an attribute be
916                          * allowed in the future param is required to have the value
917                          * store for the attribute which is going to be deleted
918                          */
919                         param = NULL;
920                 }
921                 SETMOD(mods[cnt], modarr[cnt], mod_op, "sn", sn, param);
922                 cnt++;
923         }
924         debug_print("newDN: %s\n", dn);
925         if (NoRemove)
926                 g_free(NoRemove);
927         server->retVal = LDAPRC_SUCCESS;
928         if (cnt > 0) {
929                 int rc;
930                 mods[cnt] = NULL;
931                 rc = ldap_modify_ext_s(ld, dn, mods, NULL, NULL);
932                 if (rc) {
933                         fprintf(stderr, "ldap_modify for dn=%s\" failed[0x%x]: %s\n",
934                     dn, rc, ldap_err2string(rc));
935                         server->retVal = LDAPRC_NAMING_VIOLATION;
936                 }
937                 if (mail)
938                         g_free(mail);
939         }
940         /* If we do not make changes persistent at this point then changes
941          * will be lost if the user makes new search on the same server since
942          * changes are only present in Claws' internal cache. This issue has to
943          * be solved in addressbook.c since this involves access to structures
944          * which are only accessible in addressbook.c */
945         clean_up(ld, server, contact);
946 }
947
948 /**
949  * Delete contact from LDAP
950  *
951  * \param server AddressBook resource
952  * \param contact GHashTable with object to delete
953  */
954 void ldapsvr_delete_contact(LdapServer *server, GHashTable *contact) {
955         LDAP *ld = NULL;
956         gchar *dn;
957         int rc;
958
959         g_return_if_fail(server != NULL || contact != NULL);
960         ld = ldapsvr_connect(server->control);
961         if (ld == NULL) {
962                 clean_up(ld, server, contact);
963                 return;
964         }
965         dn = g_hash_table_lookup(contact, "dn");
966         if (dn == NULL) {
967                 clean_up(ld, server, contact);
968                 return;
969         }
970         rc = ldap_delete_ext_s(ld, dn, NULL, NULL);
971         if (rc) {
972                 fprintf(stderr, "ldap_modify for dn=%s\" failed[0x%x]: %s\n",
973                                 dn, rc, ldap_err2string(rc));
974                 server->retVal = LDAPRC_NODN;
975         }
976         clean_up(ld, server, contact);
977 }
978
979 /**
980  * Update any changes to the server.
981  *
982  * \param server AddressBook resource.
983  * \param person ItemPerson holding user input.
984  */
985 void ldapsvr_update_book(LdapServer *server, ItemPerson *item) {
986         GList *node = NULL;
987         GHashTable *contact = NULL;
988         GList *contacts = NULL, *head = NULL;
989
990         g_return_if_fail(server != NULL);
991         debug_print("updating ldap addressbook\n");
992
993         contact = g_hash_table_new(g_str_hash, g_str_equal);
994         if (item) {
995                 gboolean result = ldapsvr_retrieve_item_person(item, contact);
996                 debug_print("Found contact to update: %s\n", result? "Yes" : "No");
997                 if (result) {
998                         if (debug_get_mode()) {
999                                 addritem_print_item_person(item, stdout);
1000                         }
1001                         contacts = g_list_append(contacts, contact);
1002                 }
1003         }
1004         else {
1005                 ItemFolder *folder = server->addressCache->rootFolder;
1006                 node = folder->listFolder;
1007                 if (node) {
1008                         while (node) {
1009                                 AddrItemObject *aio = node->data;
1010                                 if (aio) {
1011                                         if (aio->type == ITEMTYPE_FOLDER) {
1012                                                 ItemFolder *folder = (ItemFolder *) aio;
1013                                                 GList *persons = folder->listPerson;
1014                                                 while (persons) {
1015                                                         AddrItemObject *aio = persons->data;
1016                                                         if (aio) {
1017                                                                 if (aio->type == ITEMTYPE_PERSON) {
1018                                                                         ItemPerson *item = (ItemPerson *) aio;
1019                                                                         gboolean result = ldapsvr_retrieve_item_person(item, contact);
1020                                                                         debug_print("Found contact to update: %s\n", result? "Yes" : "No");
1021                                                                         if (result) {
1022                                                                                 if (debug_get_mode()) {
1023                                                                                         gchar *uid = g_hash_table_lookup(contact, "uid");
1024                                                                                         item = ldapsvr_get_contact(server, uid);
1025                                                                                         addritem_print_item_person(item, stdout);
1026                                                                                 }
1027                                                                                 contacts = g_list_append(contacts, contact);
1028                                                                         }
1029                                                                 }
1030                                                         }
1031                                                         persons = g_list_next(persons);
1032                                                 }
1033                                         }
1034                                 }
1035                                 else {
1036                                         fprintf(stderr, "\t\tpid : ???\n");
1037                                 }
1038                                 node = g_list_next(node);
1039                         }
1040                 }
1041         }
1042         head = contacts;
1043         if (debug_get_mode()) {
1044                 if (contacts)
1045                         debug_print("Contacts which must be updated in LDAP:\n");
1046                 while (contacts) {
1047                         debug_print("\tContact:\n");
1048                         g_hash_table_foreach(contacts->data, 
1049                                 ldapsvr_print_contacts_hashtable, stderr);
1050                         contacts = g_list_next(contacts);
1051                 }
1052         }
1053         if (contacts == NULL)
1054                 contacts = head;
1055         while (contacts) {
1056                 gchar *status;
1057                 contact = (GHashTable *) contacts->data;
1058                 status = (gchar *) g_hash_table_lookup(contact, "status");
1059                 if (status == NULL)
1060                         status = g_strdup("NULL");
1061                 if (g_ascii_strcasecmp(status, "new") == 0) {
1062                         ldapsvr_add_contact(server, contact);
1063                 }
1064                 else if (g_ascii_strcasecmp(status, "update") == 0) {
1065                         ldapsvr_update_contact(server, contact);
1066                 }
1067                 else if (g_ascii_strcasecmp(status, "delete") == 0) {
1068                         ldapsvr_delete_contact(server, contact);
1069                 }
1070                 else
1071                         g_critical(_("ldapsvr_update_book->Unknown status: %s\n"), status);
1072                 contacts = g_list_next(contacts);
1073         }
1074         ldapsvr_free_hashtable(head);
1075 }
1076
1077 #endif  /* USE_LDAP */
1078
1079 /*
1080  * End of Source.
1081  */
1082