Cache positive and negative name domain controller lookups.
[sfrench/samba-autobuild/.git] / source3 / nsswitch / winbindd_cm.c
1 /* 
2    Unix SMB/Netbios implementation.
3    Version 3.0
4
5    Winbind daemon connection manager
6
7    Copyright (C) Tim Potter 2001
8    
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18    
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23
24 /*
25    We need to manage connections to domain controllers without having to
26    mess up the main winbindd code with other issues.  The aim of the
27    connection manager is to:
28   
29        - make connections to domain controllers and cache them
30        - re-establish connections when networks or servers go down
31        - centralise the policy on connection timeouts, domain controller
32          selection etc
33        - manage re-entrancy for when winbindd becomes able to handle
34          multiple outstanding rpc requests
35   
36    Why not have connection management as part of the rpc layer like tng?
37    Good question.  This code may morph into libsmb/rpc_cache.c or something
38    like that but at the moment it's simply staying as part of winbind.  I
39    think the TNG architecture of forcing every user of the rpc layer to use
40    the connection caching system is a bad idea.  It should be an optional
41    method of using the routines.  We actually cache policy handles - tng
42    caches connections to pipes.
43
44    The TNG design is quite good but I disagree with some aspects of the
45    implementation. -tpot
46
47  */
48
49 /*
50    TODO:
51
52      - I'm pretty annoyed by all the make_nmb_name() stuff.  It should be
53        moved down into another function.
54
55      - There needs to be a utility function in libsmb/namequery.c that does
56        cm_get_dc_name() 
57
58      - When closing down sam handles we need to close down user, group and
59        domain handles.
60
61      - Take care when destroying cli_structs as they can be shared between
62        various sam handles.
63
64  */
65
66 #include "winbindd.h"
67
68 /* We store lists of connections here */
69
70 enum sam_pipe_type {
71         SAM_PIPE_BASIC,         /* A basic handle */
72         SAM_PIPE_DOM,           /* A domain handle */
73         SAM_PIPE_USER,          /* A handle on a user */
74         SAM_PIPE_GROUP          /* A handle on a group */
75 };
76
77 /* Return a string description of a SAM pipe type */
78
79 static char *pipe_type(enum sam_pipe_type pt)
80 {
81         char *msg;
82
83         switch (pt) {
84         case SAM_PIPE_BASIC:
85                 msg = "BASIC";
86                 break;
87         case SAM_PIPE_DOM:
88                 msg = "DOMAIN";
89                 break;
90         case SAM_PIPE_USER:
91                 msg = "USER";
92                 break;
93         case SAM_PIPE_GROUP:
94                 msg = "GROUP";
95                 break;
96         default:
97                 msg = "??";
98                 break;
99         }
100
101         return msg;
102 }
103
104 /* Global list of connections.  Initially a DLIST but can become a hash
105    table or whatever later. */
106
107 struct winbindd_cm_conn {
108         struct winbindd_cm_conn *prev, *next;
109         fstring domain;
110         fstring controller;
111         fstring pipe_name;
112         struct cli_state *cli;
113         POLICY_HND pol;
114
115         /* Pipe-specific properties for this instance */
116
117         union {
118                 struct {
119                         enum sam_pipe_type pipe_type;
120                         uint32 rid;
121                 } samr;
122         } pipe_data;
123 };
124
125 struct winbindd_cm_conn *cm_conns = NULL;
126
127 /* Get a domain controller name.  Cache positive and negative lookups so we
128    don't go to the network too often when something is badly broken. */
129
130 #define GET_DC_NAME_CACHE_TIMEOUT 30 /* Seconds between dc lookups */
131
132 struct get_dc_name_cache {
133         fstring domain_name;
134         fstring srv_name;
135         time_t lookup_time;
136         struct get_dc_name_cache *prev, *next;
137 };
138
139 static BOOL cm_get_dc_name(char *domain, fstring srv_name)
140 {
141         static struct get_dc_name_cache *get_dc_name_cache;
142         struct get_dc_name_cache *dcc;
143         struct in_addr *ip_list, dc_ip;
144         extern pstring global_myname;
145         int count, i;
146
147         /* Check the cache for previous lookups */
148
149         for (dcc = get_dc_name_cache; dcc; dcc = dcc->next) {
150
151                 if (!strequal(domain, dcc->domain_name))
152                         continue; /* Not our domain */
153
154                 if ((time(NULL) - dcc->lookup_time) > GET_DC_NAME_CACHE_TIMEOUT) {
155
156                         /* Cache entry has expired, delete it */
157
158                         DEBUG(10, ("get_dc_name_cache entry expired for %s\n",
159                                    domain));
160
161                         DLIST_REMOVE(get_dc_name_cache, dcc);
162                         free(dcc);
163
164                         break;
165                 }
166
167                 /* Return a positive or negative lookup for this domain */
168
169                 if (dcc->srv_name[0]) {
170                         DEBUG(10, ("returning positive get_dc_name_cache "
171                                    "entry for %s\n", domain));
172                         fstrcpy(srv_name, dcc->srv_name);
173                         return True;
174                 } else {
175                         DEBUG(10, ("returning negative get_dc_name_cache "
176                                    "entry for %s\n", domain));
177                         return False;
178                 }
179         }
180
181         /* Add cache entry for this lookup. */
182
183         DEBUG(10, ("Creating get_dc_name_cache entry for %s\n", domain));
184
185         if (!(dcc = (struct get_dc_name_cache *)
186              malloc(sizeof(struct get_dc_name_cache))))
187                 return False;
188
189         ZERO_STRUCTP(dcc);
190
191         fstrcpy(dcc->domain_name, domain);
192         dcc->lookup_time = time(NULL);
193
194         DLIST_ADD(get_dc_name_cache, dcc);
195
196         /* Lookup domain controller name */
197                 
198         if (!get_dc_list(False, domain, &ip_list, &count))
199                 return False;
200                 
201         /* Firstly choose a PDC/BDC who has the same network address as any
202            of our interfaces. */
203         
204         for (i = 0; i < count; i++) {
205                 if(is_local_net(ip_list[i]))
206                         goto got_ip;
207         }
208         
209         i = (sys_random() % count);
210         
211  got_ip:
212         dc_ip = ip_list[i];
213         SAFE_FREE(ip_list);
214                 
215         if (!lookup_pdc_name(global_myname, domain, &dc_ip, srv_name))
216                 return False;
217
218         /* We have a name so make the cache entry positive now */
219
220         fstrcpy(dcc->srv_name, srv_name);
221
222         return True;
223 }
224
225 /* Open a new smb pipe connection to a DC on a given domain.  Cache
226    negative creation attempts so we don't try and connect to broken
227    machines too often. */
228
229 #define OPEN_CONNECTION_CACHE_TIMEOUT 30 /* Seconds between attempts */
230
231 struct open_connection_cache {
232         fstring domain_name;
233         fstring controller;
234         time_t lookup_time;
235         struct open_connection_cache *prev, *next;
236 };
237
238 static BOOL cm_open_connection(char *domain, char *pipe_name,
239                                struct winbindd_cm_conn *new_conn)
240 {
241         static struct open_connection_cache *open_connection_cache;
242         struct open_connection_cache *occ;
243         struct nmb_name calling, called;
244         extern pstring global_myname;
245         fstring dest_host;
246         struct in_addr dest_ip;
247         BOOL result = False;
248         struct ntuser_creds creds;
249
250         fstrcpy(new_conn->domain, domain);
251         fstrcpy(new_conn->pipe_name, pipe_name);
252         
253         /* Look for a domain controller for this domain.  Negative results
254            are cached so don't bother applying the caching for this
255            function just yet.  */
256
257         if (!cm_get_dc_name(domain, new_conn->controller))
258                 goto done;
259
260         /* Return false if we have tried to look up this domain and netbios
261            name before and failed. */
262
263         for (occ = open_connection_cache; occ; occ = occ->next) {
264                 
265                 if (!(strequal(domain, occ->domain_name) &&
266                       strequal(new_conn->controller, occ->controller)))
267                         continue; /* Not our domain */
268
269                 if ((time(NULL) - occ->lookup_time) > OPEN_CONNECTION_CACHE_TIMEOUT) {
270                         /* Cache entry has expired, delete it */
271
272                         DEBUG(10, ("cm_open_connection cache entry expired "
273                                    "for %s, %s\n", domain,
274                                    new_conn->controller));
275
276                         DLIST_REMOVE(open_connection_cache, occ);
277                         free(occ);
278
279                         break;
280                 }
281
282                 /* The timeout hasn't expired yet so return false */
283
284                 DEBUG(10, ("returning negative open_connection_cache entry "
285                            "for %s, %s\n", domain, new_conn->controller));
286
287                 goto done;
288         }
289
290         /* Initialise SMB connection */
291
292         if (!(new_conn->cli = cli_initialise(NULL)))
293                 goto done;
294
295         if (!resolve_srv_name(new_conn->controller, dest_host, &dest_ip))
296                 goto done;
297
298         make_nmb_name(&called, dns_to_netbios_name(new_conn->controller), 
299                       0x20);
300         make_nmb_name(&calling, dns_to_netbios_name(global_myname), 0);
301
302         ZERO_STRUCT(creds);
303         creds.pwd.null_pwd = 1;
304
305         cli_init_creds(new_conn->cli, &creds);
306
307         if (!cli_establish_connection(new_conn->cli, new_conn->controller, 
308                                       &dest_ip, &calling, &called, "IPC$", 
309                                       "IPC", False, True))
310                 goto done;
311
312         if (!cli_nt_session_open (new_conn->cli, pipe_name))
313                 goto done;
314
315         result = True;
316
317  done:
318         /* Create negative lookup cache entry for this domain and
319            controller */
320
321         if (!result) {
322                 if (!(occ = (struct open_connection_cache *)
323                       malloc(sizeof(struct open_connection_cache))))
324                         return False;
325
326                 ZERO_STRUCTP(occ);
327
328                 fstrcpy(occ->domain_name, domain);
329                 fstrcpy(occ->controller, new_conn->controller);
330                 occ->lookup_time = time(NULL);
331
332                 DLIST_ADD(open_connection_cache, occ);
333         }
334
335         if (!result && new_conn->cli)
336                 cli_shutdown(new_conn->cli);
337
338         return result;
339 }
340
341 /* Return true if a connection is still alive */
342
343 static BOOL connection_ok(struct winbindd_cm_conn *conn)
344 {
345         if (!conn->cli->initialised)
346                 return False;
347
348         if (conn->cli->fd == -1)
349                 return False;
350         
351         return True;
352 }
353
354 /* Return a LSA policy handle on a domain */
355
356 CLI_POLICY_HND *cm_get_lsa_handle(char *domain)
357 {
358         struct winbindd_cm_conn *conn;
359         uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
360         NTSTATUS result;
361         static CLI_POLICY_HND hnd;
362
363         /* Look for existing connections */
364
365         for (conn = cm_conns; conn; conn = conn->next) {
366                 if (strequal(conn->domain, domain) &&
367                     strequal(conn->pipe_name, PIPE_LSARPC)) {
368
369                         if (!connection_ok(conn)) {
370                                 DLIST_REMOVE(cm_conns, conn);
371                                 return NULL;
372                         }
373
374                         goto ok;
375                 }
376         }
377
378         /* Create a new one */
379
380         if (!(conn = (struct winbindd_cm_conn *)
381               malloc(sizeof(struct winbindd_cm_conn))))
382                 return NULL;
383
384         ZERO_STRUCTP(conn);
385
386         if (!cm_open_connection(domain, PIPE_LSARPC, conn)) {
387                 DEBUG(3, ("Could not connect to a dc for domain %s\n",
388                           domain));
389                 return NULL;
390         }
391
392         result = cli_lsa_open_policy(conn->cli, conn->cli->mem_ctx, False, 
393                                      des_access, &conn->pol);
394
395         if (!NT_STATUS_IS_OK(result))
396                 return NULL;
397
398         /* Add to list */
399
400         DLIST_ADD(cm_conns, conn);
401
402  ok:
403         hnd.pol = conn->pol;
404         hnd.cli = conn->cli;
405
406         return &hnd;
407 }
408
409 /* Return a SAM policy handle on a domain */
410
411 CLI_POLICY_HND *cm_get_sam_handle(char *domain)
412
413         struct winbindd_cm_conn *conn;
414         uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
415         NTSTATUS result;
416         static CLI_POLICY_HND hnd;
417
418         /* Look for existing connections */
419
420         for (conn = cm_conns; conn; conn = conn->next) {
421                 if (strequal(conn->domain, domain) &&
422                     strequal(conn->pipe_name, PIPE_SAMR) &&
423                     conn->pipe_data.samr.pipe_type == SAM_PIPE_BASIC) {
424
425                         if (!connection_ok(conn)) {
426                                 DLIST_REMOVE(cm_conns, conn);
427                                 return NULL;
428                         }
429
430                         goto ok;
431                 }
432         }
433
434         /* Create a new one */
435
436         if (!(conn = (struct winbindd_cm_conn *)
437               malloc(sizeof(struct winbindd_cm_conn))))
438                 return NULL;
439
440         ZERO_STRUCTP(conn);
441
442         if (!cm_open_connection(domain, PIPE_SAMR, conn)) {
443                 DEBUG(3, ("Could not connect to a dc for domain %s\n",
444                           domain));
445                 return NULL;
446         }
447
448         result = cli_samr_connect(conn->cli, conn->cli->mem_ctx, des_access, 
449                                   &conn->pol);
450
451         if (!NT_STATUS_IS_OK(result))
452                 return NULL;
453
454         /* Add to list */
455
456         DLIST_ADD(cm_conns, conn);
457
458  ok:
459         hnd.pol = conn->pol;
460         hnd.cli = conn->cli;
461
462         return &hnd;        
463 }
464
465 /* Return a SAM domain policy handle on a domain */
466
467 CLI_POLICY_HND *cm_get_sam_dom_handle(char *domain, DOM_SID *domain_sid)
468 {
469         struct winbindd_cm_conn *conn, *basic_conn = NULL;
470         static CLI_POLICY_HND hnd;
471         NTSTATUS result;
472         uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
473
474         /* Look for existing connections */
475
476         for (conn = cm_conns; conn; conn = conn->next) {
477                 if (strequal(conn->domain, domain) &&
478                     strequal(conn->pipe_name, PIPE_SAMR) &&
479                     conn->pipe_data.samr.pipe_type == SAM_PIPE_DOM) {
480
481                         if (!connection_ok(conn)) {
482                                 DLIST_REMOVE(cm_conns, conn);
483                                 return NULL;
484                         }
485
486                         goto ok;
487                 }
488         }
489
490         /* Create a basic handle to open a domain handle from */
491
492         if (!cm_get_sam_handle(domain))
493                 return False;
494
495         for (conn = cm_conns; conn; conn = conn->next) {
496                 if (strequal(conn->domain, domain) &&
497                     strequal(conn->pipe_name, PIPE_SAMR) &&
498                     conn->pipe_data.samr.pipe_type == SAM_PIPE_BASIC)
499                         basic_conn = conn;
500         }
501         
502         if (!(conn = (struct winbindd_cm_conn *)
503               malloc(sizeof(struct winbindd_cm_conn))))
504                 return NULL;
505         
506         ZERO_STRUCTP(conn);
507
508         fstrcpy(conn->domain, basic_conn->domain);
509         fstrcpy(conn->controller, basic_conn->controller);
510         fstrcpy(conn->pipe_name, basic_conn->pipe_name);
511
512         conn->pipe_data.samr.pipe_type = SAM_PIPE_DOM;
513         conn->cli = basic_conn->cli;
514
515         result = cli_samr_open_domain(conn->cli, conn->cli->mem_ctx,
516                                       &basic_conn->pol, des_access, 
517                                       domain_sid, &conn->pol);
518
519         if (!NT_STATUS_IS_OK(result))
520                 return NULL;
521
522         /* Add to list */
523
524         DLIST_ADD(cm_conns, conn);
525
526  ok:
527         hnd.pol = conn->pol;
528         hnd.cli = conn->cli;
529
530         return &hnd;
531 }
532
533 /* Return a SAM policy handle on a domain user */
534
535 CLI_POLICY_HND *cm_get_sam_user_handle(char *domain, DOM_SID *domain_sid,
536                                        uint32 user_rid)
537 {
538         struct winbindd_cm_conn *conn, *basic_conn = NULL;
539         static CLI_POLICY_HND hnd;
540         NTSTATUS result;
541         uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
542
543         /* Look for existing connections */
544
545         for (conn = cm_conns; conn; conn = conn->next) {
546                 if (strequal(conn->domain, domain) &&
547                     strequal(conn->pipe_name, PIPE_SAMR) &&
548                     conn->pipe_data.samr.pipe_type == SAM_PIPE_USER &&
549                     conn->pipe_data.samr.rid == user_rid) {
550
551                         if (!connection_ok(conn)) {
552                                 DLIST_REMOVE(cm_conns, conn);
553                                 return NULL;
554                         }
555                 
556                         goto ok;
557                 }
558         }
559
560         /* Create a domain handle to open a user handle from */
561
562         if (!cm_get_sam_dom_handle(domain, domain_sid))
563                 return NULL;
564
565         for (conn = cm_conns; conn; conn = conn->next) {
566                 if (strequal(conn->domain, domain) &&
567                     strequal(conn->pipe_name, PIPE_SAMR) &&
568                     conn->pipe_data.samr.pipe_type == SAM_PIPE_DOM)
569                         basic_conn = conn;
570         }
571         
572         if (!basic_conn) {
573                 DEBUG(0, ("No domain sam handle was created!\n"));
574                 return NULL;
575         }
576
577         if (!(conn = (struct winbindd_cm_conn *)
578               malloc(sizeof(struct winbindd_cm_conn))))
579                 return NULL;
580         
581         ZERO_STRUCTP(conn);
582
583         fstrcpy(conn->domain, basic_conn->domain);
584         fstrcpy(conn->controller, basic_conn->controller);
585         fstrcpy(conn->pipe_name, basic_conn->pipe_name);
586         
587         conn->pipe_data.samr.pipe_type = SAM_PIPE_USER;
588         conn->cli = basic_conn->cli;
589         conn->pipe_data.samr.rid = user_rid;
590
591         result = cli_samr_open_user(conn->cli, conn->cli->mem_ctx,
592                                     &basic_conn->pol, des_access, user_rid,
593                                     &conn->pol);
594
595         if (!NT_STATUS_IS_OK(result))
596                 return NULL;
597
598         /* Add to list */
599
600         DLIST_ADD(cm_conns, conn);
601
602  ok:
603         hnd.pol = conn->pol;
604         hnd.cli = conn->cli;
605
606         return &hnd;
607 }
608
609 /* Return a SAM policy handle on a domain group */
610
611 CLI_POLICY_HND *cm_get_sam_group_handle(char *domain, DOM_SID *domain_sid,
612                                         uint32 group_rid)
613 {
614         struct winbindd_cm_conn *conn, *basic_conn = NULL;
615         static CLI_POLICY_HND hnd;
616         NTSTATUS result;
617         uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
618
619         /* Look for existing connections */
620
621         for (conn = cm_conns; conn; conn = conn->next) {
622                 if (strequal(conn->domain, domain) &&
623                     strequal(conn->pipe_name, PIPE_SAMR) &&
624                     conn->pipe_data.samr.pipe_type == SAM_PIPE_GROUP &&
625                     conn->pipe_data.samr.rid == group_rid) {
626
627                         if (!connection_ok(conn)) {
628                                 DLIST_REMOVE(cm_conns, conn);
629                                 return NULL;
630                         }
631                 
632                         goto ok;
633                 }
634         }
635
636         /* Create a domain handle to open a user handle from */
637
638         if (!cm_get_sam_dom_handle(domain, domain_sid))
639                 return NULL;
640
641         for (conn = cm_conns; conn; conn = conn->next) {
642                 if (strequal(conn->domain, domain) &&
643                     strequal(conn->pipe_name, PIPE_SAMR) &&
644                     conn->pipe_data.samr.pipe_type == SAM_PIPE_DOM)
645                         basic_conn = conn;
646         }
647         
648         if (!basic_conn) {
649                 DEBUG(0, ("No domain sam handle was created!\n"));
650                 return NULL;
651         }
652
653         if (!(conn = (struct winbindd_cm_conn *)
654               malloc(sizeof(struct winbindd_cm_conn))))
655                 return NULL;
656         
657         ZERO_STRUCTP(conn);
658
659         fstrcpy(conn->domain, basic_conn->domain);
660         fstrcpy(conn->controller, basic_conn->controller);
661         fstrcpy(conn->pipe_name, basic_conn->pipe_name);
662         
663         conn->pipe_data.samr.pipe_type = SAM_PIPE_GROUP;
664         conn->cli = basic_conn->cli;
665         conn->pipe_data.samr.rid = group_rid;
666
667         result = cli_samr_open_group(conn->cli, conn->cli->mem_ctx,
668                                     &basic_conn->pol, des_access, group_rid,
669                                     &conn->pol);
670
671         if (!NT_STATUS_IS_OK(result))
672                 return NULL;
673
674         /* Add to list */
675
676         DLIST_ADD(cm_conns, conn);
677
678  ok:
679         hnd.pol = conn->pol;
680         hnd.cli = conn->cli;
681
682         return &hnd;
683 }
684
685 /* Get a handle on a netlogon pipe */
686
687 struct cli_state *cm_get_netlogon_cli(char *domain, unsigned char *trust_passwd)
688 {
689         struct winbindd_cm_conn conn;
690         NTSTATUS result;
691
692         /* Open an initial conection */
693
694         ZERO_STRUCT(conn);
695
696         if (!cm_open_connection(domain, PIPE_NETLOGON, &conn)) {
697                 DEBUG(3, ("Could not open a connection to %s\n", domain));
698                 return NULL;
699         }
700
701         result = cli_nt_setup_creds(conn.cli, trust_passwd);
702
703         if (!NT_STATUS_IS_OK(result)) {
704                 DEBUG(0, ("error connecting to domain password server: %s\n",
705                           get_nt_error_msg(result)));
706                 cli_shutdown(conn.cli);
707                 return NULL;
708         }
709
710         /* We only want the client handle from this structure */
711
712         return conn.cli;
713 }
714
715 /* Dump the current connection status */
716
717 static void dump_conn_list(void)
718 {
719         struct winbindd_cm_conn *con;
720
721         DEBUG(0, ("\tDomain          Controller      Pipe             Handle type\n"));
722
723         for(con = cm_conns; con; con = con->next) {
724                 char *msg;
725
726                 /* Display pipe info */
727                 
728                 asprintf(&msg, "\t%-15s %-15s %-16s", con->domain, 
729                          con->controller, con->pipe_name);
730                 
731                 /* Display sam specific info */
732                 
733                 if (strequal(con->pipe_name, PIPE_SAMR)) {
734                         char *msg2;
735                         
736                         asprintf(&msg2, "%s %-7s", msg, 
737                                  pipe_type(con->pipe_data.samr.pipe_type));
738
739                         free(msg);
740                         msg = msg2;
741                 }
742                 
743                 if (strequal(con->pipe_name, PIPE_SAMR) &&
744                     (con->pipe_data.samr.pipe_type == SAM_PIPE_USER ||
745                      con->pipe_data.samr.pipe_type == SAM_PIPE_GROUP)) {
746                         char *msg2;
747
748                         asprintf(&msg2, "%s %4xh", msg, 
749                                  con->pipe_data.samr.rid);
750
751                         free(msg);
752                         msg = msg2;
753                 }
754
755                 DEBUG(0, ("%s\n", msg));
756                 free(msg);
757         }
758 }
759
760 void winbindd_cm_status(void)
761 {
762         /* List open connections */
763
764         DEBUG(0, ("winbindd connection manager status:\n"));
765
766         if (cm_conns)
767                 dump_conn_list();
768         else
769                 DEBUG(0, ("\tNo active connections\n"));
770
771 }