r3240: - update the rules for what error codes should be given on the
[jelmer/samba4-debian.git] / source / ntvfs / posix / pvfs_resolve.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    POSIX NTVFS backend - filename resolution
5
6    Copyright (C) Andrew Tridgell 2004
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 /*
24   this is the core code for converting a filename from the format as
25   given by a client to a posix filename, including any case-matching
26   required, and checks for legal characters
27 */
28
29
30 #include "include/includes.h"
31 #include "vfs_posix.h"
32
33 /*
34   compare two filename components. This is where the name mangling hook will go
35 */
36 static int component_compare(struct pvfs_state *pvfs, const char *comp, const char *name)
37 {
38         int ret;
39
40         ret = StrCaseCmp(comp, name);
41
42         if (ret != 0) {
43                 char *shortname = pvfs_short_name_component(pvfs, name);
44                 if (shortname) {
45                         ret = StrCaseCmp(comp, shortname);
46                         talloc_free(shortname);
47                 }
48         }
49
50         return ret;
51 }
52
53 /*
54   search for a filename in a case insensitive fashion
55
56   TODO: add a cache for previously resolved case-insensitive names
57   TODO: add mangled name support
58 */
59 static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, struct pvfs_filename *name)
60 {
61         /* break into a series of components */
62         int num_components;
63         char **components;
64         char *p, *partial_name;
65         int i;
66
67         /* break up the full name info pathname components */
68         num_components=2;
69         p = name->full_name + strlen(pvfs->base_directory) + 1;
70
71         for (;*p;p++) {
72                 if (*p == '/') {
73                         num_components++;
74                 }
75         }
76
77         components = talloc_array_p(name, char *, num_components);
78         p = name->full_name + strlen(pvfs->base_directory);
79         *p++ = 0;
80
81         components[0] = name->full_name;
82
83         for (i=1;i<num_components;i++) {
84                 components[i] = p;
85                 p = strchr(p, '/');
86                 if (p) *p++ = 0;
87                 if (pvfs_is_reserved_name(pvfs, components[i])) {
88                         return NT_STATUS_ACCESS_DENIED;
89                 }
90         }
91
92         partial_name = talloc_strdup(name, components[0]);
93         if (!partial_name) {
94                 return NT_STATUS_NO_MEMORY;
95         }
96
97         /* for each component, check if it exists as-is, and if not then
98            do a directory scan */
99         for (i=1;i<num_components;i++) {
100                 char *test_name;
101                 DIR *dir;
102                 struct dirent *de;
103                 char *long_component;
104
105                 /* possibly remap from the short name cache */
106                 long_component = pvfs_mangled_lookup(pvfs, name, components[i]);
107                 if (long_component) {
108                         components[i] = long_component;
109                 }
110
111                 test_name = talloc_asprintf(name, "%s/%s", partial_name, components[i]);
112                 if (!test_name) {
113                         return NT_STATUS_NO_MEMORY;
114                 }
115
116                 /* check if this component exists as-is */
117                 if (stat(test_name, &name->st) == 0) {
118                         if (i<num_components-1 && !S_ISDIR(name->st.st_mode)) {
119                                 return NT_STATUS_NOT_A_DIRECTORY;
120                         }
121                         talloc_free(partial_name);
122                         partial_name = test_name;
123                         if (i == num_components - 1) {
124                                 name->exists = True;
125                         }
126                         continue;
127                 }
128
129                 /* the filesystem might be case insensitive, in which
130                    case a search is pointless unless the name is
131                    mangled */
132                 if ((pvfs->flags & PVFS_FLAG_CI_FILESYSTEM) &&
133                     !pvfs_is_mangled_component(pvfs, components[i])) {
134                         if (i < num_components-1) {
135                                 return NT_STATUS_OBJECT_PATH_NOT_FOUND;
136                         }
137                         partial_name = test_name;
138                         continue;
139                 }
140                 
141                 dir = opendir(partial_name);
142                 if (!dir) {
143                         return pvfs_map_errno(pvfs, errno);
144                 }
145
146                 while ((de = readdir(dir))) {
147                         if (component_compare(pvfs, components[i], de->d_name) == 0) {
148                                 break;
149                         }
150                 }
151
152                 if (!de) {
153                         if (i < num_components-1) {
154                                 closedir(dir);
155                                 return NT_STATUS_OBJECT_PATH_NOT_FOUND;
156                         }
157                 } else {
158                         components[i] = talloc_strdup(name, de->d_name);
159                 }
160                 test_name = talloc_asprintf(name, "%s/%s", partial_name, components[i]);
161                 talloc_free(partial_name);
162                 partial_name = test_name;
163
164                 closedir(dir);
165         }
166
167         if (!name->exists) {
168                 if (stat(partial_name, &name->st) == 0) {
169                         name->exists = True;
170                 }
171         }
172
173         talloc_free(name->full_name);
174         name->full_name = partial_name;
175
176         if (name->exists) {
177                 return pvfs_fill_dos_info(pvfs, name);
178         }
179
180         return NT_STATUS_OK;
181 }
182
183
184 /*
185   convert a CIFS pathname to a unix pathname. Note that this does NOT
186   take into account case insensitivity, and in fact does not access
187   the filesystem at all. It is merely a reformatting and charset
188   checking routine.
189
190   errors are returned if the filename is illegal given the flags
191 */
192 static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_name,
193                                uint_t flags, struct pvfs_filename *name)
194 {
195         char *ret, *p, *p_start;
196         size_t len;
197
198         name->original_name = talloc_strdup(name, cifs_name);
199         name->stream_name = NULL;
200         name->has_wildcard = False;
201
202         while (*cifs_name == '\\') {
203                 cifs_name++;
204         }
205
206         if (*cifs_name == 0) {
207                 name->full_name = talloc_asprintf(name, "%s/.", pvfs->base_directory);
208                 if (name->full_name == NULL) {
209                         return NT_STATUS_NO_MEMORY;
210                 }
211                 return NT_STATUS_OK;
212         }
213
214         ret = talloc_asprintf(name, "%s/%s", pvfs->base_directory, cifs_name);
215         if (ret == NULL) {
216                 return NT_STATUS_NO_MEMORY;
217         }
218
219         p = ret + strlen(pvfs->base_directory) + 1;
220
221         len = strlen(cifs_name);
222         if (len>0 && p[len-1] == '\\') {
223                 p[len-1] = 0;
224                 len--;
225         }
226         if (len>1 && p[len-1] == '.' && p[len-2] == '\\') {
227                 return NT_STATUS_OBJECT_NAME_INVALID;
228         }
229
230         /* now do an in-place conversion of '\' to '/', checking
231            for legal characters */
232         p_start = p;
233         while (*p) {
234                 size_t c_size;
235                 codepoint_t c = next_codepoint(p, &c_size);
236                 switch (c) {
237                 case '\\':
238                         if (name->has_wildcard) {
239                                 /* wildcards are only allowed in the last part
240                                    of a name */
241                                 return NT_STATUS_ILLEGAL_CHARACTER;
242                         }
243                         *p = '/';
244                         break;
245                 case ':':
246                         if (!(flags & PVFS_RESOLVE_STREAMS)) {
247                                 return NT_STATUS_ILLEGAL_CHARACTER;
248                         }
249                         name->stream_name = talloc_strdup(name, p+1);
250                         if (name->stream_name == NULL) {
251                                 return NT_STATUS_NO_MEMORY;
252                         }
253                         *p-- = 0;
254                         break;
255                 case '*':
256                 case '>':
257                 case '<':
258                 case '?':
259                 case '"':
260                         if (flags & PVFS_RESOLVE_NO_WILDCARD) {
261                                 return NT_STATUS_ILLEGAL_CHARACTER;
262                         }
263                         name->has_wildcard = True;
264                         break;
265                 case '/':
266                 case '|':
267                         return NT_STATUS_ILLEGAL_CHARACTER;
268                 case '.':
269                         /* see if it is definately a .. or
270                            . component. If it is then fail here, and
271                            let the next layer up try again after
272                            pvfs_reduce_name() if it wants to. This is
273                            much more efficient on average than always
274                            scanning for these separately */
275                         if (p[1] == '.' && 
276                             (p[2] == 0 || p[2] == '\\') &&
277                             (p == p_start || p[-1] == '/')) {
278                                 return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
279                         }
280                         if ((p[1] == 0 || p[1] == '\\') &&
281                             (p == p_start || p[-1] == '/')) {
282                                 return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
283                         }
284                         break;
285                 }
286
287                 p += c_size;
288         }
289
290         name->full_name = ret;
291
292         return NT_STATUS_OK;
293 }
294
295
296 /*
297   reduce a name that contains .. components or repeated \ separators
298   return NULL if it can't be reduced
299 */
300 const char *pvfs_reduce_name(TALLOC_CTX *mem_ctx, const char *fname)
301 {
302         codepoint_t c;
303         size_t c_size, len;
304         int i, num_components;
305         char **components;
306         char *p, *s, *ret;
307
308         s = talloc_strdup(mem_ctx, fname);
309         if (s == NULL) return NULL;
310
311         for (num_components=1, p=s; *p; p += c_size) {
312                 c = next_codepoint(p, &c_size);
313                 if (c == '\\') num_components++;
314         }
315         if (num_components < 2) {
316                 talloc_free(s);
317                 return NULL;
318         }
319
320         components = talloc_array_p(s, char *, num_components+1);
321         if (components == NULL) {
322                 talloc_free(s);
323                 return NULL;
324         }
325
326         components[0] = s;
327         for (i=0, p=s; *p; p += c_size) {
328                 c = next_codepoint(p, &c_size);
329                 if (c == '\\') {
330                         *p = 0;
331                         components[++i] = p+1;
332                 }
333         }
334         components[i+1] = NULL;
335
336         /* remove any null components */
337         for (i=0;components[i];i++) {
338                 if (strcmp(components[i], "") == 0 ||
339                     strcmp(components[i], ".") == 0) {
340                         memmove(&components[i], &components[i+1], 
341                                 sizeof(char *)*(num_components-i));
342                         i--;
343                 }
344                 if (strcmp(components[i], "..") == 0) {
345                         if (i < 1) return NULL;
346                         memmove(&components[i-1], &components[i+1], 
347                                 sizeof(char *)*(num_components-(i+1)));
348                         i -= 2;
349                 }
350         }
351
352         if (components[0] == NULL) {
353                 talloc_free(s);
354                 return talloc_strdup(mem_ctx, "\\");
355         }
356
357         for (len=i=0;components[i];i++) {
358                 len += strlen(components[i]) + 1;
359         }
360
361         /* rebuild the name */
362         ret = talloc(mem_ctx, len+1);
363         if (ret == NULL) {
364                 talloc_free(s);
365                 return NULL;
366         }
367
368         for (len=0,i=0;components[i];i++) {
369                 size_t len1 = strlen(components[i]);
370                 ret[len] = '\\';
371                 memcpy(ret+len+1, components[i], len1);
372                 len += len1 + 1;
373         }       
374         ret[len] = 0;
375
376         talloc_free(s);
377         
378         return ret;
379 }
380
381
382 /*
383   resolve a name from relative client format to a struct pvfs_filename
384   the memory for the filename is made as a talloc child of 'name'
385
386   flags include:
387      PVFS_RESOLVE_NO_WILDCARD = wildcards are considered illegal characters
388      PVFS_RESOLVE_STREAMS     = stream names are allowed
389
390      TODO: ../ collapsing, and outside share checking
391 */
392 NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
393                            const char *cifs_name,
394                            uint_t flags, struct pvfs_filename **name)
395 {
396         NTSTATUS status;
397
398         *name = talloc_p(mem_ctx, struct pvfs_filename);
399         if (*name == NULL) {
400                 return NT_STATUS_NO_MEMORY;
401         }
402
403         (*name)->exists = False;
404
405         /* do the basic conversion to a unix formatted path,
406            also checking for allowable characters */
407         status = pvfs_unix_path(pvfs, cifs_name, flags, *name);
408
409         if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) {
410                 /* it might contain .. components which need to be reduced */
411                 cifs_name = pvfs_reduce_name(*name, cifs_name);
412                 if (cifs_name) {
413                         status = pvfs_unix_path(pvfs, cifs_name, flags, *name);
414                 }
415         }
416
417         if (!NT_STATUS_IS_OK(status)) {
418                 return status;
419         }
420
421         /* if it has a wildcard then no point doing a stat() */
422         if ((*name)->has_wildcard) {
423                 return NT_STATUS_OK;
424         }
425
426         /* if we can stat() the full name now then we are done */
427         if (stat((*name)->full_name, &(*name)->st) == 0) {
428                 (*name)->exists = True;
429                 return pvfs_fill_dos_info(pvfs, *name);
430         }
431
432         /* search for a matching filename */
433         status = pvfs_case_search(pvfs, *name);
434
435         return status;
436 }
437
438
439 /*
440   do a partial resolve, returning a pvfs_filename structure given a
441   base path and a relative component. It is an error if the file does
442   not exist. No case-insensitive matching is done.
443
444   this is used in places like directory searching where we need a pvfs_filename
445   to pass to a function, but already know the unix base directory and component
446 */
447 NTSTATUS pvfs_resolve_partial(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
448                               const char *unix_dir, const char *fname,
449                               struct pvfs_filename **name)
450 {
451         NTSTATUS status;
452
453         *name = talloc_p(mem_ctx, struct pvfs_filename);
454         if (*name == NULL) {
455                 return NT_STATUS_NO_MEMORY;
456         }
457
458         (*name)->full_name = talloc_asprintf(*name, "%s/%s", unix_dir, fname);
459         if ((*name)->full_name == NULL) {
460                 return NT_STATUS_NO_MEMORY;
461         }
462
463         if (stat((*name)->full_name, &(*name)->st) == -1) {
464                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
465         }
466
467         (*name)->exists = True;
468         (*name)->has_wildcard = False;
469         (*name)->original_name = talloc_strdup(*name, fname);
470         (*name)->stream_name = NULL;
471
472         status = pvfs_fill_dos_info(pvfs, *name);
473
474         return status;
475 }
476
477
478 /*
479   fill in the pvfs_filename info for an open file, given the current
480   info for a (possibly) non-open file. This is used by places that need
481   to update the pvfs_filename stat information, and by pvfs_open()
482 */
483 NTSTATUS pvfs_resolve_name_fd(struct pvfs_state *pvfs, int fd,
484                               struct pvfs_filename *name)
485 {
486         dev_t device;
487         ino_t inode;
488
489         if (name->exists) {
490                 device = name->st.st_dev;
491                 inode = name->st.st_ino;
492         }
493
494         if (fstat(fd, &name->st) == -1) {
495                 return NT_STATUS_INVALID_HANDLE;
496         }
497
498         if (name->exists &&
499             (device != name->st.st_dev || inode != name->st.st_ino)) {
500                 /* the file we are looking at has changed! this could
501                  be someone trying to exploit a race
502                  condition. Certainly we don't want to continue
503                  operating on this file */
504                 DEBUG(0,("pvfs: WARNING: file '%s' changed during resole - failing\n",
505                          name->full_name));
506                 return NT_STATUS_UNEXPECTED_IO_ERROR;
507         }
508
509         name->exists = True;
510         
511         return pvfs_fill_dos_info(pvfs, name);
512 }