removed a debug line
[amitay/samba.git] / source3 / nsswitch / winbindd_cache.c
1 /* 
2    Unix SMB/Netbios implementation.
3
4    Winbind cache backend functions
5
6    Copyright (C) Andrew Tridgell 2001
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 2 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, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22
23 #include "winbindd.h"
24
25 struct winbind_cache {
26         struct winbindd_methods *backend;
27         TDB_CONTEXT *tdb;
28 };
29
30 struct cache_entry {
31         NTSTATUS status;
32         uint32 sequence_number;
33         uint8 *data;
34         uint32 len, ofs;
35 };
36
37 static struct winbind_cache *wcache;
38
39 void wcache_flush_cache(void)
40 {
41         extern BOOL opt_nocache;
42
43         if (!wcache) return;
44         if (wcache->tdb) {
45                 tdb_close(wcache->tdb);
46                 wcache->tdb = NULL;
47         }
48         if (opt_nocache) return;
49
50         wcache->tdb = tdb_open_log(lock_path("winbindd_cache.tdb"), 0, 
51                                    TDB_NOLOCK, O_RDWR | O_CREAT | O_TRUNC, 0600);
52
53         if (!wcache->tdb) {
54                 DEBUG(0,("Failed to open winbindd_cache.tdb!\n"));
55         }
56 }
57
58 /* get the winbind_cache structure */
59 static struct winbind_cache *get_cache(struct winbindd_domain *domain)
60 {
61         extern struct winbindd_methods msrpc_methods;
62         struct winbind_cache *ret = wcache;
63
64         if (ret) return ret;
65         
66         ret = smb_xmalloc(sizeof(*ret));
67         ZERO_STRUCTP(ret);
68         switch (lp_security()) {
69 #ifdef HAVE_ADS
70         case SEC_ADS: {
71                 extern struct winbindd_methods ads_methods;
72                 ret->backend = &ads_methods;
73                 break;
74         }
75 #endif
76         default:
77                 ret->backend = &msrpc_methods;
78         }
79
80         wcache = ret;
81         wcache_flush_cache();
82
83         return ret;
84 }
85
86 /*
87   free a centry structure
88 */
89 static void centry_free(struct cache_entry *centry)
90 {
91         if (!centry) return;
92         SAFE_FREE(centry->data);
93         free(centry);
94 }
95
96
97 /*
98   pull a uint32 from a cache entry 
99 */
100 static uint32 centry_uint32(struct cache_entry *centry)
101 {
102         uint32 ret;
103         if (centry->len - centry->ofs < 4) {
104                 DEBUG(0,("centry corruption? needed 4 bytes, have %d\n", 
105                          centry->len - centry->ofs));
106                 smb_panic("centry_uint32");
107         }
108         ret = IVAL(centry->data, centry->ofs);
109         centry->ofs += 4;
110         return ret;
111 }
112
113 /* pull a string from a cache entry, using the supplied
114    talloc context 
115 */
116 static char *centry_string(struct cache_entry *centry, TALLOC_CTX *mem_ctx)
117 {
118         uint32 len;
119         char *ret;
120
121         len = centry_uint32(centry);
122         if (centry->len - centry->ofs < len) {
123                 DEBUG(0,("centry corruption? needed %d bytes, have %d\n", 
124                          len, centry->len - centry->ofs));
125                 smb_panic("centry_string");
126         }
127
128         ret = talloc(mem_ctx, len+1);
129         if (!ret) {
130                 smb_panic("centry_string out of memory\n");
131         }
132         memcpy(ret,centry->data + centry->ofs, len);
133         ret[len] = 0;
134         centry->ofs += len;
135         return ret;
136 }
137
138 /* the server is considered down if it can't give us a sequence number */
139 static BOOL wcache_server_down(struct winbindd_domain *domain)
140 {
141         if (!wcache->tdb) return False;
142         return (domain->sequence_number == DOM_SEQUENCE_NONE);
143 }
144
145
146 /*
147   refresh the domain sequence number. If force is True
148   then always refresh it, no matter how recently we fetched it
149 */
150 static void refresh_sequence_number(struct winbindd_domain *domain, BOOL force)
151 {
152         NTSTATUS status;
153
154         /* see if we have to refetch the domain sequence number */
155         if (!force && (time(NULL) - domain->last_seq_check < lp_winbind_cache_time())) {
156                 return;
157         }
158
159         status = wcache->backend->sequence_number(domain, &domain->sequence_number);
160
161         if (!NT_STATUS_IS_OK(status)) {
162                 domain->sequence_number = DOM_SEQUENCE_NONE;
163         }
164
165         domain->last_seq_check = time(NULL);
166 }
167
168 /*
169   decide if a cache entry has expired
170 */
171 static BOOL centry_expired(struct winbindd_domain *domain, struct cache_entry *centry)
172 {
173         /* if the server is OK and our cache entry came from when it was down then
174            the entry is invalid */
175         if (domain->sequence_number != DOM_SEQUENCE_NONE && 
176             centry->sequence_number == DOM_SEQUENCE_NONE) {
177                 return True;
178         }
179
180         /* if the server is down or the cache entry is not older than the
181            current sequence number then it is OK */
182         if (wcache_server_down(domain) || 
183             centry->sequence_number >= domain->sequence_number) {
184                 return False;
185         }
186
187         /* it's expired */
188         return True;
189 }
190
191 /*
192   fetch an entry from the cache, with a varargs key. auto-fetch the sequence
193   number and return status
194 */
195 static struct cache_entry *wcache_fetch(struct winbind_cache *cache, 
196                                         struct winbindd_domain *domain,
197                                         const char *format, ...)
198 {
199         va_list ap;
200         char *kstr;
201         TDB_DATA data;
202         struct cache_entry *centry;
203
204         refresh_sequence_number(domain, False);
205
206         va_start(ap, format);
207         smb_xvasprintf(&kstr, format, ap);
208         va_end(ap);
209         
210         data = tdb_fetch_by_string(wcache->tdb, kstr);
211         free(kstr);
212         if (!data.dptr) {
213                 /* a cache miss */
214                 return NULL;
215         }
216
217         centry = smb_xmalloc(sizeof(*centry));
218         centry->data = data.dptr;
219         centry->len = data.dsize;
220         centry->ofs = 0;
221
222         if (centry->len < 8) {
223                 /* huh? corrupt cache? */
224                 centry_free(centry);
225                 return NULL;
226         }
227         
228         centry->status = NT_STATUS(centry_uint32(centry));
229         centry->sequence_number = centry_uint32(centry);
230
231         if (centry_expired(domain, centry)) {
232                 centry_free(centry);
233                 return NULL;
234         }
235
236         return centry;
237 }
238
239 /*
240   make sure we have at least len bytes available in a centry 
241 */
242 static void centry_expand(struct cache_entry *centry, uint32 len)
243 {
244         uint8 *p;
245         if (centry->len - centry->ofs >= len) return;
246         centry->len *= 2;
247         p = realloc(centry->data, centry->len);
248         if (!p) {
249                 DEBUG(0,("out of memory: needed %d bytes in centry_expand\n", centry->len));
250                 smb_panic("out of memory in centry_expand");
251         }
252         centry->data = p;
253 }
254
255 /*
256   push a uint32 into a centry 
257 */
258 static void centry_put_uint32(struct cache_entry *centry, uint32 v)
259 {
260         centry_expand(centry, 4);
261         SIVAL(centry->data, centry->ofs, v);
262         centry->ofs += 4;
263 }
264
265 /* 
266    push a string into a centry 
267  */
268 static void centry_put_string(struct cache_entry *centry, const char *s)
269 {
270         int len = strlen(s);
271         centry_put_uint32(centry, len);
272         centry_expand(centry, len);
273         memcpy(centry->data + centry->ofs, s, len);
274         centry->ofs += len;
275 }
276
277 /*
278   start a centry for output. When finished, call centry_end()
279 */
280 struct cache_entry *centry_start(struct winbindd_domain *domain, NTSTATUS status)
281 {
282         struct cache_entry *centry;
283
284         if (!wcache->tdb) return NULL;
285
286         centry = smb_xmalloc(sizeof(*centry));
287
288         refresh_sequence_number(domain, True);
289
290         centry->len = 8192; /* reasonable default */
291         centry->data = smb_xmalloc(centry->len);
292         centry->ofs = 0;
293         centry->sequence_number = domain->sequence_number;
294         centry_put_uint32(centry, NT_STATUS_V(status));
295         centry_put_uint32(centry, centry->sequence_number);
296         return centry;
297 }
298
299 /*
300   finish a centry and write it to the tdb
301 */
302 static void centry_end(struct cache_entry *centry, const char *format, ...)
303 {
304         va_list ap;
305         char *kstr;
306
307         va_start(ap, format);
308         smb_xvasprintf(&kstr, format, ap);
309         va_end(ap);
310
311         tdb_store_by_string(wcache->tdb, kstr, centry->data, centry->ofs);
312         free(kstr);
313 }
314
315 /* Query display info. This is the basic user list fn */
316 static NTSTATUS query_user_list(struct winbindd_domain *domain,
317                                 TALLOC_CTX *mem_ctx,
318                                 uint32 *start_ndx, uint32 *num_entries, 
319                                 WINBIND_USERINFO **info)
320 {
321         struct winbind_cache *cache = get_cache(domain);
322         struct cache_entry *centry = NULL;
323         NTSTATUS status;
324         int i;
325
326         if (!cache->tdb) goto do_query;
327
328         centry = wcache_fetch(cache, domain, "USERLIST/%s/%d", domain->name, *start_ndx);
329         if (!centry) goto do_query;
330
331         *num_entries = centry_uint32(centry);
332         
333         if (*num_entries == 0) goto do_cached;
334
335         (*info) = talloc(mem_ctx, sizeof(**info) * (*num_entries));
336         if (! (*info)) smb_panic("query_user_list out of memory");
337         for (i=0; i<(*num_entries); i++) {
338                 (*info)[i].acct_name = centry_string(centry, mem_ctx);
339                 (*info)[i].full_name = centry_string(centry, mem_ctx);
340                 (*info)[i].user_rid = centry_uint32(centry);
341                 (*info)[i].group_rid = centry_uint32(centry);
342         }
343
344 do_cached:      
345         status = centry->status;
346         centry_free(centry);
347         return status;
348
349 do_query:
350         if (wcache_server_down(domain)) {
351                 *num_entries = 0;
352                 return NT_STATUS_SERVER_DISABLED;
353         }
354
355         status = cache->backend->query_user_list(domain, mem_ctx, start_ndx, num_entries, info);
356
357         /* and save it */
358         centry = centry_start(domain, status);
359         if (!centry) goto skip_save;
360         centry_put_uint32(centry, *num_entries);
361         for (i=0; i<(*num_entries); i++) {
362                 centry_put_string(centry, (*info)[i].acct_name);
363                 centry_put_string(centry, (*info)[i].full_name);
364                 centry_put_uint32(centry, (*info)[i].user_rid);
365                 centry_put_uint32(centry, (*info)[i].group_rid);
366         }       
367         centry_end(centry, "USERLIST/%s/%d", domain->name, *start_ndx);
368         centry_free(centry);
369
370 skip_save:
371         return status;
372 }
373
374 /* list all domain groups */
375 static NTSTATUS enum_dom_groups(struct winbindd_domain *domain,
376                                 TALLOC_CTX *mem_ctx,
377                                 uint32 *start_ndx, uint32 *num_entries, 
378                                 struct acct_info **info)
379 {
380         struct winbind_cache *cache = get_cache(domain);
381         struct cache_entry *centry = NULL;
382         NTSTATUS status;
383         int i;
384
385         if (!cache->tdb) goto do_query;
386
387         centry = wcache_fetch(cache, domain, "GROUPLIST/%s/%d", domain->name, *start_ndx);
388         if (!centry) goto do_query;
389
390         *num_entries = centry_uint32(centry);
391         
392         if (*num_entries == 0) goto do_cached;
393
394         (*info) = talloc(mem_ctx, sizeof(**info) * (*num_entries));
395         if (! (*info)) smb_panic("enum_dom_groups out of memory");
396         for (i=0; i<(*num_entries); i++) {
397                 fstrcpy((*info)[i].acct_name, centry_string(centry, mem_ctx));
398                 fstrcpy((*info)[i].acct_desc, centry_string(centry, mem_ctx));
399                 (*info)[i].rid = centry_uint32(centry);
400         }
401
402 do_cached:      
403         status = centry->status;
404         centry_free(centry);
405         return status;
406
407 do_query:
408         if (wcache_server_down(domain)) {
409                 *num_entries = 0;
410                 return NT_STATUS_SERVER_DISABLED;
411         }
412
413         status = cache->backend->enum_dom_groups(domain, mem_ctx, start_ndx, num_entries, info);
414
415         /* and save it */
416         centry = centry_start(domain, status);
417         if (!centry) goto skip_save;
418         centry_put_uint32(centry, *num_entries);
419         for (i=0; i<(*num_entries); i++) {
420                 centry_put_string(centry, (*info)[i].acct_name);
421                 centry_put_string(centry, (*info)[i].acct_desc);
422                 centry_put_uint32(centry, (*info)[i].rid);
423         }       
424         centry_end(centry, "GROUPLIST/%s/%d", domain->name, *start_ndx);
425         centry_free(centry);
426
427 skip_save:
428         return status;
429 }
430
431
432 /* convert a single name to a sid in a domain */
433 static NTSTATUS name_to_sid(struct winbindd_domain *domain,
434                             const char *name,
435                             DOM_SID *sid,
436                             enum SID_NAME_USE *type)
437 {
438         struct winbind_cache *cache = get_cache(domain);
439         struct cache_entry *centry = NULL;
440         NTSTATUS status;
441         int len;
442
443         if (!cache->tdb) goto do_query;
444
445         centry = wcache_fetch(cache, domain, "NAMETOSID/%s/%s", domain->name, name);
446         if (!centry) goto do_query;
447         *type = centry_uint32(centry);
448         sid_parse(centry->data + centry->ofs, centry->len - centry->ofs, sid);
449
450         status = centry->status;
451         centry_free(centry);
452         return status;
453
454 do_query:
455         if (wcache_server_down(domain)) {
456                 return NT_STATUS_SERVER_DISABLED;
457         }
458         status = cache->backend->name_to_sid(domain, name, sid, type);
459
460         /* and save it */
461         centry = centry_start(domain, status);
462         if (!centry) goto skip_save;
463         len = sid_size(sid);
464         centry_expand(centry, len);
465         centry_put_uint32(centry, *type);
466         sid_linearize(centry->data + centry->ofs, len, sid);
467         centry->ofs += len;
468         centry_end(centry, "NAMETOSID/%s/%s", domain->name, name);
469         centry_free(centry);
470
471 skip_save:
472         return status;
473 }
474
475 /* convert a sid to a user or group name */
476 static NTSTATUS sid_to_name(struct winbindd_domain *domain,
477                             TALLOC_CTX *mem_ctx,
478                             DOM_SID *sid,
479                             char **name,
480                             enum SID_NAME_USE *type)
481 {
482         struct winbind_cache *cache = get_cache(domain);
483         struct cache_entry *centry = NULL;
484         NTSTATUS status;
485         char *sidstr = NULL;
486
487         if (!cache->tdb) goto do_query;
488
489         sidstr = ads_sid_binstring(sid);
490
491         centry = wcache_fetch(cache, domain, "SIDTONAME/%s/%s", domain->name, sidstr);
492         if (!centry) goto do_query;
493         if (NT_STATUS_IS_OK(centry->status)) {
494                 *type = centry_uint32(centry);
495                 *name = centry_string(centry, mem_ctx);
496         }
497         status = centry->status;
498         centry_free(centry);
499         SAFE_FREE(sidstr);
500         return status;
501
502 do_query:
503         if (wcache_server_down(domain)) {
504                 return NT_STATUS_SERVER_DISABLED;
505         }
506         status = cache->backend->sid_to_name(domain, mem_ctx, sid, name, type);
507
508         /* and save it */
509         centry = centry_start(domain, status);
510         if (!centry) goto skip_save;
511         if (NT_STATUS_IS_OK(status)) {
512                 centry_put_uint32(centry, *type);
513                 centry_put_string(centry, *name);
514         }
515         centry_end(centry, "SIDTONAME/%s/%s", domain->name, sidstr);
516         centry_free(centry);
517
518 skip_save:
519         SAFE_FREE(sidstr);
520         return status;
521 }
522
523
524 /* Lookup user information from a rid */
525 static NTSTATUS query_user(struct winbindd_domain *domain, 
526                            TALLOC_CTX *mem_ctx, 
527                            uint32 user_rid, 
528                            WINBIND_USERINFO *info)
529 {
530         struct winbind_cache *cache = get_cache(domain);
531         struct cache_entry *centry = NULL;
532         NTSTATUS status;
533
534         if (!cache->tdb) goto do_query;
535
536         centry = wcache_fetch(cache, domain, "USER/%s/%x", domain->name, user_rid);
537         if (!centry) goto do_query;
538
539         info->acct_name = centry_string(centry, mem_ctx);
540         info->full_name = centry_string(centry, mem_ctx);
541         info->user_rid = centry_uint32(centry);
542         info->group_rid = centry_uint32(centry);
543         status = centry->status;
544         centry_free(centry);
545         return status;
546
547 do_query:
548         if (wcache_server_down(domain)) {
549                 return NT_STATUS_SERVER_DISABLED;
550         }
551         status = cache->backend->query_user(domain, mem_ctx, user_rid, info);
552
553         /* and save it */
554         centry = centry_start(domain, status);
555         if (!centry) goto skip_save;
556         centry_put_string(centry, info->acct_name);
557         centry_put_string(centry, info->full_name);
558         centry_put_uint32(centry, info->user_rid);
559         centry_put_uint32(centry, info->group_rid);
560         centry_end(centry, "USER/%s/%x", domain->name, user_rid);
561         centry_free(centry);
562
563 skip_save:
564         return status;
565 }
566
567
568 /* Lookup groups a user is a member of. */
569 static NTSTATUS lookup_usergroups(struct winbindd_domain *domain,
570                                   TALLOC_CTX *mem_ctx,
571                                   uint32 user_rid, 
572                                   uint32 *num_groups, uint32 **user_gids)
573 {
574         struct winbind_cache *cache = get_cache(domain);
575         struct cache_entry *centry = NULL;
576         NTSTATUS status;
577         int i;
578
579         if (!cache->tdb) goto do_query;
580
581         centry = wcache_fetch(cache, domain, "USERGROUPS/%s/%x", domain->name, user_rid);
582         if (!centry) goto do_query;
583
584         *num_groups = centry_uint32(centry);
585         
586         if (*num_groups == 0) goto do_cached;
587
588         (*user_gids) = talloc(mem_ctx, sizeof(**user_gids) * (*num_groups));
589         if (! (*user_gids)) smb_panic("lookup_usergroups out of memory");
590         for (i=0; i<(*num_groups); i++) {
591                 (*user_gids)[i] = centry_uint32(centry);
592         }
593
594 do_cached:      
595         status = centry->status;
596         centry_free(centry);
597         return status;
598
599 do_query:
600         if (wcache_server_down(domain)) {
601                 (*num_groups) = 0;
602                 return NT_STATUS_SERVER_DISABLED;
603         }
604         status = cache->backend->lookup_usergroups(domain, mem_ctx, user_rid, num_groups, user_gids);
605
606         /* and save it */
607         centry = centry_start(domain, status);
608         if (!centry) goto skip_save;
609         centry_put_uint32(centry, *num_groups);
610         for (i=0; i<(*num_groups); i++) {
611                 centry_put_uint32(centry, (*user_gids)[i]);
612         }       
613         centry_end(centry, "USERGROUPS/%s/%x", domain->name, user_rid);
614         centry_free(centry);
615
616 skip_save:
617         return status;
618 }
619
620
621 static NTSTATUS lookup_groupmem(struct winbindd_domain *domain,
622                                 TALLOC_CTX *mem_ctx,
623                                 uint32 group_rid, uint32 *num_names, 
624                                 uint32 **rid_mem, char ***names, 
625                                 uint32 **name_types)
626 {
627         struct winbind_cache *cache = get_cache(domain);
628         struct cache_entry *centry = NULL;
629         NTSTATUS status;
630         int i;
631
632         if (!cache->tdb) goto do_query;
633
634         centry = wcache_fetch(cache, domain, "GROUPMEM/%s/%x", domain->name, group_rid);
635         if (!centry) goto do_query;
636
637         *num_names = centry_uint32(centry);
638         
639         if (*num_names == 0) goto do_cached;
640
641         (*rid_mem) = talloc(mem_ctx, sizeof(**rid_mem) * (*num_names));
642         (*names) = talloc(mem_ctx, sizeof(**names) * (*num_names));
643         (*name_types) = talloc(mem_ctx, sizeof(**name_types) * (*num_names));
644
645         if (! (*rid_mem) || ! (*names) || ! (*name_types)) {
646                 smb_panic("lookup_groupmem out of memory");
647         }
648
649         for (i=0; i<(*num_names); i++) {
650                 (*rid_mem)[i] = centry_uint32(centry);
651                 (*names)[i] = centry_string(centry, mem_ctx);
652                 (*name_types)[i] = centry_uint32(centry);
653         }
654
655 do_cached:      
656         status = centry->status;
657         centry_free(centry);
658         return status;
659
660 do_query:
661         if (wcache_server_down(domain)) {
662                 (*num_names) = 0;
663                 return NT_STATUS_SERVER_DISABLED;
664         }
665         status = cache->backend->lookup_groupmem(domain, mem_ctx, group_rid, num_names, 
666                                                  rid_mem, names, name_types);
667
668         /* and save it */
669         centry = centry_start(domain, status);
670         if (!centry) goto skip_save;
671         centry_put_uint32(centry, *num_names);
672         for (i=0; i<(*num_names); i++) {
673                 centry_put_uint32(centry, (*rid_mem)[i]);
674                 centry_put_string(centry, (*names)[i]);
675                 centry_put_uint32(centry, (*name_types)[i]);
676         }       
677         centry_end(centry, "GROUPMEM/%s/%x", domain->name, group_rid);
678         centry_free(centry);
679
680 skip_save:
681         return status;
682 }
683
684 /* find the sequence number for a domain */
685 static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq)
686 {
687         refresh_sequence_number(domain, False);
688
689         *seq = domain->sequence_number;
690
691         return NT_STATUS_OK;
692 }
693
694 /* the ADS backend methods are exposed via this structure */
695 struct winbindd_methods cache_methods = {
696         query_user_list,
697         enum_dom_groups,
698         name_to_sid,
699         sid_to_name,
700         query_user,
701         lookup_usergroups,
702         lookup_groupmem,
703         sequence_number
704 };
705
706