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