r5578: get 'recurse; dir' working across multiple levels of dfs referrals
[tprouty/samba.git] / source3 / 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       
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #define NO_SYSLOG
23
24 #include "includes.h"
25
26
27 struct client_connection {
28         struct client_connection *prev, *next;
29         struct cli_state *cli;
30         pstring mount;
31 };
32
33 /* global state....globals reek! */
34
35 static pstring username;
36 static pstring password;
37 static BOOL use_kerberos;
38 static BOOL got_pass;
39 static int signing_state;
40
41 static int port;
42 static int name_type = 0x20;
43 static int max_protocol = PROTOCOL_NT1;
44 static BOOL have_ip;
45 static struct in_addr dest_ip;
46
47 static struct client_connection *connections;
48
49 /********************************************************************
50  Return a connection to a server.
51 ********************************************************************/
52
53 static struct cli_state *do_connect( const char *server, const char *share,
54                                      BOOL show_sessetup )
55 {
56         struct cli_state *c;
57         struct nmb_name called, calling;
58         const char *server_n;
59         struct in_addr ip;
60         pstring servicename;
61         char *sharename;
62         
63         /* make a copy so we don't modify the global string 'service' */
64         pstrcpy(servicename, share);
65         sharename = servicename;
66         if (*sharename == '\\') {
67                 server = sharename+2;
68                 sharename = strchr_m(server,'\\');
69                 if (!sharename) return NULL;
70                 *sharename = 0;
71                 sharename++;
72         }
73
74         server_n = server;
75         
76         zero_ip(&ip);
77
78         make_nmb_name(&calling, global_myname(), 0x0);
79         make_nmb_name(&called , server, name_type);
80
81  again:
82         zero_ip(&ip);
83         if (have_ip) 
84                 ip = dest_ip;
85
86         /* have to open a new connection */
87         if (!(c=cli_initialise(NULL)) || (cli_set_port(c, port) != port) ||
88             !cli_connect(c, server_n, &ip)) {
89                 d_printf("Connection to %s failed\n", server_n);
90                 return NULL;
91         }
92
93         c->protocol = max_protocol;
94         c->use_kerberos = use_kerberos;
95         cli_setup_signing_state(c, signing_state);
96                 
97
98         if (!cli_session_request(c, &calling, &called)) {
99                 char *p;
100                 d_printf("session request to %s failed (%s)\n", 
101                          called.name, cli_errstr(c));
102                 cli_shutdown(c);
103                 if ((p=strchr_m(called.name, '.'))) {
104                         *p = 0;
105                         goto again;
106                 }
107                 if (strcmp(called.name, "*SMBSERVER")) {
108                         make_nmb_name(&called , "*SMBSERVER", 0x20);
109                         goto again;
110                 }
111                 return NULL;
112         }
113
114         DEBUG(4,(" session request ok\n"));
115
116         if (!cli_negprot(c)) {
117                 d_printf("protocol negotiation failed\n");
118                 cli_shutdown(c);
119                 return NULL;
120         }
121
122         if (!got_pass) {
123                 char *pass = getpass("Password: ");
124                 if (pass) {
125                         pstrcpy(password, pass);
126                         got_pass = 1;
127                 }
128         }
129
130         if (!cli_session_setup(c, username, 
131                                password, strlen(password),
132                                password, strlen(password),
133                                lp_workgroup())) {
134                 /* if a password was not supplied then try again with a null username */
135                 if (password[0] || !username[0] || use_kerberos ||
136                     !cli_session_setup(c, "", "", 0, "", 0, lp_workgroup())) { 
137                         d_printf("session setup failed: %s\n", cli_errstr(c));
138                         if (NT_STATUS_V(cli_nt_error(c)) == 
139                             NT_STATUS_V(NT_STATUS_MORE_PROCESSING_REQUIRED))
140                                 d_printf("did you forget to run kinit?\n");
141                         cli_shutdown(c);
142                         return NULL;
143                 }
144                 d_printf("Anonymous login successful\n");
145         }
146
147         if ( show_sessetup ) {
148                 if (*c->server_domain) {
149                         DEBUG(0,("Domain=[%s] OS=[%s] Server=[%s]\n",
150                                 c->server_domain,c->server_os,c->server_type));
151                 } else if (*c->server_os || *c->server_type){
152                         DEBUG(0,("OS=[%s] Server=[%s]\n",
153                                  c->server_os,c->server_type));
154                 }               
155         }
156         DEBUG(4,(" session setup ok\n"));
157
158         if (!cli_send_tconX(c, sharename, "?????",
159                             password, strlen(password)+1)) {
160                 d_printf("tree connect failed: %s\n", cli_errstr(c));
161                 cli_shutdown(c);
162                 return NULL;
163         }
164
165         DEBUG(4,(" tconx ok\n"));
166
167         return c;
168 }
169
170 /****************************************************************************
171 ****************************************************************************/
172
173 static void cli_cm_set_mntpoint( struct cli_state *c, const char *mnt )
174 {
175         struct client_connection *p;
176         int i;
177
178         for ( p=connections,i=0; p; p=p->next,i++ ) {
179                 if ( strequal(p->cli->desthost, c->desthost) && strequal(p->cli->share, c->share) )
180                         break;
181         }
182         
183         if ( p ) {
184                 pstrcpy( p->mount, mnt );
185                 dos_clean_name( p->mount );
186         }
187 }
188
189 /****************************************************************************
190 ****************************************************************************/
191
192 const char * cli_cm_get_mntpoint( struct cli_state *c )
193 {
194         struct client_connection *p;
195         int i;
196
197         for ( p=connections,i=0; p; p=p->next,i++ ) {
198                 if ( strequal(p->cli->desthost, c->desthost) && strequal(p->cli->share, c->share) )
199                         break;
200         }
201         
202         if ( p )
203                 return p->mount;
204                 
205         return NULL;
206 }
207
208 /********************************************************************
209  Add a new connection to the list
210 ********************************************************************/
211
212 static struct cli_state* cli_cm_connect( const char *server, const char *share,
213                                          BOOL show_hdr )
214 {
215         struct client_connection *node;
216         
217         node = SMB_XMALLOC_P( struct client_connection );
218         
219         node->cli = do_connect( server, share, show_hdr );
220
221         if ( !node->cli ) {
222                 SAFE_FREE( node );
223                 return NULL;
224         }
225
226         DLIST_ADD( connections, node );
227
228         cli_cm_set_mntpoint( node->cli, "" );
229
230         return node->cli;
231
232 }
233
234 /********************************************************************
235  Return a connection to a server.
236 ********************************************************************/
237
238 static struct cli_state* cli_cm_find( const char *server, const char *share )
239 {
240         struct client_connection *p;
241
242         for ( p=connections; p; p=p->next ) {
243                 if ( strequal(server, p->cli->desthost) && strequal(share,p->cli->share) )
244                         return p->cli;
245         }
246
247         return NULL;
248 }
249
250 /****************************************************************************
251  open a client connection to a \\server\share.  Set's the current *cli 
252  global variable as a side-effect (but only if the connection is successful).
253 ****************************************************************************/
254
255 struct cli_state* cli_cm_open( const char *server, const char *share, BOOL show_hdr )
256 {
257         struct cli_state *c;
258         
259         /* try to reuse an existing connection */
260
261         c = cli_cm_find( server, share );
262         
263         if ( !c )
264                 c = cli_cm_connect( server, share, show_hdr );
265
266         return c;
267 }
268
269 /****************************************************************************
270 ****************************************************************************/
271
272 void cli_cm_shutdown( void )
273 {
274
275         struct client_connection *p, *x;
276
277         for ( p=connections; p; ) {
278                 cli_shutdown( p->cli );
279                 x = p;
280                 p = p->next;
281
282                 SAFE_FREE( x );
283         }
284
285         connections = NULL;
286
287         return;
288 }
289
290 /****************************************************************************
291 ****************************************************************************/
292
293 void cli_cm_display(void)
294 {
295         struct client_connection *p;
296         int i;
297
298         for ( p=connections,i=0; p; p=p->next,i++ ) {
299                 d_printf("%d:\tserver=%s, share=%s\n", 
300                         i, p->cli->desthost, p->cli->share );
301         }
302 }
303
304 /****************************************************************************
305 ****************************************************************************/
306
307 void cli_cm_set_credentials( struct user_auth_info *user )
308 {
309         pstrcpy( username, user->username );
310         
311         if ( user->got_pass ) {
312                 pstrcpy( password, user->password );
313                 got_pass = True;
314         }
315         
316         use_kerberos = user->use_kerberos;      
317         signing_state = user->signing_state;
318 }
319
320 /****************************************************************************
321 ****************************************************************************/
322
323 void cli_cm_set_port( int port_number )
324 {
325         port = port_number;
326 }
327
328 /****************************************************************************
329 ****************************************************************************/
330
331 void cli_cm_set_dest_name_type( int type )
332 {
333         name_type = type;
334 }
335
336 /****************************************************************************
337 ****************************************************************************/
338
339 void cli_cm_set_dest_ip(struct in_addr ip )
340 {
341         dest_ip = ip;
342         have_ip = True;
343 }
344
345 /********************************************************************
346  split a dfs path into the server and share name components
347 ********************************************************************/
348
349 static void split_dfs_path( const char *nodepath, fstring server, fstring share )
350 {
351         char *p;
352         pstring path;
353
354         pstrcpy( path, nodepath );
355
356         if ( path[0] != '\\' )
357                 return;
358
359         p = strrchr_m( path, '\\' );
360
361         if ( !p )
362                 return;
363
364         *p = '\0';
365         p++;
366
367         fstrcpy( share, p );
368         fstrcpy( server, &path[1] );
369 }
370
371 /****************************************************************************
372  return the original path truncated at the first wildcard character
373  (also strips trailing \'s).  Trust the caller to provide a NULL 
374  terminated string
375 ****************************************************************************/
376
377 static void clean_path( pstring clean, const char *path )
378 {
379         int len;
380         char *p;
381         pstring newpath;
382                 
383         pstrcpy( newpath, path );
384         p = newpath;
385         
386         while ( p ) {
387                 /* first check for '*' */
388                 
389                 p = strrchr_m( newpath, '*' );
390                 if ( p ) {
391                         *p = '\0';
392                         p = newpath;
393                         continue;
394                 }
395         
396                 /* first check for '?' */
397                 
398                 p = strrchr_m( newpath, '?' );
399                 if ( p ) {
400                         *p = '\0';
401                         p = newpath;
402                 }
403         }
404         
405         /* strip a trailing backslash */
406         
407         len = strlen( newpath );
408         if ( newpath[len-1] == '\\' )
409                 newpath[len-1] = '\0';
410                 
411         pstrcpy( clean, newpath );
412 }
413
414 /****************************************************************************
415 ****************************************************************************/
416
417 static BOOL make_full_path( pstring path, const char *server, const char *share,
418                             const char *dir )
419 {
420         pstring servicename;
421         char *sharename;
422         const char *directory;
423
424         
425         /* make a copy so we don't modify the global string 'service' */
426         
427         pstrcpy(servicename, share);
428         sharename = servicename;
429         
430         if (*sharename == '\\') {
431         
432                 server = sharename+2;
433                 sharename = strchr_m(server,'\\');
434                 
435                 if (!sharename) 
436                         return False;
437                         
438                 *sharename = 0;
439                 sharename++;
440         }
441
442         directory = dir;
443         if ( *directory == '\\' )
444                 directory++;
445         
446         pstr_sprintf( path, "\\%s\\%s\\%s", server, sharename, directory );
447
448         return True;
449 }
450
451 /********************************************************************
452  check for dfs referral
453 ********************************************************************/
454
455 static BOOL cli_dfs_check_error( struct cli_state *cli )
456 {
457         uint32 flgs2 = SVAL(cli->inbuf,smb_flg2);
458
459         /* only deal with DS when we negotiated NT_STATUS codes and UNICODE */
460
461         if ( !( (flgs2&FLAGS2_32_BIT_ERROR_CODES) && (flgs2&FLAGS2_UNICODE_STRINGS) ) )
462                 return False;
463
464         if ( NT_STATUS_EQUAL( NT_STATUS_PATH_NOT_COVERED, NT_STATUS(IVAL(cli->inbuf,smb_rcls)) ) )
465                 return True;
466
467         return False;
468 }
469
470 /********************************************************************
471  get the dfs referral link
472 ********************************************************************/
473
474 BOOL cli_dfs_get_referral( struct cli_state *cli, const char *path, 
475                            CLIENT_DFS_REFERRAL**refs, size_t *num_refs,
476                            uint16 *consumed)
477 {
478         unsigned int data_len = 0;
479         unsigned int param_len = 0;
480         uint16 setup = TRANSACT2_GET_DFS_REFERRAL;
481         char param[sizeof(pstring)+2];
482         pstring data;
483         char *rparam=NULL, *rdata=NULL;
484         char *p;
485         size_t pathlen = 2*(strlen(path)+1);
486         uint16 num_referrals;
487         CLIENT_DFS_REFERRAL *referrals;
488         
489         memset(param, 0, sizeof(param));
490         SSVAL(param, 0, 0x03);  /* max referral level */
491         p = &param[2];
492
493         p += clistr_push(cli, p, path, MIN(pathlen, sizeof(param)-2), STR_TERMINATE);
494         param_len = PTR_DIFF(p, param);
495
496         if (!cli_send_trans(cli, SMBtrans2,
497                 NULL,                        /* name */
498                 -1, 0,                          /* fid, flags */
499                 &setup, 1, 0,                   /* setup, length, max */
500                 param, param_len, 2,            /* param, length, max */
501                 (char *)&data,  data_len, cli->max_xmit /* data, length, max */
502                 )) {
503                         return False;
504         }
505
506         if (!cli_receive_trans(cli, SMBtrans2,
507                 &rparam, &param_len,
508                 &rdata, &data_len)) {
509                         return False;
510         }
511         
512         *consumed     = SVAL( rdata, 0 );
513         num_referrals = SVAL( rdata, 2 );
514         
515         if ( num_referrals != 0 ) {
516                 uint16 ref_version;
517                 uint16 ref_size;
518                 int i;
519                 uint16 node_offset;
520                 
521                 
522                 referrals = SMB_XMALLOC_ARRAY( CLIENT_DFS_REFERRAL, num_referrals );
523         
524                 /* start at the referrals array */
525         
526                 p = rdata+8;
527                 for ( i=0; i<num_referrals; i++ ) {
528                         ref_version = SVAL( p, 0 );
529                         ref_size    = SVAL( p, 2 );
530                         node_offset = SVAL( p, 16 );
531                         
532                         if ( ref_version != 3 ) {
533                                 p += ref_size;
534                                 continue;
535                         }
536                         
537                         referrals[i].proximity = SVAL( p, 8 );
538                         referrals[i].ttl       = SVAL( p, 10 );
539
540                         clistr_pull( cli, referrals[i].dfspath, p+node_offset, 
541                                 sizeof(referrals[i].dfspath), -1, STR_TERMINATE|STR_UNICODE );
542
543                         p += ref_size;
544                 }
545         
546         }
547         
548         *num_refs = num_referrals;
549         *refs = referrals;
550
551         SAFE_FREE(rdata);
552         SAFE_FREE(rparam);
553
554         return True;
555 }
556
557 /********************************************************************
558 ********************************************************************/
559
560 BOOL cli_resolve_path( const char *mountpt, struct cli_state *rootcli, const char *path,
561                        struct cli_state **targetcli, pstring targetpath )
562 {
563         CLIENT_DFS_REFERRAL *refs = NULL;
564         size_t num_refs;
565         uint16 consumed;
566         struct cli_state *cli_ipc;
567         pstring fullpath, cleanpath;
568         int pathlen;
569         fstring server, share;
570         struct cli_state *newcli;
571         pstring newpath;
572         pstring newmount;
573         char *ppath;
574         
575         SMB_STRUCT_STAT sbuf;
576         uint32 attributes;
577         
578         *targetcli = NULL;
579         
580         if ( !rootcli || !path || !targetcli )
581                 return False;
582                 
583         /* send a trans2_query_path_info to check for a referral */
584         
585         clean_path( cleanpath,  path );
586         make_full_path( fullpath, rootcli->desthost, rootcli->share, cleanpath );
587
588         /* don't bother continuing if this is not a dfs root */
589         
590         if ( !rootcli->dfsroot || cli_qpathinfo_basic( rootcli, cleanpath, &sbuf, &attributes ) ) {
591                 *targetcli = rootcli;
592                 pstrcpy( targetpath, path );
593                 return True;
594         }
595
596         /* we got an error, check for DFS referral */
597                         
598         if ( !cli_dfs_check_error(rootcli) )
599                 return False;
600
601         /* check for the referral */
602
603         if ( !(cli_ipc = cli_cm_open( rootcli->desthost, "IPC$", False )) )
604                 return False;
605         
606         if ( !cli_dfs_get_referral(cli_ipc, fullpath, &refs, &num_refs, &consumed) 
607                 || !num_refs )
608         {
609                 return False;
610         }
611         
612         /* just store the first referral for now
613            Make sure to recreate the original string including any wildcards */
614         
615         make_full_path( fullpath, rootcli->desthost, rootcli->share, path );
616         pathlen = strlen( fullpath )*2;
617         consumed = MIN(pathlen, consumed );
618         pstrcpy( targetpath, &fullpath[consumed/2] );
619
620         split_dfs_path( refs[0].dfspath, server, share );
621         SAFE_FREE( refs );
622         
623         /* open the connection to the target path */
624         
625         if ( (*targetcli = cli_cm_open(server, share, False)) == NULL ) {
626                 d_printf("Unable to follow dfs referral [//%s/%s]\n",
627                         server, share );
628                         
629                 return False;
630         }
631         
632         /* parse out the consumed mount path */
633         /* trim off the \server\share\ */
634
635         fullpath[consumed/2] = '\0';
636         dos_clean_name( fullpath );
637         ppath = strchr_m( fullpath, '\\' );
638         ppath = strchr_m( ppath+1, '\\' );
639         ppath = strchr_m( ppath+1, '\\' );
640         ppath++;
641         
642         pstr_sprintf( newmount, "%s\\%s", mountpt, ppath );
643         cli_cm_set_mntpoint( *targetcli, newmount );
644
645         /* check for another dfs referral, note that we are not 
646            checking for loops here */
647
648         if ( !strequal( targetpath, "\\" ) ) {
649                 if ( cli_resolve_path( newmount, *targetcli, targetpath, &newcli, newpath ) ) {
650                         *targetcli = newcli;
651                         pstrcpy( targetpath, newpath );
652                 }
653         }
654
655         return True;
656 }