Handle FID/MID uniqueness BY MAILBOX rather than BY SERVER
[jelmer/openchange.git] / mapiproxy / libmapistore / mapistore_processing.c
1 /*
2    OpenChange Storage Abstraction Layer library
3
4    OpenChange Project
5
6    Copyright (C) Julien Kerihuel 2009-2011
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12    
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17    
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22
23 #include <sys/types.h>
24
25 #define __STDC_FORMAT_MACROS    1
26 #include <inttypes.h>
27
28 #include <dirent.h>
29 #include <fcntl.h>
30 #include <string.h>
31 #include <errno.h>
32
33 #include "mapistore_errors.h"
34 #include "mapistore.h"
35 #include "mapistore_common.h"
36 #include "mapistore_private.h"
37 #include <dlinklist.h>
38 #include "libmapi/libmapi_private.h"
39
40 #include <tdb.h>
41
42 char    *mapping_path = NULL;
43 char    *mapistore_dbpath = NULL;
44 char    *mapistore_firstorgdn = NULL;
45
46 /**
47    \details Set the mapping path
48
49    \param path pointer to the mapping path
50
51    \note The mapping path can be set unless id_mapping_context is
52    initialized. If path is NULL and mapping path is not yet
53    initialized, then mapping_path will be reset to its default value
54    when the initialization routine is called.
55
56    \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE error
57  */
58 _PUBLIC_ enum MAPISTORE_ERROR mapistore_set_mapping_path(const char *path)
59 {
60         TALLOC_CTX      *mem_ctx;
61         DIR             *dir;
62
63         /* Case 1. Path is set to NULL */
64         if (!path) {
65                 if (mapping_path) {
66                         talloc_free(mapping_path);
67                 }
68                 mapping_path = NULL;
69                 return MAPISTORE_SUCCESS;
70         }
71
72         if (mapping_path) {
73                 talloc_free(mapping_path);
74         }
75
76         /* Case 2. path is initialized */
77
78         /* Step 1. Check if path is valid path */
79         dir = opendir(path);
80         if (!dir) {
81                 return MAPISTORE_ERR_NO_DIRECTORY;
82         }
83
84         /* Step 2. TODO: Check for write permissions */
85
86         if (closedir(dir) == -1) {
87                 /* FIXME: Should have a better error name here */
88                 return MAPISTORE_ERR_NO_DIRECTORY;
89         }
90         
91         mem_ctx = talloc_autofree_context();
92         mapping_path = talloc_strdup(mem_ctx, path);
93         return MAPISTORE_SUCCESS;
94 }
95
96 /**
97    \details Return the current mapping path
98
99    \return pointer to the mapping path.
100  */
101 const char *mapistore_get_mapping_path(void)
102 {
103         return (!mapping_path) ? MAPISTORE_MAPPING_PATH : (const char *)mapping_path;
104 }
105
106
107 /**
108    \details Set the mapistore.ldb mapping path
109
110    \param dbname string pointer to the mapistore database path
111
112    \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE error
113  */
114 enum MAPISTORE_ERROR mapistore_set_database_path(const char *dbname)
115 {
116         TALLOC_CTX      *mem_ctx;
117
118         if (!dbname) {
119                 if (mapistore_dbpath) {
120                         talloc_free(mapistore_dbpath);
121                 }
122                 mapistore_dbpath = NULL;
123                 return MAPISTORE_SUCCESS;
124         }
125
126         if (mapistore_dbpath) {
127                 talloc_free(mapistore_dbpath);
128                 mapistore_dbpath = NULL;
129         }
130
131         mem_ctx = talloc_autofree_context();
132         mapistore_dbpath = talloc_strdup(mem_ctx, dbname);
133
134         return MAPISTORE_SUCCESS;
135 }
136
137
138 enum MAPISTORE_ERROR mapistore_set_firstorgdn(const char *firstou, const char *firstorg, const char *serverdn)
139 {
140         TALLOC_CTX      *mem_ctx;
141
142         if (mapistore_firstorgdn) {
143                 talloc_free(mapistore_firstorgdn);
144         }
145
146         mem_ctx = talloc_autofree_context();
147         mapistore_firstorgdn = talloc_asprintf(mem_ctx, TMPL_MDB_FIRSTORGDN, firstou, firstorg, serverdn);
148         if (!mapistore_firstorgdn) {
149                 DEBUG(5, ("! [%s:%d][%s]: Unable to allocate memory to set firstorgdn\n", 
150                           __FILE__, __LINE__, __FUNCTION__));
151                 return MAPISTORE_ERR_NO_MEMORY;
152         }
153
154         return MAPISTORE_SUCCESS;
155 }
156
157 const char *mapistore_get_firstorgdn(void)
158 {
159         return mapistore_firstorgdn;
160 }
161
162 /**
163    \details Return the current path to mapistore.ldb database
164
165    \return pointer to the mapistore database path
166  */
167 const char *mapistore_get_database_path(void)
168 {
169         return (!mapistore_dbpath) ? MAPISTORE_DBPATH : (const char *) mapistore_dbpath;
170 }
171
172
173
174
175
176 /**
177    \details Initialize the ID mapping context or return the existing
178    one if already initialized.
179
180    \param pctx pointer to the processing context
181
182    \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE error
183  */
184 enum MAPISTORE_ERROR mapistore_init_mapping_context(struct processing_context *pctx)
185 {
186         TDB_DATA        key;
187         TDB_DATA        dbuf;
188         TALLOC_CTX      *mem_ctx;
189         char            *dbpath;
190         uint64_t        last_id;
191         char            *tmp_buf;
192         int             ret;
193
194         /* mapistore_v2 */
195         const char      *db_path;
196
197         /* Sanity Checks */
198         MAPISTORE_RETVAL_IF(!pctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
199         MAPISTORE_RETVAL_IF(pctx->mapping_ctx, MAPISTORE_ERR_ALREADY_INITIALIZED, NULL);
200
201         pctx->mapping_ctx = talloc_zero(pctx, struct id_mapping_context);
202         MAPISTORE_RETVAL_IF(!pctx->mapping_ctx, MAPISTORE_ERR_NO_MEMORY, NULL);
203
204         mem_ctx = talloc_named(NULL, 0, "mapistore_init_mapping_context");
205
206         /* Step 1. Retrieve the mapistore database path */
207         db_path = mapistore_get_database_path();
208         if (!db_path) {
209                 DEBUG(5, ("! [%s:%d][%s]: Unable to retrieve the mapistore database path\n", __FILE__, __LINE__, __FUNCTION__));
210                 talloc_free(mem_ctx);
211                 talloc_free(pctx->mapping_ctx);
212                 return MAPISTORE_ERR_DATABASE_INIT;
213         }
214
215         /* Step 2. Initialize tevent structure */
216         pctx->mapping_ctx->ev = tevent_context_init(pctx);
217
218         /* Step 3. Open a wrapped connection to mapistore.ldb */
219         pctx->mapping_ctx->ldb_ctx = mapistore_ldb_wrap_connect(pctx, pctx->mapping_ctx->ev, db_path, 0);
220         if (!pctx->mapping_ctx->ldb_ctx) {
221                 DEBUG(5, ("! [%s:%d][%s]: Failed to open wrapped connection over mapistore.ldb\n", __FILE__, __LINE__, __FUNCTION__));
222                 talloc_free(pctx->mapping_ctx);
223                 return MAPISTORE_ERR_DATABASE_INIT;
224         }
225
226         /* Open/Create the used ID database */
227         if (!pctx->mapping_ctx->used_ctx) {
228                 dbpath = talloc_asprintf(mem_ctx, "%s/%s", mapistore_get_mapping_path(), MAPISTORE_DB_NAME_USED_ID);
229                 pctx->mapping_ctx->used_ctx = tdb_wrap_open(pctx, dbpath, 0, 0, O_RDWR|O_CREAT, 0600);
230                 talloc_free(dbpath);
231                 if (!pctx->mapping_ctx->used_ctx) {
232                         DEBUG(3, ("[%s:%d]: %s\n", __FUNCTION__, __LINE__, strerror(errno)));
233                         talloc_free(mem_ctx);
234                         talloc_free(pctx->mapping_ctx);
235                         return MAPISTORE_ERR_DATABASE_INIT;
236                 }
237         }
238
239         /* Retrieve the last ID value */
240         key.dptr = (unsigned char *) MAPISTORE_DB_LAST_ID_KEY;
241         key.dsize = strlen(MAPISTORE_DB_LAST_ID_KEY);
242
243         dbuf = tdb_fetch(pctx->mapping_ctx->used_ctx->tdb, key);
244
245         /* If the record doesn't exist, insert it */
246         if (!dbuf.dptr || !dbuf.dsize) {
247                 dbuf.dptr = (unsigned char *) talloc_asprintf(mem_ctx, "0x%"PRIx64, (uint64_t) MAPISTORE_DB_LAST_ID_VAL);
248                 dbuf.dsize = strlen((const char *) dbuf.dptr);
249                 last_id = MAPISTORE_DB_LAST_ID_VAL;
250
251                 ret = tdb_store(pctx->mapping_ctx->used_ctx->tdb, key, dbuf, TDB_INSERT);
252                 talloc_free(dbuf.dptr);
253                 if (ret == -1) {
254                         DEBUG(3, ("[%s:%d]: Unable to create %s record: %s\n", __FUNCTION__, __LINE__,
255                                   MAPISTORE_DB_LAST_ID_KEY, tdb_errorstr(pctx->mapping_ctx->used_ctx->tdb)));
256                         talloc_free(mem_ctx);
257                         talloc_free(pctx->mapping_ctx);
258
259                         return MAPISTORE_ERR_DATABASE_OPS;
260                 }
261
262         } else {
263                 tmp_buf = talloc_strndup(mem_ctx, (char *)dbuf.dptr, dbuf.dsize);
264                 free(dbuf.dptr);
265                 last_id = strtoull(tmp_buf, NULL, 16);
266                 talloc_free(tmp_buf);
267         }
268
269         pctx->mapping_ctx->last_id = last_id;
270
271         talloc_free(mem_ctx);
272
273         return MAPISTORE_SUCCESS;
274 }
275
276
277 enum MAPISTORE_ERROR mapistore_write_ldif_string_to_store(struct processing_context *pctx, const char *ldif_string)
278 {
279         struct ldb_context      *ldb_ctx;
280         struct ldb_ldif         *ldif;
281         int                     ret;
282
283         /* Sanity checks */
284         MAPISTORE_RETVAL_IF(!pctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
285         MAPISTORE_RETVAL_IF(!pctx->mapping_ctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
286         MAPISTORE_RETVAL_IF(!pctx->mapping_ctx->ldb_ctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
287         MAPISTORE_RETVAL_IF(!ldif_string, MAPISTORE_ERR_INVALID_PARAMETER, NULL);
288
289         ldb_ctx = pctx->mapping_ctx->ldb_ctx;
290
291         while ((ldif = ldb_ldif_read_string(ldb_ctx, (const char **)&ldif_string))) {
292                 ret = ldb_msg_normalize(ldb_ctx, ldif, ldif->msg, &ldif->msg);
293                 if (ret != LDB_SUCCESS) {
294                         ldb_ldif_read_free(ldb_ctx, ldif);
295                         DEBUG(5, ("! [%s:%d][%s]: Unable to normalize ldif\n", __FILE__, __LINE__, __FUNCTION__));
296                         return MAPISTORE_ERROR;
297                 }
298                 ret = ldb_add(ldb_ctx, ldif->msg);
299                 if (ret != LDB_SUCCESS) {
300                         ldb_ldif_read_free(ldb_ctx, ldif);
301                         DEBUG(5, ("! [%s:%d][%s]: Unable to add ldb msg\n", __FILE__, __LINE__, __FUNCTION__));
302                         return MAPISTORE_ERROR;
303                 }
304                 ldb_ldif_read_free(ldb_ctx, ldif);
305         }
306
307         return MAPISTORE_SUCCESS;
308 }
309
310
311 /**
312    \details Retrieve the next available folder or message identifier
313
314    \param pctx pointer to the mapistore processing context
315    \param username the username's mailbox to retrieve GlobalCount from
316    \param fmid pointer to the first available fmid within the range
317
318    \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE error
319  */
320 enum MAPISTORE_ERROR mapistore_get_new_fmid(struct processing_context *pctx,
321                                             const char *username,
322                                             uint64_t *fmid)
323 {
324         int                     ret;
325         TALLOC_CTX              *mem_ctx;
326         struct ldb_context      *ldb_ctx;
327         struct ldb_result       *res = NULL;
328         struct ldb_message      *msg;
329         const char * const      attrs[] = { "*", NULL };
330         uint64_t                ReplicaID;
331         uint64_t                GlobalCount;
332         uint64_t                new_GlobalCount;
333
334         /* Sanity checks */
335         MAPISTORE_RETVAL_IF(!pctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
336         MAPISTORE_RETVAL_IF(!pctx->mapping_ctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
337         MAPISTORE_RETVAL_IF(!pctx->mapping_ctx->ldb_ctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
338         MAPISTORE_RETVAL_IF(!username, MAPISTORE_ERR_INVALID_PARAMETER, NULL);
339         MAPISTORE_RETVAL_IF(!fmid, MAPISTORE_ERR_INVALID_PARAMETER, NULL);
340
341         mem_ctx = talloc_named(NULL, 0, "mapistore_get_new_fmid");
342
343         /* Step 1. Retrieve the server object */
344         ldb_ctx = pctx->mapping_ctx->ldb_ctx;
345
346         /* TODO: In the future, we may want to deal with different servers */
347         /* TODO: Use ldb_transaction to lock between get/set operations */
348         ret = ldb_search(ldb_ctx, mem_ctx, &res, ldb_get_root_basedn(ldb_ctx),
349                          LDB_SCOPE_SUBTREE, attrs, "(&(cn=%s)(objectClass=store))", username);
350         if (ret != LDB_SUCCESS || res->count != 1) {
351                 talloc_free(mem_ctx);
352                 DEBUG(5, ("! [%s:%d][%s]: Unable to find the user store object\n", __FILE__, __LINE__, __FUNCTION__));
353                 return MAPISTORE_ERR_NOT_FOUND;
354         }
355
356         /* Step 2. Get the [48 GlobalCount][16 ReplicaID] */
357         GlobalCount = ldb_msg_find_attr_as_uint64(res->msgs[0], "GlobalCount", 0);
358         new_GlobalCount = GlobalCount + 1;
359
360         ReplicaID = ldb_msg_find_attr_as_uint64(res->msgs[0], "ReplicaID", 0);
361
362         /* Step 3. Update the GlobalCount */
363         msg = ldb_msg_new(mem_ctx);
364         msg->dn = ldb_dn_copy(msg, ldb_msg_find_attr_as_dn(ldb_ctx, mem_ctx, res->msgs[0], "distinguishedName"));
365         ldb_msg_add_fmt(msg, "GlobalCount", "0x%"PRIx64, new_GlobalCount);
366         msg->elements[0].flags = LDB_FLAG_MOD_REPLACE;
367         ret = ldb_modify(ldb_ctx, msg);
368         if (ret != LDB_SUCCESS) {
369                 talloc_free(mem_ctx);
370                 DEBUG(5, ("! [%s:%d][%s]: Unable to update GlobalCount for server object\n", __FILE__, __LINE__, __FUNCTION__));
371                 return MAPISTORE_ERROR;
372         }
373
374         /* Step 4. Set the fmid value */
375
376         talloc_free(mem_ctx);
377         *fmid = (GlobalCount << 16) + ReplicaID;
378
379         return MAPISTORE_SUCCESS;
380 }
381
382
383 /**
384    \details Retrieve an allocation range
385
386    \param pctx pointer to the mapistore processing context
387    \param username the user store from which to retrieve the new allocation range
388    \param range the range to allocate
389    \param range_start pointer to the first allocation range fmid to return
390    \param range_end pointer to the last allocation range fmid to return
391
392    \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE error
393  */
394 enum MAPISTORE_ERROR mapistore_get_new_allocation_range(struct processing_context *pctx,
395                                                         const char *username,
396                                                         uint64_t range,
397                                                         uint64_t *range_start,
398                                                         uint64_t *range_end)
399 {
400         int                     ret;
401         TALLOC_CTX              *mem_ctx;
402         struct ldb_context      *ldb_ctx;
403         struct ldb_result       *res = NULL;
404         struct ldb_message      *msg;
405         const char * const      attrs[] = { "*", NULL };
406         uint64_t                ReplicaID;
407         uint64_t                GlobalCount;
408         uint64_t                new_GlobalCount;
409
410         /* Sanity checks */
411         MAPISTORE_RETVAL_IF(!pctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
412         MAPISTORE_RETVAL_IF(!pctx->mapping_ctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
413         MAPISTORE_RETVAL_IF(!pctx->mapping_ctx->ldb_ctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
414         MAPISTORE_RETVAL_IF(!range_start || !range_end, MAPISTORE_ERR_INVALID_PARAMETER, NULL);
415
416         mem_ctx = talloc_named(NULL, 0, "mapistore_get_next_range");
417
418         /* Step 1. Retrieve the user store object */
419         ldb_ctx = pctx->mapping_ctx->ldb_ctx;
420         ret = ldb_search(ldb_ctx, mem_ctx, &res, ldb_get_root_basedn(ldb_ctx),
421                          LDB_SCOPE_SUBTREE, attrs, "(&(objectClass=store)(cn=%s))", username);
422         if (ret != LDB_SUCCESS || res->count != 1) {
423                 talloc_free(mem_ctx);
424                 DEBUG(5, ("! [%s:%d][%s]: Unable to find the user store object\n", __FILE__, __LINE__, __FUNCTION__));
425                 return MAPISTORE_ERR_NOT_FOUND;
426         }
427
428         /* Step 2. Get the current GlobalCount and set the new one */
429         GlobalCount = ldb_msg_find_attr_as_uint64(res->msgs[0], "GlobalCount", 0);
430         ReplicaID = ldb_msg_find_attr_as_uint64(res->msgs[0], "ReplicaID", 0);
431
432         *range_start = GlobalCount;
433
434         new_GlobalCount = GlobalCount + range -1;
435         *range_end = new_GlobalCount;
436
437         new_GlobalCount += 1;
438
439         /* Step 3. Update the GlobalCount */
440         msg = ldb_msg_new(mem_ctx);
441         msg->dn = ldb_dn_copy(msg, ldb_msg_find_attr_as_dn(ldb_ctx, mem_ctx, res->msgs[0], "distinguishedName"));
442         ldb_msg_add_fmt(msg, "GlobalCount", "0x%"PRIx64, new_GlobalCount);
443         msg->elements[0].flags = LDB_FLAG_MOD_REPLACE;
444         ret = ldb_modify(ldb_ctx, msg);
445         if (ret != LDB_SUCCESS) {
446                 talloc_free(mem_ctx);
447                 DEBUG(5, ("! [%s:%d][%s]: Unable to update GlobalCount for server object\n", __FILE__, __LINE__, __FUNCTION__));
448                 return MAPISTORE_ERROR;
449         }
450
451         /* Step 4. Set the fmid value ranges */
452         talloc_free(mem_ctx);
453         *range_start = (*range_start << 16) + ReplicaID;
454         *range_end = (*range_end << 16) + ReplicaID;
455
456         return MAPISTORE_SUCCESS;
457 }
458
459
460 /**
461    \details Retrieve the mapistore URI for a given user mailbox
462
463    \param pctx pointer to the processing context
464    \param username pointer to the username to lookup
465    \param mapistore_uri pointer on pointer to the mapistore URI
466
467    \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE error
468  */
469 enum MAPISTORE_ERROR mapistore_get_mailbox_uri(struct processing_context *pctx,
470                                                const char *username,
471                                                char **mapistore_uri)
472 {
473         int                     ret;
474         TALLOC_CTX              *mem_ctx;
475         const char              *uri;
476         const char * const      attrs[] = { "*", NULL };
477         struct ldb_context      *ldb_ctx;
478         struct ldb_result       *res;
479         struct ldb_dn           *dn;
480
481         /* Sanity checks */
482         MAPISTORE_RETVAL_IF(!pctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
483         MAPISTORE_RETVAL_IF(!username, MAPISTORE_ERR_INVALID_PARAMETER, NULL);
484         MAPISTORE_RETVAL_IF(!mapistore_uri, MAPISTORE_ERR_INVALID_PARAMETER, NULL);
485
486         mem_ctx = talloc_new(pctx);
487         ldb_ctx = pctx->mapping_ctx->ldb_ctx;
488
489         dn = ldb_dn_new_fmt(mem_ctx, ldb_ctx, TMPL_MDB_USERSTORE, username, mapistore_get_firstorgdn());
490         ret = ldb_search(ldb_ctx, mem_ctx, &res, dn, LDB_SCOPE_SUBTREE, attrs, "(&(objectClass=mailbox)(objectClass=container))");
491         if (ret != LDB_SUCCESS || res->count != 1) {
492                 talloc_free(mem_ctx);
493                 DEBUG(5, ("! [%s:%d][%s]: Unable to find the user mailbox root container\n", __FILE__, __LINE__, __FUNCTION__));
494                 return MAPISTORE_ERR_NOT_FOUND;
495         }
496
497         uri = ldb_msg_find_attr_as_string(res->msgs[0], "mapistore_uri", NULL);
498         MAPISTORE_RETVAL_IF(!uri, MAPISTORE_ERR_NOT_FOUND, mem_ctx);
499
500         *mapistore_uri = talloc_strdup(pctx, (char *)uri);
501
502         talloc_free(mem_ctx);
503         return MAPISTORE_SUCCESS;
504 }
505
506
507 /**
508    \details Return an unused or new context identifier
509
510    \param pctx pointer to the processing context
511    \param context_id pointer to the context identifier the function
512    returns
513
514    \return a non zero context identifier on success, otherwise 0.
515  */
516 enum MAPISTORE_ERROR mapistore_get_context_id(struct processing_context *pctx, uint32_t *context_id)
517 {
518         struct context_id_list  *el;
519
520         /* Sanity checks */
521         MAPISTORE_RETVAL_IF(!pctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
522
523         /* Step 1. The free context list doesn't exist yet */
524         if (!pctx->free_ctx) {
525                 pctx->last_context_id++;
526                 *context_id = pctx->last_context_id;
527         }
528
529         /* Step 2. We have a free list */
530         for (el = pctx->free_ctx; el; el = el->next) {
531                 if (el->context_id) {
532                         *context_id = el->context_id;
533                         DLIST_REMOVE(pctx->free_ctx, el);
534                         break;
535                 }
536         }
537
538         return MAPISTORE_SUCCESS;
539 }
540
541
542 /**
543    \details Add a context identifier to the list
544
545    \param pctx pointer to the processing context
546    \param context_id the identifier referencing the context to free
547
548    \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE error
549  */
550 enum MAPISTORE_ERROR mapistore_free_context_id(struct processing_context *pctx, uint32_t context_id)
551 {
552         struct context_id_list  *el;
553
554         /* Sanity checks */
555         MAPISTORE_RETVAL_IF(!pctx, MAPISTORE_ERR_NOT_INITIALIZED, NULL);
556
557         /* Step 1. Ensure the list is not corrupted */
558         for (el = pctx->free_ctx; el; el = el->next) {
559                 if (el->context_id == context_id) {
560                         return MAPISTORE_ERR_CORRUPTED;
561                 }
562         }
563
564         /* Step 2. Create the element and add it to the list */
565         el = talloc_zero((TALLOC_CTX *)pctx, struct context_id_list);
566         el->context_id = context_id;
567         DLIST_ADD_END(pctx->free_ctx, el, struct context_id_list *);
568
569         return MAPISTORE_SUCCESS;
570 }