The beginnings of alternative backends for winbindd
[nivanova/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.
42
43    The TNG design is quite good but I disagree with some aspects of the
44    implementation. -tpot
45
46  */
47
48 /*
49    TODO:
50
51      - I'm pretty annoyed by all the make_nmb_name() stuff.  It should be
52        moved down into another function.
53
54      - There needs to be a utility function in libsmb/namequery.c that does
55        cm_get_dc_name() 
56
57      - Take care when destroying cli_structs as they can be shared between
58        various sam handles.
59
60  */
61
62 #include "winbindd.h"
63
64 /* Global list of connections.  Initially a DLIST but can become a hash
65    table or whatever later. */
66
67 struct winbindd_cm_conn {
68         struct winbindd_cm_conn *prev, *next;
69         fstring domain;
70         fstring controller;
71         fstring pipe_name;
72         struct cli_state *cli;
73         POLICY_HND pol;
74 };
75
76 static struct winbindd_cm_conn *cm_conns = NULL;
77
78 /* Get a domain controller name.  Cache positive and negative lookups so we
79    don't go to the network too often when something is badly broken. */
80
81 #define GET_DC_NAME_CACHE_TIMEOUT 30 /* Seconds between dc lookups */
82
83 struct get_dc_name_cache {
84         fstring domain_name;
85         fstring srv_name;
86         time_t lookup_time;
87         struct get_dc_name_cache *prev, *next;
88 };
89
90 static BOOL cm_get_dc_name(char *domain, fstring srv_name)
91 {
92         static struct get_dc_name_cache *get_dc_name_cache;
93         struct get_dc_name_cache *dcc;
94         struct in_addr *ip_list, dc_ip;
95         int count, i;
96
97         /* Check the cache for previous lookups */
98
99         for (dcc = get_dc_name_cache; dcc; dcc = dcc->next) {
100
101                 if (!strequal(domain, dcc->domain_name))
102                         continue; /* Not our domain */
103
104                 if ((time(NULL) - dcc->lookup_time) > 
105                     GET_DC_NAME_CACHE_TIMEOUT) {
106
107                         /* Cache entry has expired, delete it */
108
109                         DEBUG(10, ("get_dc_name_cache entry expired for %s\n", domain));
110
111                         DLIST_REMOVE(get_dc_name_cache, dcc);
112                         SAFE_FREE(dcc);
113
114                         break;
115                 }
116
117                 /* Return a positive or negative lookup for this domain */
118
119                 if (dcc->srv_name[0]) {
120                         DEBUG(10, ("returning positive get_dc_name_cache entry for %s\n", domain));
121                         fstrcpy(srv_name, dcc->srv_name);
122                         return True;
123                 } else {
124                         DEBUG(10, ("returning negative get_dc_name_cache entry for %s\n", domain));
125                         return False;
126                 }
127         }
128
129         /* Add cache entry for this lookup. */
130
131         DEBUG(10, ("Creating get_dc_name_cache entry for %s\n", domain));
132
133         if (!(dcc = (struct get_dc_name_cache *) 
134               malloc(sizeof(struct get_dc_name_cache))))
135                 return False;
136
137         ZERO_STRUCTP(dcc);
138
139         fstrcpy(dcc->domain_name, domain);
140         dcc->lookup_time = time(NULL);
141
142         DLIST_ADD(get_dc_name_cache, dcc);
143
144         /* Lookup domain controller name */
145                 
146         if (!get_dc_list(False, domain, &ip_list, &count)) {
147                 DEBUG(3, ("Could not look up dc's for domain %s\n", domain));
148                 return False;
149         }
150                 
151         /* Firstly choose a PDC/BDC who has the same network address as any
152            of our interfaces. */
153         
154         for (i = 0; i < count; i++) {
155                 if(is_local_net(ip_list[i]))
156                         goto got_ip;
157         }
158
159         if (count == 0) {
160                 DEBUG(3, ("No domain controllers for domain %s\n", domain));
161                 return False;
162         }
163         
164         i = (sys_random() % count);
165         
166  got_ip:
167         dc_ip = ip_list[i];
168         SAFE_FREE(ip_list);
169                 
170         /* We really should be doing a GETDC call here rather than a node
171            status lookup. */
172
173         if (!name_status_find(domain, 0x1c, 0x20, dc_ip, srv_name)) {
174                 DEBUG(3, ("Error looking up DC name for %s in domain %s\n", inet_ntoa(dc_ip), domain));
175                 return False;
176         }
177
178         /* We have a name so make the cache entry positive now */
179
180         fstrcpy(dcc->srv_name, srv_name);
181
182         return True;
183 }
184
185 /* Open a new smb pipe connection to a DC on a given domain.  Cache
186    negative creation attempts so we don't try and connect to broken
187    machines too often. */
188
189 #define OPEN_CONNECTION_CACHE_TIMEOUT 30 /* Seconds between attempts */
190
191 struct open_connection_cache {
192         fstring domain_name;
193         fstring controller;
194         time_t lookup_time;
195         struct open_connection_cache *prev, *next;
196 };
197
198 static BOOL cm_open_connection(char *domain, char *pipe_name,
199                                struct winbindd_cm_conn *new_conn)
200 {
201         static struct open_connection_cache *open_connection_cache;
202         struct open_connection_cache *occ;
203         struct nmb_name calling, called;
204         extern pstring global_myname;
205         fstring dest_host;
206         struct in_addr dest_ip;
207         BOOL result = False;
208         struct ntuser_creds creds;
209
210         fstrcpy(new_conn->domain, domain);
211         fstrcpy(new_conn->pipe_name, pipe_name);
212         
213         /* Look for a domain controller for this domain.  Negative results
214                 are cached so don't bother applying the caching for this
215                 function just yet.  */
216
217         if (!cm_get_dc_name(domain, new_conn->controller))
218                 goto done;
219
220         /* Return false if we have tried to look up this domain and netbios
221                 name before and failed. */
222
223         for (occ = open_connection_cache; occ; occ = occ->next) {
224                 
225                 if (!(strequal(domain, occ->domain_name) &&
226                       strequal(new_conn->controller, occ->controller)))
227                         continue; /* Not our domain */
228
229                 if ((time(NULL) - occ->lookup_time) > 
230                     OPEN_CONNECTION_CACHE_TIMEOUT) {
231
232                         /* Cache entry has expired, delete it */
233
234                         DEBUG(10, ("cm_open_connection cache entry expired for %s, %s\n", domain, new_conn->controller));
235
236                         DLIST_REMOVE(open_connection_cache, occ);
237                         free(occ);
238
239                         break;
240                 }
241
242                 /* The timeout hasn't expired yet so return false */
243
244                 DEBUG(10, ("returning negative open_connection_cache entry for %s, %s\n", domain, new_conn->controller));
245
246                 goto done;
247         }
248
249         /* Initialise SMB connection */
250
251         if (!(new_conn->cli = cli_initialise(NULL)))
252                 goto done;
253
254         if (!resolve_srv_name(new_conn->controller, dest_host, &dest_ip))
255                 goto done;
256
257         make_nmb_name(&called, dns_to_netbios_name(new_conn->controller), 0x20);
258         make_nmb_name(&calling, dns_to_netbios_name(global_myname), 0);
259
260         ZERO_STRUCT(creds);
261         creds.pwd.null_pwd = 1;
262
263         cli_init_creds(new_conn->cli, &creds);
264
265         if (!cli_establish_connection(new_conn->cli, new_conn->controller, 
266                                       &dest_ip, &calling, &called, "IPC$", 
267                                       "IPC", False, True))
268                 goto done;
269
270         if (!cli_nt_session_open (new_conn->cli, pipe_name))
271                 goto done;
272
273         result = True;
274
275  done:
276
277         /* Create negative lookup cache entry for this domain and controller */
278
279         if (!result) {
280                 if (!(occ = (struct open_connection_cache *)
281                       malloc(sizeof(struct open_connection_cache))))
282                         return False;
283
284                 ZERO_STRUCTP(occ);
285
286                 fstrcpy(occ->domain_name, domain);
287                 fstrcpy(occ->controller, new_conn->controller);
288                 occ->lookup_time = time(NULL);
289                 
290                 DLIST_ADD(open_connection_cache, occ);
291         }
292
293         if (!result && new_conn->cli)
294                 cli_shutdown(new_conn->cli);
295
296         return result;
297 }
298
299 /* Return true if a connection is still alive */
300
301 static BOOL connection_ok(struct winbindd_cm_conn *conn)
302 {
303         if (!conn->cli->initialised)
304                 return False;
305
306         if (conn->cli->fd == -1)
307                 return False;
308         
309         return True;
310 }
311
312 /* Return a LSA policy handle on a domain */
313
314 CLI_POLICY_HND *cm_get_lsa_handle(char *domain)
315 {
316         struct winbindd_cm_conn *conn;
317         uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
318         NTSTATUS result;
319         static CLI_POLICY_HND hnd;
320
321         /* Look for existing connections */
322
323         for (conn = cm_conns; conn; conn = conn->next) {
324                 if (strequal(conn->domain, domain) && 
325                     strequal(conn->pipe_name, PIPE_LSARPC)) {
326
327                         if (!connection_ok(conn)) {
328                                 DLIST_REMOVE(cm_conns, conn);
329                                 return NULL;
330                         }
331
332                         goto ok;
333                 }
334         }
335
336         /* Create a new one */
337
338         if (!(conn = (struct winbindd_cm_conn *) malloc(sizeof(struct winbindd_cm_conn))))
339                 return NULL;
340
341         ZERO_STRUCTP(conn);
342
343         if (!cm_open_connection(domain, PIPE_LSARPC, conn)) {
344                 DEBUG(3, ("Could not connect to a dc for domain %s\n", domain));
345                 return NULL;
346         }
347
348         result = cli_lsa_open_policy(conn->cli, conn->cli->mem_ctx, False, 
349                                      des_access, &conn->pol);
350
351         if (!NT_STATUS_IS_OK(result))
352                 return NULL;
353
354         /* Add to list */
355
356         DLIST_ADD(cm_conns, conn);
357
358  ok:
359         hnd.pol = conn->pol;
360         hnd.cli = conn->cli;
361
362         return &hnd;
363 }
364
365 /* Return a SAM policy handle on a domain */
366
367 CLI_POLICY_HND *cm_get_sam_handle(char *domain)
368
369         struct winbindd_cm_conn *conn;
370         uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
371         NTSTATUS result;
372         static CLI_POLICY_HND hnd;
373
374         /* Look for existing connections */
375
376         for (conn = cm_conns; conn; conn = conn->next) {
377                 if (strequal(conn->domain, domain) && strequal(conn->pipe_name, PIPE_SAMR)) {
378
379                         if (!connection_ok(conn)) {
380                                 DLIST_REMOVE(cm_conns, conn);
381                                 return NULL;
382                         }
383
384                         goto ok;
385                 }
386         }
387
388         /* Create a new one */
389
390         if (!(conn = (struct winbindd_cm_conn *) 
391               malloc(sizeof(struct winbindd_cm_conn))))
392                 return NULL;
393
394         ZERO_STRUCTP(conn);
395
396         if (!cm_open_connection(domain, PIPE_SAMR, conn)) {
397                 DEBUG(3, ("Could not connect to a dc for domain %s\n", domain));
398                 return NULL;
399         }
400
401         result = cli_samr_connect(conn->cli, conn->cli->mem_ctx,
402                                   des_access, &conn->pol);
403
404         if (!NT_STATUS_IS_OK(result))
405                 return NULL;
406
407         /* Add to list */
408
409         DLIST_ADD(cm_conns, conn);
410
411  ok:
412         hnd.pol = conn->pol;
413         hnd.cli = conn->cli;
414
415         return &hnd;        
416 }
417
418 #if 0
419
420 /* Return a SAM domain policy handle on a domain */
421
422 CLI_POLICY_HND *cm_get_sam_dom_handle(char *domain, DOM_SID *domain_sid)
423 {
424         struct winbindd_cm_conn *conn, *basic_conn = NULL;
425         static CLI_POLICY_HND hnd;
426         NTSTATUS result;
427         uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
428
429         /* Look for existing connections */
430
431         for (conn = cm_conns; conn; conn = conn->next) {
432                 if (strequal(conn->domain, domain) &&
433                     strequal(conn->pipe_name, PIPE_SAMR) &&
434                     conn->pipe_data.samr.pipe_type == SAM_PIPE_DOM) {
435
436                         if (!connection_ok(conn)) {
437                                 DLIST_REMOVE(cm_conns, conn);
438                                 return NULL;
439                         }
440
441                         goto ok;
442                 }
443         }
444
445         /* Create a basic handle to open a domain handle from */
446
447         if (!cm_get_sam_handle(domain))
448                 return False;
449
450         for (conn = cm_conns; conn; conn = conn->next) {
451                 if (strequal(conn->domain, domain) &&
452                     strequal(conn->pipe_name, PIPE_SAMR) &&
453                     conn->pipe_data.samr.pipe_type == SAM_PIPE_BASIC)
454                         basic_conn = conn;
455         }
456         
457         if (!(conn = (struct winbindd_cm_conn *)
458               malloc(sizeof(struct winbindd_cm_conn))))
459                 return NULL;
460         
461         ZERO_STRUCTP(conn);
462
463         fstrcpy(conn->domain, basic_conn->domain);
464         fstrcpy(conn->controller, basic_conn->controller);
465         fstrcpy(conn->pipe_name, basic_conn->pipe_name);
466
467         conn->pipe_data.samr.pipe_type = SAM_PIPE_DOM;
468         conn->cli = basic_conn->cli;
469
470         result = cli_samr_open_domain(conn->cli, conn->cli->mem_ctx,
471                                       &basic_conn->pol, des_access, 
472                                       domain_sid, &conn->pol);
473
474         if (!NT_STATUS_IS_OK(result))
475                 return NULL;
476
477         /* Add to list */
478
479         DLIST_ADD(cm_conns, conn);
480
481  ok:
482         hnd.pol = conn->pol;
483         hnd.cli = conn->cli;
484
485         return &hnd;
486 }
487
488 /* Return a SAM policy handle on a domain user */
489
490 CLI_POLICY_HND *cm_get_sam_user_handle(char *domain, DOM_SID *domain_sid,
491                                        uint32 user_rid)
492 {
493         struct winbindd_cm_conn *conn, *basic_conn = NULL;
494         static CLI_POLICY_HND hnd;
495         NTSTATUS result;
496         uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
497
498         /* Look for existing connections */
499
500         for (conn = cm_conns; conn; conn = conn->next) {
501                 if (strequal(conn->domain, domain) &&
502                     strequal(conn->pipe_name, PIPE_SAMR) &&
503                     conn->pipe_data.samr.pipe_type == SAM_PIPE_USER &&
504                     conn->pipe_data.samr.rid == user_rid) {
505
506                         if (!connection_ok(conn)) {
507                                 DLIST_REMOVE(cm_conns, conn);
508                                 return NULL;
509                         }
510                 
511                         goto ok;
512                 }
513         }
514
515         /* Create a domain handle to open a user handle from */
516
517         if (!cm_get_sam_dom_handle(domain, domain_sid))
518                 return NULL;
519
520         for (conn = cm_conns; conn; conn = conn->next) {
521                 if (strequal(conn->domain, domain) &&
522                     strequal(conn->pipe_name, PIPE_SAMR) &&
523                     conn->pipe_data.samr.pipe_type == SAM_PIPE_DOM)
524                         basic_conn = conn;
525         }
526         
527         if (!basic_conn) {
528                 DEBUG(0, ("No domain sam handle was created!\n"));
529                 return NULL;
530         }
531
532         if (!(conn = (struct winbindd_cm_conn *)
533               malloc(sizeof(struct winbindd_cm_conn))))
534                 return NULL;
535         
536         ZERO_STRUCTP(conn);
537
538         fstrcpy(conn->domain, basic_conn->domain);
539         fstrcpy(conn->controller, basic_conn->controller);
540         fstrcpy(conn->pipe_name, basic_conn->pipe_name);
541         
542         conn->pipe_data.samr.pipe_type = SAM_PIPE_USER;
543         conn->cli = basic_conn->cli;
544         conn->pipe_data.samr.rid = user_rid;
545
546         result = cli_samr_open_user(conn->cli, conn->cli->mem_ctx,
547                                     &basic_conn->pol, des_access, user_rid,
548                                     &conn->pol);
549
550         if (!NT_STATUS_IS_OK(result))
551                 return NULL;
552
553         /* Add to list */
554
555         DLIST_ADD(cm_conns, conn);
556
557  ok:
558         hnd.pol = conn->pol;
559         hnd.cli = conn->cli;
560
561         return &hnd;
562 }
563
564 /* Return a SAM policy handle on a domain group */
565
566 CLI_POLICY_HND *cm_get_sam_group_handle(char *domain, DOM_SID *domain_sid,
567                                         uint32 group_rid)
568 {
569         struct winbindd_cm_conn *conn, *basic_conn = NULL;
570         static CLI_POLICY_HND hnd;
571         NTSTATUS result;
572         uint32 des_access = SEC_RIGHTS_MAXIMUM_ALLOWED;
573
574         /* Look for existing connections */
575
576         for (conn = cm_conns; conn; conn = conn->next) {
577                 if (strequal(conn->domain, domain) &&
578                     strequal(conn->pipe_name, PIPE_SAMR) &&
579                     conn->pipe_data.samr.pipe_type == SAM_PIPE_GROUP &&
580                     conn->pipe_data.samr.rid == group_rid) {
581
582                         if (!connection_ok(conn)) {
583                                 DLIST_REMOVE(cm_conns, conn);
584                                 return NULL;
585                         }
586                 
587                         goto ok;
588                 }
589         }
590
591         /* Create a domain handle to open a user handle from */
592
593         if (!cm_get_sam_dom_handle(domain, domain_sid))
594                 return NULL;
595
596         for (conn = cm_conns; conn; conn = conn->next) {
597                 if (strequal(conn->domain, domain) &&
598                     strequal(conn->pipe_name, PIPE_SAMR) &&
599                     conn->pipe_data.samr.pipe_type == SAM_PIPE_DOM)
600                         basic_conn = conn;
601         }
602         
603         if (!basic_conn) {
604                 DEBUG(0, ("No domain sam handle was created!\n"));
605                 return NULL;
606         }
607
608         if (!(conn = (struct winbindd_cm_conn *)
609               malloc(sizeof(struct winbindd_cm_conn))))
610                 return NULL;
611         
612         ZERO_STRUCTP(conn);
613
614         fstrcpy(conn->domain, basic_conn->domain);
615         fstrcpy(conn->controller, basic_conn->controller);
616         fstrcpy(conn->pipe_name, basic_conn->pipe_name);
617         
618         conn->pipe_data.samr.pipe_type = SAM_PIPE_GROUP;
619         conn->cli = basic_conn->cli;
620         conn->pipe_data.samr.rid = group_rid;
621
622         result = cli_samr_open_group(conn->cli, conn->cli->mem_ctx,
623                                     &basic_conn->pol, des_access, group_rid,
624                                     &conn->pol);
625
626         if (!NT_STATUS_IS_OK(result))
627                 return NULL;
628
629         /* Add to list */
630
631         DLIST_ADD(cm_conns, conn);
632
633  ok:
634         hnd.pol = conn->pol;
635         hnd.cli = conn->cli;
636
637         return &hnd;
638 }
639
640 #endif
641
642 /* Get a handle on a netlogon pipe.  This is a bit of a hack to re-use the
643    netlogon pipe as no handle is returned. */
644
645 NTSTATUS cm_get_netlogon_cli(char *domain, unsigned char *trust_passwd,
646                              struct cli_state **cli)
647 {
648         struct winbindd_cm_conn conn;
649         NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
650
651         /* Open an initial conection */
652
653         ZERO_STRUCT(conn);
654
655         if (!cm_open_connection(domain, PIPE_NETLOGON, &conn)) {
656                 DEBUG(3, ("Could not open a connection to %s\n", domain));
657                 return result;
658         }
659
660         result = cli_nt_setup_creds(conn.cli, trust_passwd);
661
662         if (!NT_STATUS_IS_OK(result)) {
663                 DEBUG(0, ("error connecting to domain password server: %s\n",
664                         get_nt_error_msg(result)));
665                         cli_shutdown(conn.cli);
666                         return result;
667         }
668
669         if (cli)
670                 *cli = conn.cli;
671
672         return result;
673 }
674
675 /* Dump the current connection status */
676
677 static void dump_conn_list(void)
678 {
679         struct winbindd_cm_conn *con;
680
681         DEBUG(0, ("\tDomain          Controller      Pipe\n"));
682
683         for(con = cm_conns; con; con = con->next) {
684                 char *msg;
685
686                 /* Display pipe info */
687                 
688                 asprintf(&msg, "\t%-15s %-15s %-16s", con->domain, con->controller, con->pipe_name);
689                 
690                 DEBUG(0, ("%s\n", msg));
691                 free(msg);
692         }
693 }
694
695 void winbindd_cm_status(void)
696 {
697         /* List open connections */
698
699         DEBUG(0, ("winbindd connection manager status:\n"));
700
701         if (cm_conns)
702                 dump_conn_list();
703         else
704                 DEBUG(0, ("\tNo active connections\n"));
705 }