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