Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / lib / e2k-global-catalog.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* Copyright (C) 2001-2004 Novell, Inc.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "e2k-global-catalog-ldap.h"
25 #include "e2k-sid.h"
26 #include "e2k-utils.h"
27
28 #include <pthread.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/time.h>
32
33 #ifdef HAVE_LDAP_NTLM_BIND
34 #include "xntlm.h"
35 #endif
36
37 #ifdef E2K_DEBUG
38 static gboolean e2k_gc_debug = FALSE;
39 #define E2K_GC_DEBUG_MSG(x) if (e2k_gc_debug) printf x
40 #else
41 #define E2K_GC_DEBUG_MSG(x)
42 #endif
43
44 struct _E2kGlobalCatalogPrivate {
45         GMutex *ldap_lock;
46         LDAP *ldap;
47
48         GPtrArray *entries;
49         GHashTable *entry_cache, *server_cache;
50
51         char *server, *user, *nt_domain, *password;
52 };
53
54 #define PARENT_TYPE G_TYPE_OBJECT
55 static GObjectClass *parent_class = NULL;
56
57 static void finalize (GObject *);
58 static int get_gc_connection (E2kGlobalCatalog *gc, E2kOperation *op);
59
60
61 static void
62 class_init (GObjectClass *object_class)
63 {
64 #ifdef E2K_DEBUG
65         char *e2k_debug = getenv ("E2K_DEBUG");
66
67         if (e2k_debug && atoi (e2k_debug) > 3)
68                 e2k_gc_debug = TRUE;
69 #endif
70
71         /* For some reason, sasl_client_init (called by ldap_init
72          * below) takes a *really* long time to scan the sasl modules
73          * when running under gdb. We're not using sasl anyway, so...
74          */
75         putenv("SASL_PATH=");
76
77         parent_class = g_type_class_ref (PARENT_TYPE);
78
79         /* virtual method override */
80         object_class->finalize = finalize;
81 }
82
83 static void
84 init (GObject *object)
85 {
86         E2kGlobalCatalog *gc = E2K_GLOBAL_CATALOG (object);
87
88         gc->priv = g_new0 (E2kGlobalCatalogPrivate, 1);
89         gc->priv->ldap_lock = g_mutex_new ();
90         gc->priv->entries = g_ptr_array_new ();
91         gc->priv->entry_cache = g_hash_table_new (e2k_ascii_strcase_hash,
92                                                   e2k_ascii_strcase_equal);
93         gc->priv->server_cache = g_hash_table_new (g_str_hash, g_str_equal);
94 }
95
96 static void
97 free_entry (E2kGlobalCatalogEntry *entry)
98 {
99         int i;
100
101         g_free (entry->dn);
102         g_free (entry->display_name);
103
104         if (entry->sid)
105                 g_object_unref (entry->sid);
106
107         g_free (entry->email);
108         g_free (entry->mailbox);
109
110         if (entry->delegates) {
111                 for (i = 0; i < entry->delegates->len; i++)
112                         g_free (entry->delegates->pdata[i]);
113                 g_ptr_array_free (entry->delegates, TRUE);
114         }
115         if (entry->delegators) {
116                 for (i = 0; i < entry->delegators->len; i++)
117                         g_free (entry->delegators->pdata[i]);
118                 g_ptr_array_free (entry->delegators, TRUE);
119         }
120
121         g_free (entry);
122 }
123
124 static void
125 free_server (gpointer key, gpointer value, gpointer data)
126 {
127         g_free (key);
128         g_free (value);
129 }
130
131 static void
132 finalize (GObject *object)
133 {
134         E2kGlobalCatalog *gc = E2K_GLOBAL_CATALOG (object);
135         int i;
136
137         if (gc->priv) {
138                 if (gc->priv->ldap)
139                         ldap_unbind (gc->priv->ldap);
140
141                 for (i = 0; i < gc->priv->entries->len; i++)
142                         free_entry (gc->priv->entries->pdata[i]);
143                 g_ptr_array_free (gc->priv->entries, TRUE);
144
145                 g_hash_table_foreach (gc->priv->server_cache, free_server, NULL);
146                 g_hash_table_destroy (gc->priv->server_cache);
147
148                 g_free (gc->priv->server);
149                 g_free (gc->priv->user);
150                 g_free (gc->priv->nt_domain);
151                 if (gc->priv->password) {
152                         memset (gc->priv->password, 0, strlen (gc->priv->password));
153                         g_free (gc->priv->password);
154                 }
155
156                 g_mutex_free (gc->priv->ldap_lock);
157
158                 g_free (gc->priv);
159                 gc->priv = NULL;
160         }
161
162         g_free (gc->domain);
163         gc->domain = NULL;
164
165         G_OBJECT_CLASS (parent_class)->finalize (object);
166 }
167
168
169 E2K_MAKE_TYPE (e2k_global_catalog, E2kGlobalCatalog, class_init, init, PARENT_TYPE)
170
171 static int
172 gc_ldap_result (LDAP *ldap, E2kOperation *op,
173                 int msgid, LDAPMessage **msg)
174 {
175         struct timeval tv;
176         int status, ldap_error;
177
178         tv.tv_sec = 1;
179         tv.tv_usec = 0;
180         *msg = NULL;
181         do {
182                 status = ldap_result (ldap, msgid, TRUE, &tv, msg);
183                 if (status == -1) {
184                         ldap_get_option (ldap, LDAP_OPT_ERROR_NUMBER,
185                                          &ldap_error);
186                         return ldap_error;
187                 }
188         } while (status == 0 && !e2k_operation_is_cancelled (op));
189
190         if (e2k_operation_is_cancelled (op)) {
191                 ldap_abandon (ldap, msgid);
192                 return LDAP_USER_CANCELLED;
193         } else
194                 return LDAP_SUCCESS;
195 }
196
197 static int
198 gc_search (E2kGlobalCatalog *gc, E2kOperation *op,
199            const char *base, int scope, const char *filter,
200            const char **attrs, LDAPMessage **msg)
201 {
202         int ldap_error, msgid, try;
203
204         for (try = 0; try < 2; try++) {
205                 ldap_error = get_gc_connection (gc, op);
206                 if (ldap_error != LDAP_SUCCESS)
207                         return ldap_error;
208                 ldap_error = ldap_search_ext (gc->priv->ldap, base, scope,
209                                               filter, (char **)attrs,
210                                               FALSE, NULL, NULL, NULL, 0,
211                                               &msgid);
212                 if (ldap_error == LDAP_SERVER_DOWN)
213                         continue;
214                 else if (ldap_error != LDAP_SUCCESS)
215                         return ldap_error;
216
217                 ldap_error = gc_ldap_result (gc->priv->ldap, op, msgid, msg);
218                 if (ldap_error == LDAP_SERVER_DOWN)
219                         continue;
220                 else if (ldap_error != LDAP_SUCCESS)
221                         return ldap_error;
222
223                 return LDAP_SUCCESS;
224         }
225
226         return LDAP_SERVER_DOWN;
227 }
228
229 #ifdef HAVE_LDAP_NTLM_BIND
230 static int
231 ntlm_bind (E2kGlobalCatalog *gc, E2kOperation *op, LDAP *ldap)
232 {
233         LDAPMessage *msg;
234         int ldap_error, msgid, err;
235         char *nonce, *default_domain;
236         GByteArray *ba;
237         struct berval ldap_buf;
238
239         /* Create and send NTLM request */
240         ba = xntlm_negotiate ();
241         ldap_buf.bv_len = ba->len;
242         ldap_buf.bv_val = ba->data;
243         ldap_error = ldap_ntlm_bind (ldap, "NTLM", LDAP_AUTH_NTLM_REQUEST,
244                                      &ldap_buf, NULL, NULL, &msgid);
245         g_byte_array_free (ba, TRUE);
246         if (ldap_error != LDAP_SUCCESS) {
247                 E2K_GC_DEBUG_MSG(("GC: Failure sending first NTLM bind message: 0x%02x\n", ldap_error));
248                 return ldap_error;
249         }
250
251         /* Extract challenge */
252         ldap_error = gc_ldap_result (ldap, op, msgid, &msg);
253         if (ldap_error != LDAP_SUCCESS) {
254                 E2K_GC_DEBUG_MSG(("GC: Could not parse first NTLM bind response\n"));
255                 return ldap_error;
256         }
257         ldap_error = ldap_parse_ntlm_bind_result (ldap, msg, &ldap_buf);
258         ldap_msgfree (msg);
259         if (ldap_error != LDAP_SUCCESS) {
260                 E2K_GC_DEBUG_MSG(("GC: Could not parse NTLM bind response: 0x%02x\n", ldap_error));
261                 return ldap_error;
262         }
263
264         if (!xntlm_parse_challenge (ldap_buf.bv_val, ldap_buf.bv_len,
265                                     &nonce, &default_domain,
266                                     &gc->domain)) {
267                 E2K_GC_DEBUG_MSG(("GC: Could not find nonce in NTLM bind response\n"));
268                 ber_memfree (ldap_buf.bv_val);
269
270                 return LDAP_DECODING_ERROR;
271         }
272         ber_memfree (ldap_buf.bv_val);
273
274         /* Create and send response */
275         ba = xntlm_authenticate (nonce, gc->priv->nt_domain ? gc->priv->nt_domain : default_domain,
276                                  gc->priv->user, gc->priv->password, NULL);
277         ldap_buf.bv_len = ba->len;
278         ldap_buf.bv_val = ba->data;
279         ldap_error = ldap_ntlm_bind (ldap, "NTLM", LDAP_AUTH_NTLM_RESPONSE,
280                                      &ldap_buf, NULL, NULL, &msgid);
281         g_byte_array_free (ba, TRUE);
282         g_free (nonce);
283         g_free (default_domain);
284         if (ldap_error != LDAP_SUCCESS) {
285                 E2K_GC_DEBUG_MSG(("GC: Failure sending second NTLM bind message: 0x%02x\n", ldap_error));
286                 return ldap_error;
287         }
288
289         /* And get the final result */
290         ldap_error = gc_ldap_result (ldap, op, msgid, &msg);
291         if (ldap_error != LDAP_SUCCESS) {
292                 E2K_GC_DEBUG_MSG(("GC: Could not parse second NTLM bind response\n"));
293                 return ldap_error;
294         }
295         ldap_error = ldap_parse_result (ldap, msg, &err, NULL, NULL,
296                                         NULL, NULL, TRUE);
297         if (ldap_error != LDAP_SUCCESS) {
298                 E2K_GC_DEBUG_MSG(("GC: Could not parse second NTLM bind response: 0x%02x\n", ldap_error));
299                 return ldap_error;
300         }
301
302         return err;
303 }
304 #endif
305
306 static int
307 connect_ldap (E2kGlobalCatalog *gc, E2kOperation *op, LDAP *ldap)
308 {
309         int ldap_error;
310 #ifndef HAVE_LDAP_NTLM_BIND
311         char *nt_name;
312 #ifdef G_OS_WIN32
313         SEC_WINNT_AUTH_IDENTITY_W auth;
314 #endif
315 #endif
316
317         /* authenticate */
318 #ifdef HAVE_LDAP_NTLM_BIND
319         ldap_error = ntlm_bind (gc, op, ldap);
320 #else
321         nt_name = gc->priv->nt_domain ?
322                 g_strdup_printf ("%s\\%s", gc->priv->nt_domain, gc->priv->user) :
323                 g_strdup (gc->priv->user);
324 #ifndef G_OS_WIN32
325         ldap_error = ldap_simple_bind_s (ldap, nt_name, gc->priv->password);
326 #else
327         auth.User = g_utf8_to_utf16 (gc->priv->user, -1, NULL, NULL, NULL);
328         auth.UserLength = wcslen (auth.User);
329         auth.Domain = gc->priv->nt_domain ?
330                 g_utf8_to_utf16 (gc->priv->nt_domain, -1, NULL, NULL, NULL) :
331                 g_utf8_to_utf16 ("", -1, NULL, NULL, NULL);
332         auth.DomainLength = wcslen (auth.Domain);
333         auth.Password = g_utf8_to_utf16 (gc->priv->password, -1, NULL, NULL, NULL);
334         auth.PasswordLength = wcslen (auth.Password);
335         auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
336         ldap_error = ldap_bind_s (ldap, nt_name, &auth, LDAP_AUTH_NTLM);
337         g_free (auth.Password);
338         g_free (auth.Domain);
339         g_free (auth.User);
340 #endif
341         g_free (nt_name);
342 #endif
343         if (ldap_error != LDAP_SUCCESS)
344                 g_warning ("LDAP authentication failed (0x%02x)", ldap_error);
345         else
346                 E2K_GC_DEBUG_MSG(("GC: connected\n\n"));
347
348         return ldap_error;
349 }
350
351 static int
352 get_ldap_connection (E2kGlobalCatalog *gc, E2kOperation *op,
353                      const char *server, int port,
354                      LDAP **ldap)
355 {
356         int ldap_opt, ldap_error;
357
358         E2K_GC_DEBUG_MSG(("\nGC: Connecting to ldap://%s:%d/\n", server, port));
359
360         *ldap = ldap_init (server, port);
361         if (!*ldap) {
362                 E2K_GC_DEBUG_MSG(("GC: failed\n\n"));
363                 g_warning ("Could not connect to ldap://%s:%d/",
364                            server, port);
365                 return LDAP_SERVER_DOWN;
366         }
367
368         /* Set options */
369         ldap_opt = LDAP_DEREF_ALWAYS;
370         ldap_set_option (*ldap, LDAP_OPT_DEREF, &ldap_opt);
371         ldap_opt = gc->response_limit;
372         ldap_set_option (*ldap, LDAP_OPT_SIZELIMIT, &ldap_opt);
373         ldap_opt = LDAP_VERSION3;
374         ldap_set_option (*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldap_opt);
375
376         ldap_error = connect_ldap (gc, op, *ldap);
377         if (ldap_error != LDAP_SUCCESS) {
378                 ldap_unbind (*ldap);
379                 *ldap = NULL;
380         }
381         return ldap_error;
382 }
383
384 static int
385 get_gc_connection (E2kGlobalCatalog *gc, E2kOperation *op)
386 {
387         int err;
388
389         if (gc->priv->ldap) {
390                 ldap_get_option (gc->priv->ldap, LDAP_OPT_ERROR_NUMBER, &err);
391                 if (err != LDAP_SERVER_DOWN)
392                         return LDAP_SUCCESS;
393
394                 return connect_ldap (gc, op, gc->priv->ldap);
395         } else {
396                 return get_ldap_connection (gc, op,
397                                             gc->priv->server, 3268,
398                                             &gc->priv->ldap);
399         }
400 }
401
402 /**
403  * e2k_global_catalog_get_ldap:
404  * @gc: the global catalog
405  * @op: pointer to an initialized #E2kOperation to use for cancellation
406  *
407  * Returns a new LDAP handle. The caller must ldap_unbind() it when it
408  * is done.
409  *
410  * Return value: an LDAP handle, or %NULL if it can't connect
411  **/
412 LDAP *
413 e2k_global_catalog_get_ldap (E2kGlobalCatalog *gc, E2kOperation *op)
414 {
415         LDAP *ldap;
416
417         g_return_val_if_fail (E2K_IS_GLOBAL_CATALOG (gc), NULL);
418
419         get_ldap_connection (gc, op, gc->priv->server, 3268, &ldap);
420         return ldap;
421 }
422
423 /**
424  * e2k_global_catalog_new:
425  * @server: the GC server name
426  * @response_limit: the maximum number of responses to return from a search
427  * @user: username to authenticate with
428  * @domain: NT domain of @user, or %NULL to autodetect.
429  * @password: password to authenticate with
430  *
431  * Create an object for communicating with the Windows Global Catalog
432  * via LDAP.
433  *
434  * Return value: the new E2kGlobalCatalog. (This call will always succeed.
435  * If the passed-in data is bad, it will fail on a later call.)
436  **/
437 E2kGlobalCatalog *
438 e2k_global_catalog_new (const char *server, int response_limit,
439                         const char *user, const char *domain,
440                         const char *password)
441 {
442         E2kGlobalCatalog *gc;
443
444         gc = g_object_new (E2K_TYPE_GLOBAL_CATALOG, NULL);
445         gc->priv->server = g_strdup (server);
446         gc->priv->user = g_strdup (user);
447         gc->priv->nt_domain = g_strdup (domain);
448         gc->priv->password = g_strdup (password);
449         gc->response_limit = response_limit;
450
451         return gc;
452 }
453
454 static const char *
455 lookup_mta (E2kGlobalCatalog *gc, E2kOperation *op, const char *mta_dn)
456 {
457         char *hostname, **values;
458         const char *attrs[2];
459         LDAPMessage *resp;
460         int ldap_error, i;
461
462         /* Skip over "CN=Microsoft MTA," */
463         mta_dn = strchr (mta_dn, ',');
464         if (!mta_dn)
465                 return NULL;
466         mta_dn++;
467
468         hostname = g_hash_table_lookup (gc->priv->server_cache, mta_dn);
469         if (hostname)
470                 return hostname;
471
472         E2K_GC_DEBUG_MSG(("GC:   Finding hostname for %s\n", mta_dn));
473
474         attrs[0] = "networkAddress";
475         attrs[1] = NULL;
476
477         ldap_error = gc_search (gc, op, mta_dn, LDAP_SCOPE_BASE,
478                                 NULL, attrs, &resp);
479         if (ldap_error != LDAP_SUCCESS) {
480                 E2K_GC_DEBUG_MSG(("GC:   lookup failed (0x%02x)\n", ldap_error));
481                 return NULL;
482         }
483
484         values = ldap_get_values (gc->priv->ldap, resp, "networkAddress");
485         ldap_msgfree (resp);
486         if (!values) {
487                 E2K_GC_DEBUG_MSG(("GC:   entry has no networkAddress\n"));
488                 return NULL;
489         }
490
491         hostname = NULL;
492         for (i = 0; values[i]; i++) {
493                 if (strstr (values[i], "_tcp")) {
494                         hostname = strchr (values[i], ':');
495                         break;
496                 }
497         }
498         if (!hostname) {
499                 E2K_GC_DEBUG_MSG(("GC:   host is not availble by TCP?\n"));
500                 ldap_value_free (values);
501                 return NULL;
502         }
503
504         hostname = g_strdup (hostname + 1);
505         g_hash_table_insert (gc->priv->server_cache, g_strdup (mta_dn), hostname);
506         ldap_value_free (values);
507
508         E2K_GC_DEBUG_MSG(("GC:   %s\n", hostname));
509         return hostname;
510 }
511
512
513 static void
514 get_sid_values (E2kGlobalCatalog *gc, E2kOperation *op,
515                 LDAPMessage *msg, E2kGlobalCatalogEntry *entry)
516 {
517         char **values;
518         struct berval **bsid_values;
519         E2kSidType type;
520
521         values = ldap_get_values (gc->priv->ldap, msg, "displayName");
522         if (values) {
523                 E2K_GC_DEBUG_MSG(("GC: displayName %s\n", values[0]));
524                 entry->display_name = g_strdup (values[0]);
525                 ldap_value_free (values);
526         }
527
528         bsid_values = ldap_get_values_len (gc->priv->ldap, msg, "objectSid");
529         if (!bsid_values)
530                 return;
531         if (bsid_values[0]->bv_len < 2 ||
532             bsid_values[0]->bv_len != E2K_SID_BINARY_SID_LEN (bsid_values[0]->bv_val)) {
533                 E2K_GC_DEBUG_MSG(("GC: invalid SID\n"));
534                 return;
535         }
536
537         values = ldap_get_values (gc->priv->ldap, msg, "objectCategory");
538         if (values && values[0] && !g_ascii_strncasecmp (values[0], "CN=Group", 8))
539                 type = E2K_SID_TYPE_GROUP;
540         else if (values && values[0] && !g_ascii_strncasecmp (values[0], "CN=Foreign", 10))
541                 type = E2K_SID_TYPE_WELL_KNOWN_GROUP;
542         else /* FIXME? */
543                 type = E2K_SID_TYPE_USER;
544         if (values)
545                 ldap_value_free (values);
546
547         entry->sid = e2k_sid_new_from_binary_sid (
548                 type, bsid_values[0]->bv_val, entry->display_name);
549         entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_SID;
550
551         ldap_value_free_len (bsid_values);
552 }
553
554 static void
555 get_mail_values (E2kGlobalCatalog *gc, E2kOperation *op,
556                  LDAPMessage *msg, E2kGlobalCatalogEntry *entry)
557 {
558         char **values, **mtavalues;
559
560         values = ldap_get_values (gc->priv->ldap, msg, "mail");
561         if (values) {
562                 E2K_GC_DEBUG_MSG(("GC: mail %s\n", values[0]));
563                 entry->email = g_strdup (values[0]);
564                 g_hash_table_insert (gc->priv->entry_cache,
565                                      entry->email, entry);
566                 entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_EMAIL;
567                 ldap_value_free (values);
568         }
569
570         values = ldap_get_values (gc->priv->ldap, msg, "mailNickname");
571         mtavalues = ldap_get_values (gc->priv->ldap, msg, "homeMTA");
572         if (values && mtavalues) {
573                 E2K_GC_DEBUG_MSG(("GC: mailNickname %s\n", values[0]));
574                 E2K_GC_DEBUG_MSG(("GC: homeMTA %s\n", mtavalues[0]));
575                 entry->exchange_server = (char *)lookup_mta (gc, op, mtavalues[0]);
576                 ldap_value_free (mtavalues);
577                 if (entry->exchange_server)
578                         entry->mailbox = g_strdup (values[0]);
579                 ldap_value_free (values);
580                 entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX;
581         }
582
583         values = ldap_get_values (gc->priv->ldap, msg, "legacyExchangeDN");
584         if (values) {
585                 E2K_GC_DEBUG_MSG(("GC: legacyExchangeDN %s\n", values[0]));
586                 entry->legacy_exchange_dn = g_strdup (values[0]);
587                 g_hash_table_insert (gc->priv->entry_cache,
588                                      entry->legacy_exchange_dn,
589                                      entry);
590                 entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_LEGACY_EXCHANGE_DN;
591                 ldap_value_free (values);
592         }
593 }
594
595 static void
596 get_delegation_values (E2kGlobalCatalog *gc, E2kOperation *op,
597                        LDAPMessage *msg, E2kGlobalCatalogEntry *entry)
598 {
599         char **values;
600         int i;
601
602         values = ldap_get_values (gc->priv->ldap, msg, "publicDelegates");
603         if (values) {
604                 E2K_GC_DEBUG_MSG(("GC: publicDelegates\n"));
605                 entry->delegates = g_ptr_array_new ();
606                 for (i = 0; values[i]; i++) {
607                         E2K_GC_DEBUG_MSG(("GC:   %s\n", values[i]));
608                         g_ptr_array_add (entry->delegates,
609                                          g_strdup (values[i]));
610                 }
611                 entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_DELEGATES;
612                 ldap_value_free (values);
613         }
614         values = ldap_get_values (gc->priv->ldap, msg, "publicDelegatesBL");
615         if (values) {
616                 E2K_GC_DEBUG_MSG(("GC: publicDelegatesBL\n"));
617                 entry->delegators = g_ptr_array_new ();
618                 for (i = 0; values[i]; i++) {
619                         E2K_GC_DEBUG_MSG(("GC:   %s\n", values[i]));
620                         g_ptr_array_add (entry->delegators,
621                                          g_strdup (values[i]));
622                 }
623                 entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_DELEGATORS;
624                 ldap_value_free (values);
625         }
626 }
627
628 static void
629 get_quota_values (E2kGlobalCatalog *gc, E2kOperation *op,
630                   LDAPMessage *msg, E2kGlobalCatalogEntry *entry)
631 {
632         char **quota_setting_values, **quota_limit_values;
633
634         /* Check if mailbox store default values are used */
635         quota_setting_values = ldap_get_values (gc->priv->ldap, msg, "mDBUseDefaults");
636         if (!quota_setting_values) {
637                 entry->quota_warn = entry->quota_nosend = entry->quota_norecv = 0;
638                 return;
639         }
640
641         entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_QUOTA;
642         E2K_GC_DEBUG_MSG(("GC: mDBUseDefaults %s\n", quota_setting_values[0]));
643
644         if (!strcmp (quota_setting_values[0], "TRUE")) {
645                 /* use global mailbox store settings */
646                 E2K_GC_DEBUG_MSG(("GC: Using global mailbox store limits\n"));
647         }
648         ldap_value_free (quota_setting_values);
649         
650         quota_limit_values = ldap_get_values (gc->priv->ldap, msg, "mDBStorageQuota");
651         if (quota_limit_values) {
652                 entry->quota_warn = atoi(quota_limit_values[0]);
653                 E2K_GC_DEBUG_MSG(("GC: mDBStorageQuota %s\n", quota_limit_values[0]));
654                 ldap_value_free (quota_limit_values);   
655         }
656
657         quota_limit_values = ldap_get_values (gc->priv->ldap, msg, "mDBOverQuotaLimit");
658         if (quota_limit_values) {
659                 entry->quota_nosend = atoi(quota_limit_values[0]);
660                 E2K_GC_DEBUG_MSG(("GC: mDBOverQuotaLimit %s\n", quota_limit_values[0]));
661                 ldap_value_free (quota_limit_values);   
662         }
663
664         quota_limit_values = ldap_get_values (gc->priv->ldap, msg, "mDBOverHardQuotaLimit");
665         if (quota_limit_values) {
666                 entry->quota_norecv = atoi(quota_limit_values[0]);
667                 E2K_GC_DEBUG_MSG(("GC: mDBHardQuotaLimit %s\n", quota_limit_values[0]));
668                 ldap_value_free (quota_limit_values);   
669         }
670 }
671
672 static void
673 get_account_control_values (E2kGlobalCatalog *gc, E2kOperation *op, 
674                             LDAPMessage *msg, E2kGlobalCatalogEntry *entry)
675 {
676         char **values;
677
678         values = ldap_get_values (gc->priv->ldap, msg, "userAccountControl");
679         if (values) {
680                 entry->user_account_control = atoi(values[0]);
681                 E2K_GC_DEBUG_MSG(("GC: userAccountControl %s\n", values[0]));
682                 entry->mask |= E2K_GLOBAL_CATALOG_LOOKUP_ACCOUNT_CONTROL;
683                 ldap_value_free (values);
684         }
685         
686 }
687
688 /**
689  * e2k_global_catalog_lookup:
690  * @gc: the global catalog
691  * @op: pointer to an #E2kOperation to use for cancellation
692  * @type: the type of information in @key
693  * @key: email address or DN to look up
694  * @flags: the information to look up
695  * @entry_p: pointer to a variable to return the entry in.
696  *
697  * Look up the indicated user in the global catalog and
698  * return their information in *@entry_p.
699  *
700  * Return value: the status of the lookup
701  **/
702 E2kGlobalCatalogStatus
703 e2k_global_catalog_lookup (E2kGlobalCatalog *gc,
704                            E2kOperation *op,
705                            E2kGlobalCatalogLookupType type,
706                            const char *key,
707                            E2kGlobalCatalogLookupFlags flags,
708                            E2kGlobalCatalogEntry **entry_p)
709 {
710         E2kGlobalCatalogEntry *entry;
711         GPtrArray *attrs;
712         E2kGlobalCatalogLookupFlags lookup_flags, need_flags = 0;
713         const char *base = NULL;
714         char *filter = NULL, *dn;
715         int scope = LDAP_SCOPE_BASE, ldap_error;
716         E2kGlobalCatalogStatus status;
717         LDAPMessage *msg, *resp;
718
719         g_return_val_if_fail (E2K_IS_GLOBAL_CATALOG (gc), E2K_GLOBAL_CATALOG_ERROR);
720         g_return_val_if_fail (key != NULL, E2K_GLOBAL_CATALOG_ERROR);
721
722         g_mutex_lock (gc->priv->ldap_lock);
723
724         entry = g_hash_table_lookup (gc->priv->entry_cache, key);
725         if (!entry)
726                 entry = g_new0 (E2kGlobalCatalogEntry, 1);
727
728         attrs = g_ptr_array_new ();
729
730         if (!entry->display_name)
731                 g_ptr_array_add (attrs, "displayName");
732         if (!entry->email) {
733                 g_ptr_array_add (attrs, "mail");
734                 if (flags & E2K_GLOBAL_CATALOG_LOOKUP_EMAIL)
735                         need_flags |= E2K_GLOBAL_CATALOG_LOOKUP_EMAIL;
736         }
737         if (!entry->legacy_exchange_dn) {
738                 g_ptr_array_add (attrs, "legacyExchangeDN");
739                 if (flags & E2K_GLOBAL_CATALOG_LOOKUP_LEGACY_EXCHANGE_DN)
740                         need_flags |= E2K_GLOBAL_CATALOG_LOOKUP_LEGACY_EXCHANGE_DN;
741         }
742
743         lookup_flags = flags & ~entry->mask;
744
745         if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_SID) {
746                 g_ptr_array_add (attrs, "objectSid");
747                 g_ptr_array_add (attrs, "objectCategory");
748                 need_flags |= E2K_GLOBAL_CATALOG_LOOKUP_SID;
749         }
750         if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX) {
751                 g_ptr_array_add (attrs, "mailNickname");
752                 g_ptr_array_add (attrs, "homeMTA");
753                 need_flags |= E2K_GLOBAL_CATALOG_LOOKUP_MAILBOX;
754         }
755         if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_DELEGATES)
756                 g_ptr_array_add (attrs, "publicDelegates");
757         if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_DELEGATORS)
758                 g_ptr_array_add (attrs, "publicDelegatesBL");
759         if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_QUOTA) {
760                 g_ptr_array_add (attrs, "mDBUseDefaults");
761                 g_ptr_array_add (attrs, "mDBStorageQuota");
762                 g_ptr_array_add (attrs, "mDBOverQuotaLimit");
763                 g_ptr_array_add (attrs, "mDBOverHardQuotaLimit");
764         }
765         if (lookup_flags & E2K_GLOBAL_CATALOG_LOOKUP_ACCOUNT_CONTROL)
766                 g_ptr_array_add (attrs, "userAccountControl");
767
768         if (attrs->len == 0) {
769                 E2K_GC_DEBUG_MSG(("\nGC: returning cached info for %s\n", key));
770                 goto lookedup;
771         }
772
773         E2K_GC_DEBUG_MSG(("\nGC: looking up info for %s\n", key));
774         g_ptr_array_add (attrs, NULL);
775
776         switch (type) {
777         case E2K_GLOBAL_CATALOG_LOOKUP_BY_EMAIL:
778                 filter = g_strdup_printf ("(mail=%s)", key);
779                 base = LDAP_ROOT_DSE;
780                 scope = LDAP_SCOPE_SUBTREE;
781                 break;
782
783         case E2K_GLOBAL_CATALOG_LOOKUP_BY_DN:
784                 filter = NULL;
785                 base = key;
786                 scope = LDAP_SCOPE_BASE;
787                 break;
788
789         case E2K_GLOBAL_CATALOG_LOOKUP_BY_LEGACY_EXCHANGE_DN:
790                 filter = g_strdup_printf ("(legacyExchangeDN=%s)", key);
791                 base = LDAP_ROOT_DSE;
792                 scope = LDAP_SCOPE_SUBTREE;
793                 break;
794         }
795
796         ldap_error = gc_search (gc, op, base, scope, filter,
797                                 (const char **)attrs->pdata, &msg);
798         if (ldap_error == LDAP_USER_CANCELLED) {
799                 E2K_GC_DEBUG_MSG(("GC: ldap_search cancelled"));
800                 status = E2K_GLOBAL_CATALOG_CANCELLED;
801                 goto done;
802         } else if (ldap_error == LDAP_INVALID_CREDENTIALS) {
803                 E2K_GC_DEBUG_MSG(("GC: ldap_search auth failed"));
804                 status = E2K_GLOBAL_CATALOG_AUTH_FAILED;
805                 goto done;
806         } else if (ldap_error != LDAP_SUCCESS) {
807                 E2K_GC_DEBUG_MSG(("GC: ldap_search failed: 0x%02x\n\n", ldap_error));
808                 status = E2K_GLOBAL_CATALOG_ERROR;
809                 goto done;
810         }
811
812         resp = ldap_first_entry (gc->priv->ldap, msg);
813         if (!resp) {
814                 E2K_GC_DEBUG_MSG(("GC: no such user\n\n"));
815                 status = E2K_GLOBAL_CATALOG_NO_SUCH_USER;
816                 ldap_msgfree (msg);
817                 goto done;
818         }
819
820         if (!entry->dn) {
821                 dn = ldap_get_dn (gc->priv->ldap, resp);
822                 entry->dn = g_strdup (dn);
823                 E2K_GC_DEBUG_MSG(("GC: dn = %s\n\n", dn));
824                 ldap_memfree (dn);
825                 g_ptr_array_add (gc->priv->entries, entry);
826                 g_hash_table_insert (gc->priv->entry_cache,
827                                      entry->dn, entry);
828         }
829
830         get_sid_values (gc, op, resp, entry);
831         get_mail_values (gc, op, resp, entry);
832         get_delegation_values (gc, op, resp, entry);
833         get_quota_values (gc, op, resp, entry);
834         get_account_control_values (gc, op, resp, entry);
835         ldap_msgfree (msg);
836
837  lookedup:
838         if (need_flags & ~entry->mask) {
839                 E2K_GC_DEBUG_MSG(("GC: no data\n\n"));
840                 status = E2K_GLOBAL_CATALOG_NO_DATA;
841         } else {
842                 E2K_GC_DEBUG_MSG(("\n"));
843                 status = E2K_GLOBAL_CATALOG_OK;
844                 entry->mask |= lookup_flags;
845                 *entry_p = entry;
846         }
847
848  done:
849         g_free (filter);
850         g_ptr_array_free (attrs, TRUE);
851
852         if (status != E2K_GLOBAL_CATALOG_OK && !entry->dn)
853                 g_free (entry);
854
855         g_mutex_unlock (gc->priv->ldap_lock);
856         return status;
857 }
858
859
860 struct async_lookup_data {
861         E2kGlobalCatalog *gc;
862         E2kOperation *op;
863         E2kGlobalCatalogLookupType type;
864         char *key;
865         E2kGlobalCatalogLookupFlags flags;
866         E2kGlobalCatalogCallback callback;
867         gpointer user_data;
868
869         E2kGlobalCatalogEntry *entry;
870         E2kGlobalCatalogStatus status;
871 };
872
873 static gboolean
874 idle_lookup_result (gpointer user_data)
875 {
876         struct async_lookup_data *ald = user_data;
877
878         ald->callback (ald->gc, ald->status, ald->entry, ald->user_data);
879         g_object_unref (ald->gc);
880         g_free (ald->key);
881         g_free (ald);
882         return FALSE;
883 }
884
885 static void *
886 do_lookup_thread (void *user_data)
887 {
888         struct async_lookup_data *ald = user_data;
889
890         ald->status = e2k_global_catalog_lookup (ald->gc, ald->op, ald->type,
891                                                  ald->key, ald->flags,
892                                                  &ald->entry);
893         g_idle_add (idle_lookup_result, ald);
894         return NULL;
895 }
896
897 /**
898  * e2k_global_catalog_async_lookup:
899  * @gc: the global catalog
900  * @op: pointer to an #E2kOperation to use for cancellation
901  * @type: the type of information in @key
902  * @key: email address or DN to look up
903  * @flags: the information to look up
904  * @callback: the callback to invoke after finding the user
905  * @user_data: data to pass to callback
906  *
907  * Asynchronously look up the indicated user in the global catalog and
908  * return the requested information to the callback.
909  **/
910 void
911 e2k_global_catalog_async_lookup (E2kGlobalCatalog *gc,
912                                  E2kOperation *op,
913                                  E2kGlobalCatalogLookupType type,
914                                  const char *key,
915                                  E2kGlobalCatalogLookupFlags flags,
916                                  E2kGlobalCatalogCallback callback,
917                                  gpointer user_data)
918 {
919         struct async_lookup_data *ald;
920         pthread_t pth;
921
922         ald = g_new0 (struct async_lookup_data, 1);
923         ald->gc = g_object_ref (gc);
924         ald->op = op;
925         ald->type = type;
926         ald->key = g_strdup (key);
927         ald->flags = flags;
928         ald->callback = callback;
929         ald->user_data = user_data;
930
931         if (pthread_create (&pth, NULL, do_lookup_thread, ald) == -1) {
932                 g_warning ("Could not create lookup thread\n");
933                 ald->status = E2K_GLOBAL_CATALOG_ERROR;
934                 g_idle_add (idle_lookup_result, ald);
935         }
936 }
937
938 static const char *
939 lookup_controlling_ad_server (E2kGlobalCatalog *gc, E2kOperation *op,
940                               const char *dn)
941 {
942         char *hostname, **values, *ad_dn;
943         const char *attrs[2];
944         LDAPMessage *resp;
945         int ldap_error;
946
947         while (g_ascii_strncasecmp (dn, "DC=", 3) != 0) {
948                 dn = strchr (dn, ',');
949                 if (!dn)
950                         return NULL;
951                 dn++;
952         }
953
954         hostname = g_hash_table_lookup (gc->priv->server_cache, dn);
955         if (hostname)
956                 return hostname;
957
958         E2K_GC_DEBUG_MSG(("GC:   Finding AD server for %s\n", dn));
959
960         attrs[0] = "masteredBy";
961         attrs[1] = NULL;
962
963         ldap_error = gc_search (gc, op, dn, LDAP_SCOPE_BASE, NULL, attrs, &resp);
964         if (ldap_error != LDAP_SUCCESS) {
965                 E2K_GC_DEBUG_MSG(("GC:   ldap_search failed: 0x%02x\n", ldap_error));
966                 return NULL;
967         }
968
969         values = ldap_get_values (gc->priv->ldap, resp, "masteredBy");
970         ldap_msgfree (resp);
971         if (!values) {
972                 E2K_GC_DEBUG_MSG(("GC:   no known AD server\n\n"));
973                 return NULL;
974         }
975
976         /* Skip over "CN=NTDS Settings," */
977         ad_dn = strchr (values[0], ',');
978         if (!ad_dn) {
979                 E2K_GC_DEBUG_MSG(("GC:   bad dn %s\n\n", values[0]));
980                 ldap_value_free (values);
981                 return NULL;
982         }
983         ad_dn++;
984
985         attrs[0] = "dNSHostName";
986         attrs[1] = NULL;
987
988         ldap_error = gc_search (gc, op, ad_dn, LDAP_SCOPE_BASE, NULL, attrs, &resp);
989         ldap_value_free (values);
990
991         if (ldap_error != LDAP_SUCCESS) {
992                 E2K_GC_DEBUG_MSG(("GC:   ldap_search failed: 0x%02x\n\n", ldap_error));
993                 return NULL;
994         }
995
996         values = ldap_get_values (gc->priv->ldap, resp, "dNSHostName");
997         ldap_msgfree (resp);
998         if (!values) {
999                 E2K_GC_DEBUG_MSG(("GC:   entry has no dNSHostName\n\n"));
1000                 return NULL;
1001         }
1002
1003         hostname = g_strdup (values[0]);
1004         ldap_value_free (values);
1005
1006         g_hash_table_insert (gc->priv->server_cache, g_strdup (dn), hostname);
1007
1008         E2K_GC_DEBUG_MSG(("GC:   %s\n", hostname));
1009         return hostname;
1010 }
1011
1012 static gchar *
1013 find_domain_dn (char *domain)
1014 {
1015         GString *dn_value = g_string_new (NULL);
1016         gchar *dn;
1017         char  *sub_domain=NULL;
1018
1019         sub_domain = strtok (domain, ".");
1020         while (sub_domain != NULL) {
1021                 g_string_append (dn_value, "DC=");
1022                 g_string_append (dn_value, sub_domain);
1023                 g_string_append (dn_value, ",");
1024                 sub_domain = strtok (NULL, ".");
1025         }
1026         if (dn_value->str[0])
1027                 dn = g_strndup (dn_value->str, strlen(dn_value->str) - 1);
1028         else 
1029                 dn = NULL;
1030         g_string_free (dn_value, TRUE);
1031         return dn;
1032 }
1033
1034 double 
1035 lookup_passwd_max_age (E2kGlobalCatalog *gc, E2kOperation *op)
1036 {
1037         char **values = NULL, *filter = NULL, *val=NULL;
1038         const char *attrs[2];
1039         LDAP *ldap;
1040         LDAPMessage *msg=NULL;
1041         int ldap_error, msgid;
1042         double maxAge=0;
1043         gchar *dn=NULL;
1044         
1045         attrs[0] = "maxPwdAge";
1046         attrs[1] = NULL;
1047
1048         filter = g_strdup("objectClass=domainDNS");
1049
1050         dn = find_domain_dn (gc->domain);
1051
1052         ldap_error = get_ldap_connection (gc, op, gc->priv->server, LDAP_PORT, &ldap);
1053         if (ldap_error != LDAP_SUCCESS) {
1054                 E2K_GC_DEBUG_MSG(("GC: Establishing ldap connection failed : 0x%02x\n\n", 
1055                                                                         ldap_error));
1056                 return -1; 
1057         }
1058
1059         ldap_error = ldap_search_ext (ldap, dn, LDAP_SCOPE_BASE, filter, (char **)attrs, 
1060                                       FALSE, NULL, NULL, NULL, 0, &msgid);
1061         if (!ldap_error) {
1062                 ldap_error = gc_ldap_result (ldap, op, msgid, &msg);
1063                 if (ldap_error) {
1064                         E2K_GC_DEBUG_MSG(("GC: ldap_result failed: 0x%02x\n\n", ldap_error));
1065                         return -1;
1066                 }
1067         }
1068         else {
1069                 E2K_GC_DEBUG_MSG(("GC: ldap_search failed:0x%02x \n\n", ldap_error));
1070                 return -1;
1071         }
1072
1073         values = ldap_get_values (ldap, msg, "maxPwdAge");
1074         if (!values) {
1075                 E2K_GC_DEBUG_MSG(("GC: couldn't retrieve maxPwdAge\n")); 
1076                 return -1;
1077         }
1078
1079         if (values[0]) {
1080                 val = values[0];
1081                 if (*val == '-')
1082                         ++val; 
1083                 maxAge = strtod (val, NULL);
1084         }
1085
1086         //g_hash_table_insert (gc->priv->server_cache, g_strdup (dn), hostname); FIXME?
1087
1088         E2K_GC_DEBUG_MSG(("GC:   maxPwdAge = %f\n", maxAge));
1089
1090         if (msg)
1091                 ldap_msgfree (msg);
1092         if (values)
1093                 ldap_value_free (values);
1094         ldap_unbind (ldap);
1095         g_free (filter);
1096         g_free (dn);
1097         return maxAge;
1098 }
1099
1100 static E2kGlobalCatalogStatus
1101 do_delegate_op (E2kGlobalCatalog *gc, E2kOperation *op, int deleg_op,
1102                 const char *self_dn, const char *delegate_dn)
1103 {
1104         LDAP *ldap;
1105         LDAPMod *mods[2], mod;
1106         const char *ad_server;
1107         char *values[2];
1108         int ldap_error, msgid;
1109
1110         g_return_val_if_fail (E2K_IS_GLOBAL_CATALOG (gc), E2K_GLOBAL_CATALOG_ERROR);
1111         g_return_val_if_fail (self_dn != NULL, E2K_GLOBAL_CATALOG_ERROR);
1112         g_return_val_if_fail (delegate_dn != NULL, E2K_GLOBAL_CATALOG_ERROR);
1113
1114         ad_server = lookup_controlling_ad_server (gc, op, self_dn);
1115         if (!ad_server) {
1116                 if (e2k_operation_is_cancelled (op))
1117                         return E2K_GLOBAL_CATALOG_CANCELLED;
1118                 else
1119                         return E2K_GLOBAL_CATALOG_ERROR;
1120         }
1121
1122         ldap_error = get_ldap_connection (gc, op, ad_server, LDAP_PORT, &ldap);
1123         if (ldap_error == LDAP_USER_CANCELLED)
1124                 return E2K_GLOBAL_CATALOG_CANCELLED;
1125         else if (ldap_error != LDAP_SUCCESS)
1126                 return E2K_GLOBAL_CATALOG_ERROR;
1127
1128         mod.mod_op = deleg_op;
1129         mod.mod_type = "publicDelegates";
1130         mod.mod_values = values;
1131         values[0] = (char *)delegate_dn;
1132         values[1] = NULL;
1133
1134         mods[0] = &mod;
1135         mods[1] = NULL;
1136
1137         ldap_error = ldap_modify_ext (ldap, self_dn, mods, NULL, NULL, &msgid);
1138         if (ldap_error == LDAP_SUCCESS) {
1139                 LDAPMessage *msg;
1140
1141                 ldap_error = gc_ldap_result (ldap, op, msgid, &msg);
1142                 if (ldap_error == LDAP_SUCCESS) {
1143                         ldap_parse_result (ldap, msg, &ldap_error, NULL, NULL,
1144                                            NULL, NULL, TRUE);
1145                 }
1146         }
1147         ldap_unbind (ldap);
1148
1149         switch (ldap_error) {
1150         case LDAP_SUCCESS:
1151                 E2K_GC_DEBUG_MSG(("\n"));
1152                 return E2K_GLOBAL_CATALOG_OK;
1153
1154         case LDAP_NO_SUCH_OBJECT:
1155                 E2K_GC_DEBUG_MSG(("GC: no such user\n\n"));
1156                 return E2K_GLOBAL_CATALOG_NO_SUCH_USER;
1157
1158         case LDAP_NO_SUCH_ATTRIBUTE:
1159                 E2K_GC_DEBUG_MSG(("GC: no such delegate\n\n"));
1160                 return E2K_GLOBAL_CATALOG_NO_DATA;
1161
1162         case LDAP_CONSTRAINT_VIOLATION:
1163                 E2K_GC_DEBUG_MSG(("GC: bad delegate\n\n"));
1164                 return E2K_GLOBAL_CATALOG_BAD_DATA;
1165
1166         case LDAP_TYPE_OR_VALUE_EXISTS:
1167                 E2K_GC_DEBUG_MSG(("GC: delegate already exists\n\n"));
1168                 return E2K_GLOBAL_CATALOG_EXISTS;
1169
1170         case LDAP_USER_CANCELLED:
1171                 E2K_GC_DEBUG_MSG(("GC: cancelled\n\n"));
1172                 return E2K_GLOBAL_CATALOG_CANCELLED;
1173
1174         default:
1175                 E2K_GC_DEBUG_MSG(("GC: ldap_modify failed: 0x%02x\n\n", ldap_error));
1176                 return E2K_GLOBAL_CATALOG_ERROR;
1177         }
1178 }
1179
1180 /**
1181  * e2k_global_catalog_add_delegate:
1182  * @gc: the global catalog
1183  * @op: pointer to an #E2kOperation to use for cancellation
1184  * @self_dn: Active Directory DN of the user to add a delegate to
1185  * @delegate_dn: Active Directory DN of the new delegate
1186  *
1187  * Attempts to make @delegate_dn a delegate of @self_dn.
1188  *
1189  * Return value: %E2K_GLOBAL_CATALOG_OK on success,
1190  * %E2K_GLOBAL_CATALOG_NO_SUCH_USER if @self_dn is invalid,
1191  * %E2K_GLOBAL_CATALOG_BAD_DATA if @delegate_dn is invalid,
1192  * %E2K_GLOBAL_CATALOG_EXISTS if @delegate_dn is already a delegate,
1193  * %E2K_GLOBAL_CATALOG_ERROR on other errors.
1194  **/
1195 E2kGlobalCatalogStatus
1196 e2k_global_catalog_add_delegate (E2kGlobalCatalog *gc,
1197                                  E2kOperation *op,
1198                                  const char *self_dn,
1199                                  const char *delegate_dn)
1200 {
1201         E2K_GC_DEBUG_MSG(("\nGC: adding %s as delegate for %s\n", delegate_dn, self_dn));
1202
1203         return do_delegate_op (gc, op, LDAP_MOD_ADD, self_dn, delegate_dn);
1204 }
1205
1206 /**
1207  * e2k_global_catalog_remove_delegate:
1208  * @gc: the global catalog
1209  * @op: pointer to an #E2kOperation to use for cancellation
1210  * @self_dn: Active Directory DN of the user to remove a delegate from
1211  * @delegate_dn: Active Directory DN of the delegate to remove
1212  *
1213  * Attempts to remove @delegate_dn as a delegate of @self_dn.
1214  *
1215  * Return value: %E2K_GLOBAL_CATALOG_OK on success,
1216  * %E2K_GLOBAL_CATALOG_NO_SUCH_USER if @self_dn is invalid,
1217  * %E2K_GLOBAL_CATALOG_NO_DATA if @delegate_dn is not a delegate of @self_dn,
1218  * %E2K_GLOBAL_CATALOG_ERROR on other errors.
1219  **/
1220 E2kGlobalCatalogStatus
1221 e2k_global_catalog_remove_delegate (E2kGlobalCatalog *gc,
1222                                     E2kOperation *op,
1223                                     const char *self_dn,
1224                                     const char *delegate_dn)
1225 {
1226         E2K_GC_DEBUG_MSG(("\nGC: removing %s as delegate for %s\n", delegate_dn, self_dn));
1227
1228         return do_delegate_op (gc, op, LDAP_MOD_DELETE, self_dn, delegate_dn);
1229 }