8bf797f95275be906578c95664baaf6c2f31f18a
[samba.git] / source3 / winbindd / idmap_cache.c
1 /* 
2    Unix SMB/CIFS implementation.
3    ID Mapping Cache
4
5    based on gencache
6
7    Copyright (C) Simo Sorce             2006
8    Copyright (C) Rafal Szczesniak       2002
9
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 3 of the License, or
13    (at your option) any later version.
14
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19
20    You should have received a copy of the GNU General Public License
21    along with this program.  If not, see <http://www.gnu.org/licenses/>.*/
22
23 #include "includes.h"
24 #include "winbindd.h"
25
26 #define TIMEOUT_LEN 12
27 #define IDMAP_CACHE_DATA_FMT    "%12u/%s"
28
29 struct idmap_cache_ctx {
30         TDB_CONTEXT *tdb;
31 };
32
33 static int idmap_cache_destructor(struct idmap_cache_ctx *cache)
34 {
35         int ret = 0;
36
37         if (cache && cache->tdb) {
38                 ret = tdb_close(cache->tdb);
39                 cache->tdb = NULL;
40         }
41
42         return ret;
43 }
44
45 struct idmap_cache_ctx *idmap_cache_init(TALLOC_CTX *memctx)
46 {
47         struct idmap_cache_ctx *cache;
48         char* cache_fname = NULL;
49
50         cache = talloc(memctx, struct idmap_cache_ctx);
51         if ( ! cache) {
52                 DEBUG(0, ("Out of memory!\n"));
53                 return NULL;
54         }
55
56         cache_fname = lock_path("idmap_cache.tdb");
57
58         DEBUG(10, ("Opening cache file at %s\n", cache_fname));
59
60         cache->tdb = tdb_open_log(cache_fname, 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600);
61
62         if (!cache->tdb) {
63                 DEBUG(5, ("Attempt to open %s has failed.\n", cache_fname));
64                 return NULL;
65         }
66
67         talloc_set_destructor(cache, idmap_cache_destructor);
68
69         return cache;
70 }
71
72 static char *idmap_cache_sidkey(TALLOC_CTX *ctx, const DOM_SID *sid)
73 {
74         fstring sidstr;
75
76         return talloc_asprintf(ctx, "IDMAP/SID/%s",
77                                sid_to_fstring(sidstr, sid));
78 }
79
80 static char *idmap_cache_idkey(TALLOC_CTX *ctx, const struct id_map *id)
81 {
82         return talloc_asprintf(ctx, "IDMAP/%s/%lu",
83                                (id->xid.type==ID_TYPE_UID)?"UID":"GID",
84                                (unsigned long)id->xid.id);
85 }
86
87 NTSTATUS idmap_cache_set(struct idmap_cache_ctx *cache, const struct id_map *id)
88 {
89         NTSTATUS ret;
90         time_t timeout = time(NULL) + lp_idmap_cache_time();
91         TDB_DATA databuf;
92         char *sidkey;
93         char *idkey;
94         char *valstr;
95
96         /* Don't cache lookups in the S-1-22-{1,2} domain */
97
98         if (sid_check_is_in_unix_users(id->sid)
99             || sid_check_is_in_unix_groups(id->sid)) {
100                 return NT_STATUS_OK;
101         }
102
103         sidkey = idmap_cache_sidkey(cache, id->sid);
104         if (sidkey == NULL) {
105                 return NT_STATUS_NO_MEMORY;
106         }
107
108         /* use sidkey as the local memory ctx */
109         idkey = idmap_cache_idkey(sidkey, id);
110         if (idkey == NULL) {
111                 ret = NT_STATUS_NO_MEMORY;
112                 goto done;
113         }
114
115         /* save SID -> ID */
116
117         /* use sidkey as the local memory ctx */
118         valstr = talloc_asprintf(sidkey, IDMAP_CACHE_DATA_FMT, (int)timeout, idkey);
119         if (!valstr) {
120                 DEBUG(0, ("Out of memory!\n"));
121                 ret = NT_STATUS_NO_MEMORY;
122                 goto done;
123         }
124
125         databuf = string_term_tdb_data(valstr);
126         DEBUG(10, ("Adding cache entry with key = %s; value = %s and timeout ="
127                    " %s (%d seconds %s)\n", sidkey, valstr , ctime(&timeout),
128                    (int)(timeout - time(NULL)), 
129                    timeout > time(NULL) ? "ahead" : "in the past"));
130
131         if (tdb_store_bystring(cache->tdb, sidkey, databuf, TDB_REPLACE) != 0) {
132                 DEBUG(3, ("Failed to store cache entry!\n"));
133                 ret = NT_STATUS_UNSUCCESSFUL;
134                 goto done;
135         }
136
137         /* save ID -> SID */
138
139         /* use sidkey as the local memory ctx */
140         valstr = talloc_asprintf(sidkey, IDMAP_CACHE_DATA_FMT, (int)timeout, sidkey);
141         if (!valstr) {
142                 DEBUG(0, ("Out of memory!\n"));
143                 ret = NT_STATUS_NO_MEMORY;
144                 goto done;
145         }
146
147         databuf = string_term_tdb_data(valstr);
148         DEBUG(10, ("Adding cache entry with key = %s; value = %s and timeout ="
149                    " %s (%d seconds %s)\n", idkey, valstr, ctime(&timeout),
150                    (int)(timeout - time(NULL)), 
151                    timeout > time(NULL) ? "ahead" : "in the past"));
152
153         if (tdb_store_bystring(cache->tdb, idkey, databuf, TDB_REPLACE) != 0) {
154                 DEBUG(3, ("Failed to store cache entry!\n"));
155                 ret = NT_STATUS_UNSUCCESSFUL;
156                 goto done;
157         }
158
159         ret = NT_STATUS_OK;
160
161 done:
162         talloc_free(sidkey);
163         return ret;
164 }
165
166 NTSTATUS idmap_cache_set_negative_sid(struct idmap_cache_ctx *cache, const struct id_map *id)
167 {
168         NTSTATUS ret = NT_STATUS_OK;
169         time_t timeout = time(NULL) + lp_idmap_negative_cache_time();
170         TDB_DATA databuf;
171         char *sidkey;
172         char *valstr;
173
174         sidkey = idmap_cache_sidkey(cache, id->sid);
175         if (sidkey == NULL) {
176                 return NT_STATUS_NO_MEMORY;
177         }
178
179         /* use sidkey as the local memory ctx */
180         valstr = talloc_asprintf(sidkey, IDMAP_CACHE_DATA_FMT, (int)timeout, "IDMAP/NEGATIVE");
181         if (!valstr) {
182                 DEBUG(0, ("Out of memory!\n"));
183                 ret = NT_STATUS_NO_MEMORY;
184                 goto done;
185         }
186
187         databuf = string_term_tdb_data(valstr);
188         DEBUG(10, ("Adding cache entry with key = %s; value = %s and timeout ="
189                    " %s (%d seconds %s)\n", sidkey, valstr, ctime(&timeout),
190                    (int)(timeout - time(NULL)), 
191                    timeout > time(NULL) ? "ahead" : "in the past"));
192
193         if (tdb_store_bystring(cache->tdb, sidkey, databuf, TDB_REPLACE) != 0) {
194                 DEBUG(3, ("Failed to store cache entry!\n"));
195                 ret = NT_STATUS_UNSUCCESSFUL;
196                 goto done;
197         }
198
199 done:
200         talloc_free(sidkey);
201         return ret;
202 }
203
204 NTSTATUS idmap_cache_set_negative_id(struct idmap_cache_ctx *cache, const struct id_map *id)
205 {
206         NTSTATUS ret = NT_STATUS_OK;
207         time_t timeout = time(NULL) + lp_idmap_negative_cache_time();
208         TDB_DATA databuf;
209         char *idkey;
210         char *valstr;
211
212         idkey = idmap_cache_idkey(cache, id);
213         if (idkey == NULL) {
214                 return NT_STATUS_NO_MEMORY;
215         }
216
217         /* use idkey as the local memory ctx */
218         valstr = talloc_asprintf(idkey, IDMAP_CACHE_DATA_FMT, (int)timeout, "IDMAP/NEGATIVE");
219         if (!valstr) {
220                 DEBUG(0, ("Out of memory!\n"));
221                 ret = NT_STATUS_NO_MEMORY;
222                 goto done;
223         }
224
225         databuf = string_term_tdb_data(valstr);
226         DEBUG(10, ("Adding cache entry with key = %s; value = %s and timeout ="
227                    " %s (%d seconds %s)\n", idkey, valstr, ctime(&timeout),
228                    (int)(timeout - time(NULL)), 
229                    timeout > time(NULL) ? "ahead" : "in the past"));
230
231         if (tdb_store_bystring(cache->tdb, idkey, databuf, TDB_REPLACE) != 0) {
232                 DEBUG(3, ("Failed to store cache entry!\n"));
233                 ret = NT_STATUS_UNSUCCESSFUL;
234                 goto done;
235         }
236
237 done:
238         talloc_free(idkey);
239         return ret;
240 }
241
242 static NTSTATUS idmap_cache_fill_map(struct id_map *id, const char *value)
243 {
244         char *rem;
245
246         /* see if it is a sid */
247         if ( ! strncmp("IDMAP/SID/", value, 10)) {
248
249                 if ( ! string_to_sid(id->sid, &value[10])) {
250                         goto failed;
251                 }
252
253                 id->status = ID_MAPPED;
254
255                 return NT_STATUS_OK;
256         }
257
258         /* not a SID see if it is an UID or a GID */
259         if ( ! strncmp("IDMAP/UID/", value, 10)) {
260
261                 /* a uid */
262                 id->xid.type = ID_TYPE_UID;
263
264         } else if ( ! strncmp("IDMAP/GID/", value, 10)) {
265
266                 /* a gid */
267                 id->xid.type = ID_TYPE_GID;
268
269         } else {
270
271                 /* a completely bogus value bail out */
272                 goto failed;
273         }
274
275         id->xid.id = strtol(&value[10], &rem, 0);
276         if (*rem != '\0') {
277                 goto failed;
278         }
279
280         id->status = ID_MAPPED;
281
282         return NT_STATUS_OK;
283
284 failed:
285         DEBUG(1, ("invalid value: %s\n", value));
286         id->status = ID_UNKNOWN;
287         return NT_STATUS_INTERNAL_DB_CORRUPTION;
288 }
289
290 /* search the cache for the SID an return a mapping if found *
291  *
292  * 4 cases are possible
293  *
294  * 1 map found
295  *      in this case id->status = ID_MAPPED and NT_STATUS_OK is returned
296  * 2 map not found
297  *      in this case id->status = ID_UNKNOWN and NT_STATUS_NONE_MAPPED is returned
298  * 3 negative cache found
299  *      in this case id->status = ID_UNMAPPED and NT_STATUS_OK is returned
300  * 4 map found but timer expired
301  *      in this case id->status = ID_EXPIRED and NT_STATUS_SYNCHRONIZATION_REQUIRED
302  *      is returned. In this case revalidation of the cache is needed.
303  */
304
305 NTSTATUS idmap_cache_map_sid(struct idmap_cache_ctx *cache, struct id_map *id)
306 {
307         NTSTATUS ret = NT_STATUS_OK;
308         TDB_DATA databuf;
309         time_t t;
310         char *sidkey;
311         char *endptr;
312         struct winbindd_domain *our_domain = find_our_domain(); 
313         time_t now = time(NULL);        
314
315         /* make sure it is marked as not mapped by default */
316         id->status = ID_UNKNOWN;
317
318         sidkey = idmap_cache_sidkey(cache, id->sid);
319         if (sidkey == NULL) {
320                 return NT_STATUS_NO_MEMORY;
321         }
322
323         databuf = tdb_fetch_bystring(cache->tdb, sidkey);
324
325         if (databuf.dptr == NULL) {
326                 DEBUG(10, ("Cache entry with key = %s couldn't be found\n", sidkey));
327                 ret = NT_STATUS_NONE_MAPPED;
328                 goto done;
329         }
330
331         t = strtol((const char *)databuf.dptr, &endptr, 10);
332
333         if ((endptr == NULL) || (*endptr != '/')) {
334                 DEBUG(2, ("Invalid idmap cache data format: %s\n",
335                           (const char *)databuf.dptr));
336                 /* remove the entry */
337                 tdb_delete_bystring(cache->tdb, sidkey);
338                 ret = NT_STATUS_NONE_MAPPED;
339                 goto done;
340         }
341
342         /* check it is not negative */
343         if (strcmp("IDMAP/NEGATIVE", endptr+1) != 0) {
344
345                 DEBUG(10, ("Returning %s cache entry: key = %s, value = %s, "
346                            "timeout = %s", t > now ? "valid" :
347                            "expired", sidkey, endptr+1, ctime(&t)));
348
349                 /* this call if successful will also mark the entry as mapped */
350                 ret = idmap_cache_fill_map(id, endptr+1);
351                 if ( ! NT_STATUS_IS_OK(ret)) {
352                         /* if not valid form delete the entry */
353                         tdb_delete_bystring(cache->tdb, sidkey);
354                         ret = NT_STATUS_NONE_MAPPED;
355                         goto done;
356                 }
357
358                 /* here ret == NT_STATUS_OK and id->status = ID_MAPPED */
359
360                 if (t <= now) {
361                         /* If we've been told to be offline - stay in 
362                            that state... */
363                         if ( IS_DOMAIN_OFFLINE(our_domain) ) {
364                                 DEBUG(10,("idmap_cache_map_sid: idmap is offline\n"));
365                                 goto done;                              
366                         }
367
368                         /* We're expired, set an error code
369                            for upper layer */
370                         ret = NT_STATUS_SYNCHRONIZATION_REQUIRED;
371                 }
372
373                 goto done;              
374         }
375
376         /* Was a negative cache hit */
377
378         /* Ignore the negative cache when offline */
379
380         if ( IS_DOMAIN_OFFLINE(our_domain) ) {
381                 DEBUG(10,("idmap_cache_map_sid: idmap is offline\n"));
382                 goto done;
383         }
384
385
386         /* Check for valid or expired cache hits */
387         if (t <= now) {
388                 /* We're expired. Return not mapped */
389                 ret = NT_STATUS_NONE_MAPPED;
390         } else {
391                 /* this is not mapped as it was a negative cache hit */
392                 id->status = ID_UNMAPPED;
393                 ret = NT_STATUS_OK;
394         }
395
396 done:
397         SAFE_FREE(databuf.dptr);
398         talloc_free(sidkey);
399         return ret;
400 }
401
402 /* search the cache for the ID an return a mapping if found *
403  *
404  * 4 cases are possible
405  *
406  * 1 map found
407  *      in this case id->status = ID_MAPPED and NT_STATUS_OK is returned
408  * 2 map not found
409  *      in this case id->status = ID_UNKNOWN and NT_STATUS_NONE_MAPPED is returned
410  * 3 negative cache found
411  *      in this case id->status = ID_UNMAPPED and NT_STATUS_OK is returned
412  * 4 map found but timer expired
413  *      in this case id->status = ID_EXPIRED and NT_STATUS_SYNCHRONIZATION_REQUIRED
414  *      is returned. In this case revalidation of the cache is needed.
415  */
416
417 NTSTATUS idmap_cache_map_id(struct idmap_cache_ctx *cache, struct id_map *id)
418 {
419         NTSTATUS ret;
420         TDB_DATA databuf;
421         time_t t;
422         char *idkey;
423         char *endptr;
424         struct winbindd_domain *our_domain = find_our_domain(); 
425         time_t now = time(NULL);        
426
427         /* make sure it is marked as unknown by default */
428         id->status = ID_UNKNOWN;
429
430         idkey = idmap_cache_idkey(cache, id);
431         if (idkey == NULL) {
432                 return NT_STATUS_NO_MEMORY;
433         }
434
435         databuf = tdb_fetch_bystring(cache->tdb, idkey);
436
437         if (databuf.dptr == NULL) {
438                 DEBUG(10, ("Cache entry with key = %s couldn't be found\n", idkey));
439                 ret = NT_STATUS_NONE_MAPPED;
440                 goto done;
441         }
442
443         t = strtol((const char *)databuf.dptr, &endptr, 10);
444
445         if ((endptr == NULL) || (*endptr != '/')) {
446                 DEBUG(2, ("Invalid idmap cache data format: %s\n",
447                           (const char *)databuf.dptr));
448                 /* remove the entry */
449                 tdb_delete_bystring(cache->tdb, idkey);
450                 ret = NT_STATUS_NONE_MAPPED;
451                 goto done;
452         }
453
454         /* check it is not negative */
455         if (strcmp("IDMAP/NEGATIVE", endptr+1) != 0) {
456
457                 DEBUG(10, ("Returning %s cache entry: key = %s, value = %s, "
458                            "timeout = %s", t > now ? "valid" :
459                            "expired", idkey, endptr+1, ctime(&t)));
460
461                 /* this call if successful will also mark the entry as mapped */
462                 ret = idmap_cache_fill_map(id, endptr+1);
463                 if ( ! NT_STATUS_IS_OK(ret)) {
464                         /* if not valid form delete the entry */
465                         tdb_delete_bystring(cache->tdb, idkey);
466                         ret = NT_STATUS_NONE_MAPPED;
467                         goto done;
468                 }
469
470                 /* here ret == NT_STATUS_OK and id->mapped = ID_MAPPED */
471
472                 if (t <= now) {
473                         /* If we've been told to be offline - stay in
474                            that state... */
475                         if ( IS_DOMAIN_OFFLINE(our_domain) ) {
476                                 DEBUG(10,("idmap_cache_map_sid: idmap is offline\n"));
477                                 goto done;
478                         }
479
480                         /* We're expired, set an error code
481                            for upper layer */
482                         ret = NT_STATUS_SYNCHRONIZATION_REQUIRED;
483                 }
484
485                 goto done;
486         }
487
488         /* Was a negative cache hit */
489
490         /* Ignore the negative cache when offline */
491
492         if ( IS_DOMAIN_OFFLINE(our_domain) ) {
493                 DEBUG(10,("idmap_cache_map_sid: idmap is offline\n"));
494                 ret = NT_STATUS_NONE_MAPPED;
495                 goto done;
496         }
497
498         /* Process the negative cache hit */
499
500         if (t <= now) {
501                 /* We're expired.  Return not mapped */
502                 ret = NT_STATUS_NONE_MAPPED;
503         } else {
504                 /* this is not mapped is it was a negative cache hit */
505                 id->status = ID_UNMAPPED;
506                 ret = NT_STATUS_OK;
507         }
508
509 done:
510         SAFE_FREE(databuf.dptr);
511         talloc_free(idkey);
512         return ret;
513 }
514