r21800: Check-in the DFS rewrite. I am still testing this but it
[ira/wip.git] / source / libsmb / clidfs.c
1 /* 
2    Unix SMB/CIFS implementation.
3    client connect/disconnect routines
4    Copyright (C) Andrew Tridgell                  1994-1998
5    Copyright (C) Gerald (Jerry) Carter            2004
6    Copyright (C) Jeremy Allison                   2007
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 "includes.h"
24
25 /********************************************************************
26  Important point.
27
28  DFS paths are *always* of the form \server\share\<pathname> (the \ characters
29  are not C escaped here).
30
31  - but if we're using POSIX paths then <pathname> may contain
32    '/' separators, not '\\' separators. So cope with '\\' or '/'
33    as a separator when looking at the pathname part.... JRA.
34 ********************************************************************/
35
36 struct client_connection {
37         struct client_connection *prev, *next;
38         struct cli_state *cli;
39         pstring mount;
40 };
41
42 /* global state....globals reek! */
43
44 static pstring username;
45 static pstring password;
46 static BOOL use_kerberos;
47 static BOOL got_pass;
48 static int signing_state;
49 int max_protocol = PROTOCOL_NT1;
50
51 static int port;
52 static int name_type = 0x20;
53 static BOOL have_ip;
54 static struct in_addr dest_ip;
55
56 static struct client_connection *connections;
57
58 /********************************************************************
59  Return a connection to a server.
60 ********************************************************************/
61
62 static struct cli_state *do_connect( const char *server, const char *share,
63                                      BOOL show_sessetup )
64 {
65         struct cli_state *c = NULL;
66         struct nmb_name called, calling;
67         const char *server_n;
68         struct in_addr ip;
69         pstring servicename;
70         char *sharename;
71         fstring newserver, newshare;
72         
73         /* make a copy so we don't modify the global string 'service' */
74         pstrcpy(servicename, share);
75         sharename = servicename;
76         if (*sharename == '\\') {
77                 server = sharename+2;
78                 sharename = strchr_m(server,'\\');
79                 if (!sharename) return NULL;
80                 *sharename = 0;
81                 sharename++;
82         }
83
84         server_n = server;
85         
86         zero_ip(&ip);
87
88         make_nmb_name(&calling, global_myname(), 0x0);
89         make_nmb_name(&called , server, name_type);
90
91  again:
92         zero_ip(&ip);
93         if (have_ip) 
94                 ip = dest_ip;
95
96         /* have to open a new connection */
97         if (!(c=cli_initialise()) || (cli_set_port(c, port) != port) ||
98             !cli_connect(c, server_n, &ip)) {
99                 d_printf("Connection to %s failed\n", server_n);
100                 return NULL;
101         }
102
103         c->protocol = max_protocol;
104         c->use_kerberos = use_kerberos;
105         cli_setup_signing_state(c, signing_state);
106                 
107
108         if (!cli_session_request(c, &calling, &called)) {
109                 char *p;
110                 d_printf("session request to %s failed (%s)\n", 
111                          called.name, cli_errstr(c));
112                 cli_shutdown(c);
113                 c = NULL;
114                 if ((p=strchr_m(called.name, '.'))) {
115                         *p = 0;
116                         goto again;
117                 }
118                 if (strcmp(called.name, "*SMBSERVER")) {
119                         make_nmb_name(&called , "*SMBSERVER", 0x20);
120                         goto again;
121                 }
122                 return NULL;
123         }
124
125         DEBUG(4,(" session request ok\n"));
126
127         if (!cli_negprot(c)) {
128                 d_printf("protocol negotiation failed\n");
129                 cli_shutdown(c);
130                 return NULL;
131         }
132
133         if (!got_pass) {
134                 char *pass = getpass("Password: ");
135                 if (pass) {
136                         pstrcpy(password, pass);
137                         got_pass = 1;
138                 }
139         }
140
141         if (!NT_STATUS_IS_OK(cli_session_setup(c, username, 
142                                                password, strlen(password),
143                                                password, strlen(password),
144                                                lp_workgroup()))) {
145                 /* if a password was not supplied then try again with a null username */
146                 if (password[0] || !username[0] || use_kerberos ||
147                     !NT_STATUS_IS_OK(cli_session_setup(c, "", "", 0, "", 0,
148                                                        lp_workgroup()))) { 
149                         d_printf("session setup failed: %s\n", cli_errstr(c));
150                         if (NT_STATUS_V(cli_nt_error(c)) == 
151                             NT_STATUS_V(NT_STATUS_MORE_PROCESSING_REQUIRED))
152                                 d_printf("did you forget to run kinit?\n");
153                         cli_shutdown(c);
154                         return NULL;
155                 }
156                 d_printf("Anonymous login successful\n");
157         }
158
159         if ( show_sessetup ) {
160                 if (*c->server_domain) {
161                         DEBUG(0,("Domain=[%s] OS=[%s] Server=[%s]\n",
162                                 c->server_domain,c->server_os,c->server_type));
163                 } else if (*c->server_os || *c->server_type){
164                         DEBUG(0,("OS=[%s] Server=[%s]\n",
165                                  c->server_os,c->server_type));
166                 }               
167         }
168         DEBUG(4,(" session setup ok\n"));
169
170         /* here's the fun part....to support 'msdfs proxy' shares
171            (on Samba or windows) we have to issues a TRANS_GET_DFS_REFERRAL 
172            here before trying to connect to the original share.
173            check_dfs_proxy() will fail if it is a normal share. */
174
175         if ( (c->capabilities & CAP_DFS) && cli_check_msdfs_proxy( c, sharename, newserver, newshare ) ) {
176                 cli_shutdown(c);
177                 return do_connect( newserver, newshare, False );
178         }
179
180         /* must be a normal share */
181
182         if (!cli_send_tconX(c, sharename, "?????", password, strlen(password)+1)) {
183                 d_printf("tree connect failed: %s\n", cli_errstr(c));
184                 cli_shutdown(c);
185                 return NULL;
186         }
187
188         DEBUG(4,(" tconx ok\n"));
189
190         return c;
191 }
192
193 /****************************************************************************
194 ****************************************************************************/
195
196 static void cli_cm_set_mntpoint( struct cli_state *c, const char *mnt )
197 {
198         struct client_connection *p;
199         int i;
200
201         for ( p=connections,i=0; p; p=p->next,i++ ) {
202                 if ( strequal(p->cli->desthost, c->desthost) && strequal(p->cli->share, c->share) )
203                         break;
204         }
205         
206         if ( p ) {
207                 pstrcpy( p->mount, mnt );
208                 clean_name(p->mount);
209         }
210 }
211
212 /****************************************************************************
213 ****************************************************************************/
214
215 const char *cli_cm_get_mntpoint( struct cli_state *c )
216 {
217         struct client_connection *p;
218         int i;
219
220         for ( p=connections,i=0; p; p=p->next,i++ ) {
221                 if ( strequal(p->cli->desthost, c->desthost) && strequal(p->cli->share, c->share) )
222                         break;
223         }
224         
225         if ( p )
226                 return p->mount;
227                 
228         return NULL;
229 }
230
231 /********************************************************************
232  Add a new connection to the list
233 ********************************************************************/
234
235 static struct cli_state *cli_cm_connect( const char *server,
236                                         const char *share,
237                                         BOOL show_hdr)
238 {
239         struct client_connection *node;
240         
241         node = SMB_XMALLOC_P( struct client_connection );
242         
243         node->cli = do_connect( server, share, show_hdr );
244
245         if ( !node->cli ) {
246                 SAFE_FREE( node );
247                 return NULL;
248         }
249
250         DLIST_ADD( connections, node );
251
252         cli_cm_set_mntpoint( node->cli, "" );
253
254         return node->cli;
255
256 }
257
258 /********************************************************************
259  Return a connection to a server.
260 ********************************************************************/
261
262 static struct cli_state *cli_cm_find( const char *server, const char *share )
263 {
264         struct client_connection *p;
265
266         for ( p=connections; p; p=p->next ) {
267                 if ( strequal(server, p->cli->desthost) && strequal(share,p->cli->share) )
268                         return p->cli;
269         }
270
271         return NULL;
272 }
273
274 /****************************************************************************
275  open a client connection to a \\server\share.  Set's the current *cli 
276  global variable as a side-effect (but only if the connection is successful).
277 ****************************************************************************/
278
279 struct cli_state *cli_cm_open(const char *server,
280                                 const char *share,
281                                 BOOL show_hdr)
282 {
283         struct cli_state *c;
284         
285         /* try to reuse an existing connection */
286
287         c = cli_cm_find( server, share );
288         
289         if ( !c ) {
290                 c = cli_cm_connect(server, share, show_hdr);
291         }
292
293         return c;
294 }
295
296 /****************************************************************************
297 ****************************************************************************/
298
299 void cli_cm_shutdown( void )
300 {
301
302         struct client_connection *p, *x;
303
304         for ( p=connections; p; ) {
305                 cli_shutdown( p->cli );
306                 x = p;
307                 p = p->next;
308
309                 SAFE_FREE( x );
310         }
311
312         connections = NULL;
313         return;
314 }
315
316 /****************************************************************************
317 ****************************************************************************/
318
319 void cli_cm_display(void)
320 {
321         struct client_connection *p;
322         int i;
323
324         for ( p=connections,i=0; p; p=p->next,i++ ) {
325                 d_printf("%d:\tserver=%s, share=%s\n", 
326                         i, p->cli->desthost, p->cli->share );
327         }
328 }
329
330 /****************************************************************************
331 ****************************************************************************/
332
333 void cli_cm_set_credentials( struct user_auth_info *user )
334 {
335         pstrcpy( username, user->username );
336         
337         if ( user->got_pass ) {
338                 pstrcpy( password, user->password );
339                 got_pass = True;
340         }
341         
342         use_kerberos = user->use_kerberos;      
343         signing_state = user->signing_state;
344 }
345
346 /****************************************************************************
347 ****************************************************************************/
348
349 void cli_cm_set_port( int port_number )
350 {
351         port = port_number;
352 }
353
354 /****************************************************************************
355 ****************************************************************************/
356
357 void cli_cm_set_dest_name_type( int type )
358 {
359         name_type = type;
360 }
361
362 /****************************************************************************
363 ****************************************************************************/
364
365 void cli_cm_set_dest_ip(struct in_addr ip )
366 {
367         dest_ip = ip;
368         have_ip = True;
369 }
370
371 /**********************************************************************
372  split a dfs path into the server, share name, and extrapath components
373 **********************************************************************/
374
375 static void split_dfs_path( const char *nodepath, fstring server, fstring share, pstring extrapath )
376 {
377         char *p, *q;
378         pstring path;
379
380         pstrcpy( path, nodepath );
381
382         if ( path[0] != '\\' ) {
383                 return;
384         }
385
386         p = strchr_m( path + 1, '\\' );
387         if ( !p ) {
388                 return;
389         }
390
391         *p = '\0';
392         p++;
393
394         /* Look for any extra/deep path */
395         q = strchr_m(p, '\\');
396         if (q != NULL) {
397                 *q = '\0';
398                 q++;
399                 pstrcpy( extrapath, q );
400         } else {
401                 pstrcpy( extrapath, '\0' );
402         }
403         
404         fstrcpy( share, p );
405         fstrcpy( server, &path[1] );
406 }
407
408 /****************************************************************************
409  Return the original path truncated at the directory component before
410  the first wildcard character. Trust the caller to provide a NULL 
411  terminated string
412 ****************************************************************************/
413
414 static void clean_path(const char *path, pstring path_out)
415 {
416         size_t len;
417         char *p1, *p2, *p;
418                 
419         /* No absolute paths. */
420         while (IS_DIRECTORY_SEP(*path)) {
421                 path++;
422         }
423
424         pstrcpy(path_out, path);
425
426         p1 = strchr_m(path_out, '*');
427         p2 = strchr_m(path_out, '?');
428
429         if (p1 || p2) {
430                 if (p1 && p2) {
431                         p = MIN(p1,p2);
432                 } else if (!p1) {
433                         p = p2;
434                 } else {
435                         p = p1;
436                 }
437                 *p = '\0';
438
439                 /* Now go back to the start of this component. */
440                 p1 = strrchr_m(path_out, '/');
441                 p2 = strrchr_m(path_out, '\\');
442                 p = MAX(p1,p2);
443                 if (p) {
444                         *p = '\0';
445                 }
446         }
447
448         /* Strip any trailing separator */
449
450         len = strlen(path_out);
451         if ( (len > 0) && IS_DIRECTORY_SEP(path_out[len-1])) {
452                 path_out[len-1] = '\0';
453         }
454 }
455
456 /****************************************************************************
457 ****************************************************************************/
458
459 static void cli_dfs_make_full_path( struct cli_state *cli,
460                                         const char *dir,
461                                         pstring path_out)
462 {
463         /* Ensure the extrapath doesn't start with a separator. */
464         while (IS_DIRECTORY_SEP(*dir)) {
465                 dir++;
466         }
467
468         pstr_sprintf( path_out, "\\%s\\%s\\%s", cli->desthost, cli->share, dir);
469 }
470
471 /********************************************************************
472  check for dfs referral
473 ********************************************************************/
474
475 static BOOL cli_dfs_check_error( struct cli_state *cli, NTSTATUS status )
476 {
477         uint32 flgs2 = SVAL(cli->inbuf,smb_flg2);
478
479         /* only deal with DS when we negotiated NT_STATUS codes and UNICODE */
480
481         if ( !( (flgs2&FLAGS2_32_BIT_ERROR_CODES) && (flgs2&FLAGS2_UNICODE_STRINGS) ) )
482                 return False;
483
484         if ( NT_STATUS_EQUAL( status, NT_STATUS(IVAL(cli->inbuf,smb_rcls)) ) )
485                 return True;
486
487         return False;
488 }
489
490 /********************************************************************
491  get the dfs referral link
492 ********************************************************************/
493
494 BOOL cli_dfs_get_referral( struct cli_state *cli,
495                         const char *path, 
496                         CLIENT_DFS_REFERRAL**refs,
497                         size_t *num_refs,
498                         uint16 *consumed)
499 {
500         unsigned int data_len = 0;
501         unsigned int param_len = 0;
502         uint16 setup = TRANSACT2_GET_DFS_REFERRAL;
503         char param[sizeof(pstring)+2];
504         pstring data;
505         char *rparam=NULL, *rdata=NULL;
506         char *p;
507         size_t pathlen = 2*(strlen(path)+1);
508         uint16 num_referrals;
509         CLIENT_DFS_REFERRAL *referrals = NULL;
510         
511         memset(param, 0, sizeof(param));
512         SSVAL(param, 0, 0x03);  /* max referral level */
513         p = &param[2];
514
515         p += clistr_push(cli, p, path, MIN(pathlen, sizeof(param)-2), STR_TERMINATE);
516         param_len = PTR_DIFF(p, param);
517
518         if (!cli_send_trans(cli, SMBtrans2,
519                 NULL,                        /* name */
520                 -1, 0,                          /* fid, flags */
521                 &setup, 1, 0,                   /* setup, length, max */
522                 param, param_len, 2,            /* param, length, max */
523                 (char *)&data,  data_len, cli->max_xmit /* data, length, max */
524                 )) {
525                         return False;
526         }
527
528         if (!cli_receive_trans(cli, SMBtrans2,
529                 &rparam, &param_len,
530                 &rdata, &data_len)) {
531                         return False;
532         }
533         
534         *consumed     = SVAL( rdata, 0 );
535         num_referrals = SVAL( rdata, 2 );
536         
537         if ( num_referrals != 0 ) {
538                 uint16 ref_version;
539                 uint16 ref_size;
540                 int i;
541                 uint16 node_offset;
542
543                 referrals = SMB_XMALLOC_ARRAY( CLIENT_DFS_REFERRAL, num_referrals );
544
545                 /* start at the referrals array */
546         
547                 p = rdata+8;
548                 for ( i=0; i<num_referrals; i++ ) {
549                         ref_version = SVAL( p, 0 );
550                         ref_size    = SVAL( p, 2 );
551                         node_offset = SVAL( p, 16 );
552                         
553                         if ( ref_version != 3 ) {
554                                 p += ref_size;
555                                 continue;
556                         }
557                         
558                         referrals[i].proximity = SVAL( p, 8 );
559                         referrals[i].ttl       = SVAL( p, 10 );
560
561                         clistr_pull( cli, referrals[i].dfspath, p+node_offset, 
562                                 sizeof(referrals[i].dfspath), -1, STR_TERMINATE|STR_UNICODE );
563
564                         p += ref_size;
565                 }
566         }
567         
568         *num_refs = num_referrals;
569         *refs = referrals;
570
571         SAFE_FREE(rdata);
572         SAFE_FREE(rparam);
573
574         return True;
575 }
576
577
578 /********************************************************************
579 ********************************************************************/
580
581 BOOL cli_resolve_path( const char *mountpt,
582                         struct cli_state *rootcli,
583                         const char *path,
584                         struct cli_state **targetcli,
585                         pstring targetpath)
586 {
587         CLIENT_DFS_REFERRAL *refs = NULL;
588         size_t num_refs;
589         uint16 consumed;
590         struct cli_state *cli_ipc;
591         pstring dfs_path, cleanpath, extrapath;
592         int pathlen;
593         fstring server, share;
594         struct cli_state *newcli;
595         pstring newpath;
596         pstring newmount;
597         char *ppath, *temppath = NULL;
598         
599         SMB_STRUCT_STAT sbuf;
600         uint32 attributes;
601         
602         if ( !rootcli || !path || !targetcli ) {
603                 return False;
604         }
605                 
606         /* Don't do anything if this is not a DFS root. */
607
608         if ( !rootcli->dfsroot) {
609                 *targetcli = rootcli;
610                 pstrcpy( targetpath, path );
611                 return True;
612         }
613
614         *targetcli = NULL;
615
616         /* Send a trans2_query_path_info to check for a referral. */
617
618         clean_path(path, cleanpath);
619         cli_dfs_make_full_path(rootcli, cleanpath, dfs_path );
620
621         if (cli_qpathinfo_basic( rootcli, dfs_path, &sbuf, &attributes ) ) {
622                 /* This is an ordinary path, just return it. */
623                 *targetcli = rootcli;
624                 pstrcpy( targetpath, path );
625                 goto done;
626         }
627
628         /* Special case where client asked for a path that does not exist */
629
630         if ( cli_dfs_check_error(rootcli, NT_STATUS_OBJECT_NAME_NOT_FOUND) ) {
631                 *targetcli = rootcli;
632                 pstrcpy( targetpath, path );
633                 goto done;
634         }
635
636         /* We got an error, check for DFS referral. */
637
638         if ( !cli_dfs_check_error(rootcli, NT_STATUS_PATH_NOT_COVERED))  {
639                 return False;
640         }
641
642         /* Check for the referral. */
643
644         if ( !(cli_ipc = cli_cm_open( rootcli->desthost, "IPC$", False )) ) {
645                 return False;
646         }
647         
648         if ( !cli_dfs_get_referral(cli_ipc, dfs_path, &refs, &num_refs, &consumed) 
649                         || !num_refs ) {
650                 return False;
651         }
652         
653         /* Just store the first referral for now. */
654
655         split_dfs_path( refs[0].dfspath, server, share, extrapath );
656         SAFE_FREE(refs);
657
658         /* Make sure to recreate the original string including any wildcards. */
659         
660         cli_dfs_make_full_path( rootcli, path, dfs_path);
661         pathlen = strlen( dfs_path )*2;
662         consumed = MIN(pathlen, consumed );
663         pstrcpy( targetpath, &dfs_path[consumed/2] );
664         dfs_path[consumed/2] = '\0';
665
666         /*
667          * targetpath is now the unconsumed part of the path.
668          * dfs_path is now the consumed part of the path (in \server\share\path format).
669          */
670
671         /* Open the connection to the target server & share */
672         
673         if ( (*targetcli = cli_cm_open(server, share, False)) == NULL ) {
674                 d_printf("Unable to follow dfs referral [\\%s\\%s]\n",
675                         server, share );
676                 return False;
677         }
678         
679         if (strlen(extrapath) > 0) {
680                 string_append(&temppath, extrapath);
681                 string_append(&temppath, targetpath);
682                 pstrcpy( targetpath, temppath );
683         }
684         
685         /* parse out the consumed mount path */
686         /* trim off the \server\share\ */
687
688         ppath = dfs_path;
689
690         if (*ppath != '\\') {
691                 d_printf("cli_resolve_path: dfs_path (%s) not in correct format.\n",
692                         dfs_path );
693                 return False;
694         }
695
696         ppath++; /* Now pointing at start of server name. */
697         
698         if ((ppath = strchr_m( dfs_path, '\\' )) == NULL) {
699                 return False;
700         }
701
702         ppath++; /* Now pointing at start of share name. */
703
704         if ((ppath = strchr_m( ppath+1, '\\' )) == NULL) {
705                 return False;
706         }
707
708         ppath++; /* Now pointing at path component. */
709
710         pstr_sprintf( newmount, "%s\\%s", mountpt, ppath );
711
712         cli_cm_set_mntpoint( *targetcli, newmount );
713
714         /* Check for another dfs referral, note that we are not 
715            checking for loops here. */
716
717         if ( !strequal( targetpath, "\\" ) &&  !strequal( targetpath, "/")) {
718                 if ( cli_resolve_path( newmount, *targetcli, targetpath, &newcli, newpath ) ) {
719                         /*
720                          * When cli_resolve_path returns true here it's always
721                          * returning the complete path in newpath, so we're done
722                          * here.
723                          */
724                         *targetcli = newcli;
725                         pstrcpy( targetpath, newpath );
726                         return True;
727                 }
728         }
729
730   done:
731
732         /* If returning True ensure we return a dfs root full path. */
733         if ( (*targetcli)->dfsroot ) {
734                 pstrcpy(dfs_path, targetpath );
735                 cli_dfs_make_full_path( *targetcli, dfs_path, targetpath); 
736         }
737
738         return True;
739 }
740
741 /********************************************************************
742 ********************************************************************/
743
744 BOOL cli_check_msdfs_proxy( struct cli_state *cli, const char *sharename,
745                             fstring newserver, fstring newshare )
746 {
747         CLIENT_DFS_REFERRAL *refs = NULL;
748         size_t num_refs;
749         uint16 consumed;
750         pstring fullpath;
751         BOOL res;
752         uint16 cnum;
753         pstring newextrapath;
754         
755         if ( !cli || !sharename )
756                 return False;
757
758         cnum = cli->cnum;
759
760         /* special case.  never check for a referral on the IPC$ share */
761
762         if ( strequal( sharename, "IPC$" ) ) {
763                 return False;
764         }
765                 
766         /* send a trans2_query_path_info to check for a referral */
767         
768         pstr_sprintf( fullpath, "\\%s\\%s", cli->desthost, sharename );
769
770         /* check for the referral */
771
772         if (!cli_send_tconX(cli, "IPC$", "IPC", NULL, 0)) {
773                 return False;
774         }
775
776         res = cli_dfs_get_referral(cli, fullpath, &refs, &num_refs, &consumed);
777
778         if (!cli_tdis(cli)) {
779                 SAFE_FREE( refs );
780                 return False;
781         }
782
783         cli->cnum = cnum;
784
785         if (!res || !num_refs ) {
786                 SAFE_FREE( refs );
787                 return False;
788         }
789         
790         split_dfs_path( refs[0].dfspath, newserver, newshare, newextrapath );
791
792         /* check that this is not a self-referral */
793
794         if ( strequal( cli->desthost, newserver ) && strequal( sharename, newshare ) ) {
795                 SAFE_FREE( refs );
796                 return False;
797         }
798         
799         SAFE_FREE( refs );
800         
801         return True;
802 }