1/* SOGoLDAPUserDefaults.m - this file is part of SOGo
2 *
3 * Copyright (C) 2008 Inverse inc.
4 *
5 * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
6 *
7 * This file is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
11 *
12 * This file is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; see the file COPYING.  If not, write to
19 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 */
22
23#include <stdio.h>
24#include <stdlib.h>
25#include <ldap.h>
26
27#import <Foundation/NSArray.h>
28#import <Foundation/NSDictionary.h>
29#import <Foundation/NSString.h>
30
31#import <NGExtensions/NSObject+Logs.h>
32
33#import "SOGoLDAPUserDefaults.h"
34
35#define SOGoLDAPDescriptor @"/usr/local/etc/sogo.conf"
36#define SOGoLDAPContainerSize 8192
37#define SOGoLDAPAttrRefSeparator '/'
38
39typedef enum _SOGoLDAPValueType {
40  SOGoLDAPAtom,
41  SOGoLDAPArray,
42  SOGoLDAPDictionary,
43  SOGoLDAPLastType
44} _SOGoLDAPValueType;
45
46typedef struct _SOGoLDAPValue {
47  _SOGoLDAPValueType type;
48  void *value;
49  unsigned int maxCount;
50  char *key;
51} _SOGoLDAPValue;
52
53@implementation SOGoLDAPUserDefaults
54
55static _SOGoLDAPValue *
56_createAtom (_SOGoLDAPValueType type, void *value)
57{
58  _SOGoLDAPValue *newAtom;
59
60  newAtom = NSZoneMalloc (NULL, sizeof (_SOGoLDAPValue));
61  newAtom->type = type;
62  newAtom->value = value;
63  newAtom->maxCount = 0;
64  newAtom->key = NULL;
65
66  return newAtom;
67}
68
69static _SOGoLDAPValue *
70_createContainer (_SOGoLDAPValueType type)
71{
72  _SOGoLDAPValue *newContainer;
73  _SOGoLDAPValue **array;
74
75  array = NSZoneMalloc (NULL,
76                        sizeof (_SOGoLDAPValue *) * SOGoLDAPContainerSize);
77  *array = NULL;
78  newContainer = _createAtom (type, array);
79  newContainer->maxCount = SOGoLDAPContainerSize - 1; /* all values + NULL */
80
81  return newContainer;
82}
83
84static void
85_appendAtomToContainer (_SOGoLDAPValue *atom, _SOGoLDAPValue *container)
86{
87  unsigned int count;
88  _SOGoLDAPValue **atoms, **currentAtom;
89
90  atoms = (_SOGoLDAPValue **) container->value;
91  currentAtom = atoms;
92  while (*currentAtom)
93    currentAtom++;
94
95  count = (currentAtom - atoms);
96  if (count >= container->maxCount)
97    {
98      container->maxCount += SOGoLDAPContainerSize;
99      container->value = NSZoneRealloc (NULL, container->value,
100                                        (container->maxCount + 1)
101                                        * sizeof (_SOGoLDAPValue *));
102      currentAtom = (_SOGoLDAPValue **) container->value + count;
103    }
104
105  *currentAtom = atom;
106  *(currentAtom + 1) = NULL;
107}
108
109static _SOGoLDAPValue **
110_findAtomInDictionary (const char *key, const _SOGoLDAPValue *dictionary)
111{
112  _SOGoLDAPValue **atom, **value;
113
114  atom = NULL;
115
116  value = dictionary->value;
117  if (value)
118    {
119      while (!atom && *value)
120	if (strcmp ((*value)->key, key) == 0)
121	  atom = value;
122	else
123	  value++;
124    }
125
126  return atom;
127}
128
129static void
130_appendAtomToDictionary (_SOGoLDAPValue *atom, _SOGoLDAPValue *dictionary)
131{
132  _SOGoLDAPValue **oldAtomPtr, *oldAtom, *container;
133
134  oldAtomPtr = _findAtomInDictionary (atom->key, dictionary);
135  if (oldAtomPtr)
136    {
137      oldAtom = *oldAtomPtr;
138      if (oldAtom->type == SOGoLDAPArray)
139	container = oldAtom;
140      else
141	{
142	  container = _createContainer (SOGoLDAPArray);
143	  container->key = oldAtom->key;
144	  oldAtom->key = NULL;
145	  _appendAtomToContainer (oldAtom, container);
146	  *oldAtomPtr = container;
147	}
148    }
149  else
150    container = dictionary;
151
152  if (container->type == SOGoLDAPArray)
153    {
154      NSZoneFree (NULL, atom->key);
155      atom->key = NULL;
156    }
157  _appendAtomToContainer (atom, container);
158}
159
160static _SOGoLDAPValue *_readLDAPAttrRefWithHandle (const char *attrPair,
161						   LDAP *ldapHandle);
162
163static _SOGoLDAPValue **
164_fetchLDAPDNAndAttrWithHandle (const char *dn, char *attrs[], LDAP *ldapHandle)
165{
166  struct timeval timeout;
167  int rc;
168  LDAPMessage *messages, *message;
169  BerElement *element;
170  BerValue **values, **value;
171  const char *attribute;
172  _SOGoLDAPValue **atoms, **currentAtom;
173  unsigned int atomsCount;
174
175  atoms = NSZoneMalloc (NULL,
176                        SOGoLDAPContainerSize * sizeof (_SOGoLDAPValue *));
177  currentAtom = atoms;
178  atomsCount = 0;
179
180  timeout.tv_sec = 30;
181  timeout.tv_usec = 0;
182
183  rc = ldap_search_ext_s (ldapHandle, dn, LDAP_SCOPE_BASE, "(objectClass=*)",
184			  attrs, 0, NULL, NULL, &timeout, 0, &messages);
185  if (rc == LDAP_SUCCESS)
186    {
187      message = ldap_first_entry (ldapHandle, messages);
188      if (message)
189	{
190	  attribute = ldap_first_attribute (ldapHandle, message, &element);
191	  while (attribute)
192	    {
193	      values = ldap_get_values_len (ldapHandle, message, attribute);
194	      value = values;
195	      while (*value)
196		{
197		  if (strncmp ((*value)->bv_val, "ref-dn:", 7) == 0)
198		    *currentAtom
199		      = _readLDAPAttrRefWithHandle (((*value)->bv_val + 7),
200						    ldapHandle);
201		  else
202		    *currentAtom = _createAtom (SOGoLDAPAtom,
203						strdup ((*value)->bv_val));
204		  (*currentAtom)->key = strdup (attribute);
205		  currentAtom++;
206		  atomsCount++;
207
208		  if (!(atomsCount % SOGoLDAPContainerSize))
209		    {
210		      atoms = NSZoneRealloc (NULL, atoms,
211                                             (int) (SOGoLDAPContainerSize
212                                                    + atomsCount)
213                                             * sizeof (_SOGoLDAPValue *));
214		      currentAtom = atoms + atomsCount;
215		    }
216		  value++;
217		}
218	      ldap_value_free_len (values);
219	      attribute = ldap_next_attribute (ldapHandle, message, element);
220	    }
221	}
222      ldap_msgfree (message);
223    }
224
225  *currentAtom = NULL;
226
227  return atoms;
228}
229
230static void
231_fillLDAPDictionaryWithAtoms (_SOGoLDAPValue *container,
232			     _SOGoLDAPValue *atoms[])
233{
234  _SOGoLDAPValue **currentAtom;
235
236  currentAtom = atoms;
237  while (*currentAtom)
238    {
239      _appendAtomToDictionary (*currentAtom, container);
240      currentAtom++;
241    }
242}
243
244static _SOGoLDAPValue *
245_readLDAPAttrRefWithHandle (const char *attrPair, LDAP *ldapHandle)
246{
247  char *dn, *separator;
248  _SOGoLDAPValue *refValue;
249  _SOGoLDAPValue **atoms;
250  char *attrs[2];
251
252  dn = strdup (attrPair);
253  separator = strrchr (dn, SOGoLDAPAttrRefSeparator);
254  if (separator)
255    {
256      *separator = 0;
257      attrs[0] = strdup (separator + 1);
258      attrs[1] = NULL;
259      atoms = _fetchLDAPDNAndAttrWithHandle (dn, attrs, ldapHandle);
260      refValue = *atoms;
261      NSZoneFree (NULL, attrs[0]);
262    }
263  else
264    {
265      refValue = _createContainer (SOGoLDAPDictionary);
266      atoms = _fetchLDAPDNAndAttrWithHandle (dn, NULL, ldapHandle);
267      _fillLDAPDictionaryWithAtoms (refValue, atoms);
268    }
269  NSZoneFree (NULL, atoms);
270  NSZoneFree (NULL, dn);
271
272  return refValue;
273}
274
275static NSString *_convertLDAPAtomToNSString (_SOGoLDAPValue *atom);
276static NSArray *_convertLDAPAtomToNSArray (_SOGoLDAPValue *atom);
277static NSDictionary *_convertLDAPAtomToNSDictionary (_SOGoLDAPValue *atom);
278
279static id
280_convertLDAPAtomToNSObject (_SOGoLDAPValue *atom)
281{
282  id ldapObject;
283
284  if (atom->type == SOGoLDAPAtom)
285    ldapObject = _convertLDAPAtomToNSString (atom);
286  else if (atom->type == SOGoLDAPArray)
287    ldapObject = _convertLDAPAtomToNSArray (atom);
288  else
289    ldapObject = _convertLDAPAtomToNSDictionary (atom);
290
291  return ldapObject;
292}
293
294static NSString *
295_convertLDAPAtomToNSString (_SOGoLDAPValue *atom)
296{
297  NSString *ldapObject;
298
299  ldapObject = [[NSString alloc]
300		 initWithBytes: atom->value
301		 length: strlen (atom->value)
302		 encoding: NSUTF8StringEncoding];
303  [ldapObject autorelease];
304
305  return ldapObject;
306}
307
308static NSArray *
309_convertLDAPAtomToNSArray (_SOGoLDAPValue *atom)
310{
311  _SOGoLDAPValue **currentSubAtom;
312  NSMutableArray *ldapObject;
313
314  ldapObject = [NSMutableArray array];
315
316  currentSubAtom = atom->value;
317  while (*currentSubAtom)
318    {
319      [ldapObject addObject: _convertLDAPAtomToNSObject (*currentSubAtom)];
320      currentSubAtom++;
321    }
322
323  return ldapObject;
324}
325
326static NSDictionary *
327_convertLDAPAtomToNSDictionary (_SOGoLDAPValue *atom)
328{
329  _SOGoLDAPValue **currentSubAtom;
330  NSMutableDictionary *ldapObject;
331  NSString *atomKey;
332
333  ldapObject = [NSMutableDictionary dictionary];
334
335  currentSubAtom = atom->value;
336  while (*currentSubAtom)
337    {
338      atomKey = [[NSString alloc]
339		  initWithBytes: (*currentSubAtom)->key
340		  length: strlen ((*currentSubAtom)->key)
341		  encoding: NSUTF8StringEncoding];
342      [atomKey autorelease];
343      [ldapObject setObject: _convertLDAPAtomToNSObject (*currentSubAtom)
344		  forKey: atomKey];
345      currentSubAtom++;
346    }
347
348  return ldapObject;
349}
350
351// dn = "cn=admin,dc=inverse,dc=ca";
352// password = "qwerty";
353// uri = "ldap://127.0.0.1";
354// configDN = "cn=sogo-config,dc=inverse,dc=ca";
355
356static _SOGoLDAPValue *
357_initLDAPDefaults ()
358{
359#warning dn, password, uri, configDN should be defined
360  const char *dn, *password, *uri, *configDN;
361  LDAP *ldapHandle;
362  int rc, opt;
363  _SOGoLDAPValue *dictionary;
364  _SOGoLDAPValue **atoms;
365  BerValue cred;
366
367  ldap_initialize (&ldapHandle, uri);
368
369  opt = LDAP_VERSION3;
370  rc = ldap_set_option (ldapHandle, LDAP_OPT_PROTOCOL_VERSION, &opt);
371  rc = ldap_set_option (ldapHandle, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
372
373  dictionary = _createContainer (SOGoLDAPDictionary);
374
375  cred.bv_val = (char *) password;
376  cred.bv_len = strlen (password);
377
378  rc = ldap_sasl_bind_s (ldapHandle, dn, LDAP_SASL_SIMPLE, &cred,
379			 NULL, NULL, NULL);
380  if (rc == LDAP_SUCCESS)
381    {
382      atoms = _fetchLDAPDNAndAttrWithHandle (configDN, NULL, ldapHandle);
383      _fillLDAPDictionaryWithAtoms (dictionary, atoms);
384      NSZoneFree (NULL, atoms);
385    }
386
387  return dictionary;
388}
389
390- (id) objectForKey: (NSString *) key
391{
392  static _SOGoLDAPValue *SOGoLDAPDefaults = NULL;
393  _SOGoLDAPValue **atom;
394  id ldapObject;
395
396  if (!SOGoLDAPDefaults)
397    SOGoLDAPDefaults = _initLDAPDefaults ();
398
399  atom = _findAtomInDictionary ([key UTF8String], SOGoLDAPDefaults);
400  ldapObject = ((atom)
401		? _convertLDAPAtomToNSObject (*atom)
402		: NULL);
403
404  if (!ldapObject)
405    ldapObject = [super objectForKey: key];
406
407  return ldapObject;
408}
409
410@end
411