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