r5521: allow smbclient to follow multiple leveles of dfs referrals (no loop checking...
[kai/samba.git] / source / libsmb / clidfs.c
1 /* 
2    Unix SMB/CIFS implementation.
3    client connect/disconnect routines
4    Copyright (C) Gerald (Jerry) Carter            2004
5       
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10    
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15    
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 #define NO_SYSLOG
22
23 #include "includes.h"
24
25
26 /********************************************************************
27  split a dfs path into the server and share name components
28 ********************************************************************/
29
30 static void split_dfs_path( const char *nodepath, fstring server, fstring share )
31 {
32         char *p;
33         pstring path;
34
35         pstrcpy( path, nodepath );
36
37         if ( path[0] != '\\' )
38                 return;
39
40         p = strrchr_m( path, '\\' );
41
42         if ( !p )
43                 return;
44
45         *p = '\0';
46         p++;
47
48         fstrcpy( share, p );
49         fstrcpy( server, &path[1] );
50 }
51
52 /****************************************************************************
53  return the original path truncated at the first wildcard character
54  (also strips trailing \'s).  Trust the caller to provide a NULL 
55  terminated string
56 ****************************************************************************/
57
58 static void clean_path( pstring clean, const char *path )
59 {
60         int len;
61         char *p;
62         pstring newpath;
63                 
64         pstrcpy( newpath, path );
65         p = newpath;
66         
67         while ( p ) {
68                 /* first check for '*' */
69                 
70                 p = strrchr_m( newpath, '*' );
71                 if ( p ) {
72                         *p = '\0';
73                         p = newpath;
74                         continue;
75                 }
76         
77                 /* first check for '?' */
78                 
79                 p = strrchr_m( newpath, '?' );
80                 if ( p ) {
81                         *p = '\0';
82                         p = newpath;
83                 }
84         }
85         
86         /* strip a trailing backslash */
87         
88         len = strlen( newpath );
89         if ( newpath[len-1] == '\\' )
90                 newpath[len-1] = '\0';
91                 
92         pstrcpy( clean, newpath );
93 }
94
95 /****************************************************************************
96 ****************************************************************************/
97
98 static BOOL make_full_path( pstring path, const char *server, const char *share,
99                             const char *dir )
100 {
101         pstring servicename;
102         char *sharename;
103         const char *directory;
104
105         
106         /* make a copy so we don't modify the global string 'service' */
107         
108         pstrcpy(servicename, share);
109         sharename = servicename;
110         
111         if (*sharename == '\\') {
112         
113                 server = sharename+2;
114                 sharename = strchr_m(server,'\\');
115                 
116                 if (!sharename) 
117                         return False;
118                         
119                 *sharename = 0;
120                 sharename++;
121         }
122
123         directory = dir;
124         if ( *directory == '\\' )
125                 directory++;
126         
127         pstr_sprintf( path, "\\%s\\%s\\%s", server, sharename, directory );
128
129         return True;
130 }
131
132 /********************************************************************
133  check for dfs referral
134 ********************************************************************/
135
136 static BOOL cli_dfs_check_error( struct cli_state *cli )
137 {
138         uint32 flgs2 = SVAL(cli->inbuf,smb_flg2);
139
140         /* only deal with DS when we negotiated NT_STATUS codes and UNICODE */
141
142         if ( !( (flgs2&FLAGS2_32_BIT_ERROR_CODES) && (flgs2&FLAGS2_UNICODE_STRINGS) ) )
143                 return False;
144
145         if ( NT_STATUS_EQUAL( NT_STATUS_PATH_NOT_COVERED, NT_STATUS(IVAL(cli->inbuf,smb_rcls)) ) )
146                 return True;
147
148         return False;
149 }
150
151 /********************************************************************
152  get the dfs referral link
153 ********************************************************************/
154
155 BOOL cli_dfs_get_referral( struct cli_state *cli, const char *path, 
156                            CLIENT_DFS_REFERRAL**refs, size_t *num_refs,
157                            uint16 *consumed)
158 {
159         unsigned int data_len = 0;
160         unsigned int param_len = 0;
161         uint16 setup = TRANSACT2_GET_DFS_REFERRAL;
162         char param[sizeof(pstring)+2];
163         pstring data;
164         char *rparam=NULL, *rdata=NULL;
165         char *p;
166         size_t pathlen = 2*(strlen(path)+1);
167         uint16 num_referrals;
168         CLIENT_DFS_REFERRAL *referrals;
169         
170         memset(param, 0, sizeof(param));
171         SSVAL(param, 0, 0x03);  /* max referral level */
172         p = &param[2];
173
174         p += clistr_push(cli, p, path, MIN(pathlen, sizeof(param)-2), STR_TERMINATE);
175         param_len = PTR_DIFF(p, param);
176
177         if (!cli_send_trans(cli, SMBtrans2,
178                 NULL,                        /* name */
179                 -1, 0,                          /* fid, flags */
180                 &setup, 1, 0,                   /* setup, length, max */
181                 param, param_len, 2,            /* param, length, max */
182                 (char *)&data,  data_len, cli->max_xmit /* data, length, max */
183                 )) {
184                         return False;
185         }
186
187         if (!cli_receive_trans(cli, SMBtrans2,
188                 &rparam, &param_len,
189                 &rdata, &data_len)) {
190                         return False;
191         }
192         
193         *consumed     = SVAL( rdata, 0 );
194         num_referrals = SVAL( rdata, 2 );
195         
196         if ( num_referrals != 0 ) {
197                 uint16 ref_version;
198                 uint16 ref_size;
199                 int i;
200                 uint16 node_offset;
201                 
202                 
203                 referrals = SMB_XMALLOC_ARRAY( CLIENT_DFS_REFERRAL, num_referrals );
204         
205                 /* start at the referrals array */
206         
207                 p = rdata+8;
208                 for ( i=0; i<num_referrals; i++ ) {
209                         ref_version = SVAL( p, 0 );
210                         ref_size    = SVAL( p, 2 );
211                         node_offset = SVAL( p, 16 );
212                         
213                         if ( ref_version != 3 ) {
214                                 p += ref_size;
215                                 continue;
216                         }
217                         
218                         referrals[i].proximity = SVAL( p, 8 );
219                         referrals[i].ttl       = SVAL( p, 10 );
220
221                         clistr_pull( cli, referrals[i].dfspath, p+node_offset, 
222                                 sizeof(referrals[i].dfspath), -1, STR_TERMINATE|STR_UNICODE );
223
224                         p += ref_size;
225                 }
226         
227         }
228         
229         *num_refs = num_referrals;
230         *refs = referrals;
231
232         SAFE_FREE(rdata);
233         SAFE_FREE(rparam);
234
235         return True;
236 }
237
238 /********************************************************************
239 ********************************************************************/
240
241 BOOL cli_resolve_path( struct cli_state *rootcli, const char *path,
242                        struct cli_state **targetcli, pstring targetpath )
243 {
244         CLIENT_DFS_REFERRAL *refs = NULL;
245         size_t num_refs;
246         uint16 consumed;
247         struct cli_state *cli_ipc;
248         pstring fullpath, cleanpath;
249         int pathlen;
250         fstring server, share;
251         struct cli_state *newcli;
252         pstring newpath;
253         
254         SMB_STRUCT_STAT sbuf;
255         uint32 attributes;
256         
257         if ( !rootcli || !path || !targetcli )
258                 return False;
259                 
260         /* send a trans2_query_path_info to check for a referral */
261         
262         clean_path( cleanpath,  path );
263         make_full_path( fullpath, rootcli->desthost, rootcli->share, cleanpath );
264
265         /* don't bother continuing if this is not a dfs root */
266         
267         if ( !rootcli->dfsroot || cli_qpathinfo_basic( rootcli, fullpath, &sbuf, &attributes ) ) {
268                 *targetcli = rootcli;
269                 pstrcpy( targetpath, path );
270                 return True;
271         }
272
273         /* we got an error, check for DFS referral */
274                         
275         if ( !cli_dfs_check_error(rootcli) )
276                 return False;
277
278         /* check for the referral */
279
280         if ( !(cli_ipc = cli_cm_open( rootcli->desthost, "IPC$", False )) )
281                 return False;
282         
283         if ( !cli_dfs_get_referral(cli_ipc, fullpath, &refs, &num_refs, &consumed) 
284                 || !num_refs )
285         {
286                 return False;
287         }
288         
289         /* just store the first referral for now
290            Make sure to recreate the original string including any wildcards */
291         
292         make_full_path( fullpath, rootcli->desthost, rootcli->share, path );
293         pathlen = strlen( fullpath )*2;
294         consumed = MIN(pathlen, consumed );
295         pstrcpy( targetpath, &fullpath[consumed/2] );
296
297         split_dfs_path( refs[0].dfspath, server, share );
298         SAFE_FREE( refs );
299         
300         /* open the connection to the target path */
301         
302         if ( (*targetcli = cli_cm_open(server, share, False)) == NULL ) {
303                 d_printf("Unable to follow dfs referral [//%s/%s]\n",
304                         server, share );
305                         
306                 return False;
307         }
308
309         /* check for another dfs refeerrali, note that we are not 
310            checking for loops here */
311
312         if ( cli_resolve_path( *targetcli, targetpath, &newcli, newpath ) ) {
313                 *targetcli = newcli;
314                 pstrcpy( targetpath, newpath );
315         }
316         
317         return True;
318 }