r2931: use next_codepoint() to ensure we properly handle multi-byte characters in...
[samba.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                 dir = opendir(partial_name);
130                 if (!dir) {
131                         return pvfs_map_errno(pvfs, errno);
132                 }
133
134                 while ((de = readdir(dir))) {
135                         if (component_compare(pvfs, components[i], de->d_name) == 0) {
136                                 break;
137                         }
138                 }
139
140                 if (!de) {
141                         if (i < num_components-1) {
142                                 closedir(dir);
143                                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
144                         }
145                 } else {
146                         components[i] = talloc_strdup(name, de->d_name);
147                 }
148                 test_name = talloc_asprintf(name, "%s/%s", partial_name, components[i]);
149                 talloc_free(partial_name);
150                 partial_name = test_name;
151
152                 closedir(dir);
153         }
154
155         if (!name->exists) {
156                 if (stat(partial_name, &name->st) == 0) {
157                         name->exists = True;
158                 }
159         }
160
161         talloc_free(name->full_name);
162         name->full_name = partial_name;
163
164         if (name->exists) {
165                 return pvfs_fill_dos_info(pvfs, name);
166         }
167
168         return NT_STATUS_OK;
169 }
170
171
172 /*
173   convert a CIFS pathname to a unix pathname. Note that this does NOT
174   take into account case insensitivity, and in fact does not access
175   the filesystem at all. It is merely a reformatting and charset
176   checking routine.
177
178   errors are returned if the filename is illegal given the flags
179 */
180 static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_name,
181                                uint_t flags, struct pvfs_filename *name)
182 {
183         char *ret, *p;
184         size_t len;
185
186         name->original_name = talloc_strdup(name, cifs_name);
187         name->stream_name = NULL;
188         name->has_wildcard = False;
189
190         while (*cifs_name == '\\') {
191                 cifs_name++;
192         }
193
194         if (*cifs_name == 0) {
195                 name->full_name = talloc_asprintf(name, "%s/.", pvfs->base_directory);
196                 if (name->full_name == NULL) {
197                         return NT_STATUS_NO_MEMORY;
198                 }
199                 return NT_STATUS_OK;
200         }
201
202         ret = talloc_asprintf(name, "%s/%s", pvfs->base_directory, cifs_name);
203         if (ret == NULL) {
204                 return NT_STATUS_NO_MEMORY;
205         }
206
207         p = ret + strlen(pvfs->base_directory) + 1;
208
209         len = strlen(cifs_name);
210         if (len>0 && p[len-1] == '\\') {
211                 p[len-1] = 0;
212                 len--;
213         }
214         if (len>1 && p[len-1] == '.' && p[len-2] == '\\') {
215                 return NT_STATUS_OBJECT_NAME_INVALID;
216         }
217
218         /* now do an in-place conversion of '\' to '/', checking
219            for legal characters */
220         while (*p) {
221                 size_t c_size;
222                 codepoint_t c = next_codepoint(p, &c_size);
223                 switch (c) {
224                 case '\\':
225                         if (name->has_wildcard) {
226                                 /* wildcards are only allowed in the last part
227                                    of a name */
228                                 return NT_STATUS_ILLEGAL_CHARACTER;
229                         }
230                         *p = '/';
231                         break;
232                 case ':':
233                         if (!(flags & PVFS_RESOLVE_STREAMS)) {
234                                 return NT_STATUS_ILLEGAL_CHARACTER;
235                         }
236                         name->stream_name = talloc_strdup(name, p+1);
237                         if (name->stream_name == NULL) {
238                                 return NT_STATUS_NO_MEMORY;
239                         }
240                         *p-- = 0;
241                         break;
242                 case '*':
243                 case '>':
244                 case '<':
245                 case '?':
246                 case '"':
247                         if (flags & PVFS_RESOLVE_NO_WILDCARD) {
248                                 return NT_STATUS_ILLEGAL_CHARACTER;
249                         }
250                         name->has_wildcard = True;
251                         break;
252                 case '/':
253                 case '|':
254                         return NT_STATUS_ILLEGAL_CHARACTER;
255                 }
256
257                 p += c_size;
258         }
259
260         name->full_name = ret;
261
262         return NT_STATUS_OK;
263 }
264
265
266 /*
267   resolve a name from relative client format to a struct pvfs_filename
268   the memory for the filename is made as a talloc child of 'name'
269
270   flags include:
271      PVFS_RESOLVE_NO_WILDCARD = wildcards are considered illegal characters
272      PVFS_RESOLVE_STREAMS     = stream names are allowed
273
274      TODO: add reserved name checking (for things like LPT1)
275      TODO: ../ collapsing, and outside share checking
276 */
277 NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
278                            const char *cifs_name,
279                            uint_t flags, struct pvfs_filename **name)
280 {
281         NTSTATUS status;
282
283         *name = talloc_p(mem_ctx, struct pvfs_filename);
284         if (*name == NULL) {
285                 return NT_STATUS_NO_MEMORY;
286         }
287
288         (*name)->exists = False;
289
290         /* do the basic conversion to a unix formatted path,
291            also checking for allowable characters */
292         status = pvfs_unix_path(pvfs, cifs_name, flags, *name);
293         if (!NT_STATUS_IS_OK(status)) {
294                 return status;
295         }
296
297         /* if it has a wildcard then no point doing a stat() */
298         if ((*name)->has_wildcard) {
299                 return NT_STATUS_OK;
300         }
301
302         /* if we can stat() the full name now then we are done */
303         if (stat((*name)->full_name, &(*name)->st) == 0) {
304                 (*name)->exists = True;
305                 return pvfs_fill_dos_info(pvfs, *name);
306         }
307
308         /* the filesystem might be case insensitive, in which
309            case a search is pointless */
310         if (pvfs->flags & PVFS_FLAG_CI_FILESYSTEM) {
311                 return NT_STATUS_OK;
312         }
313
314         /* search for a matching filename */
315         status = pvfs_case_search(pvfs, *name);
316
317         return status;
318 }
319
320
321 /*
322   do a partial resolve, returning a pvfs_filename structure given a
323   base path and a relative component. It is an error if the file does
324   not exist. No case-insensitive matching is done.
325
326   this is used in places like directory searching where we need a pvfs_filename
327   to pass to a function, but already know the unix base directory and component
328 */
329 NTSTATUS pvfs_resolve_partial(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
330                               const char *unix_dir, const char *fname,
331                               struct pvfs_filename **name)
332 {
333         NTSTATUS status;
334
335         *name = talloc_p(mem_ctx, struct pvfs_filename);
336         if (*name == NULL) {
337                 return NT_STATUS_NO_MEMORY;
338         }
339
340         (*name)->full_name = talloc_asprintf(*name, "%s/%s", unix_dir, fname);
341         if ((*name)->full_name == NULL) {
342                 return NT_STATUS_NO_MEMORY;
343         }
344
345         if (stat((*name)->full_name, &(*name)->st) == -1) {
346                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
347         }
348
349         (*name)->exists = True;
350         (*name)->has_wildcard = False;
351         (*name)->original_name = talloc_strdup(*name, fname);
352         (*name)->stream_name = NULL;
353
354         status = pvfs_fill_dos_info(pvfs, *name);
355
356         return status;
357 }
358
359
360 /*
361   fill in the pvfs_filename info for an open file, given the current
362   info for a (possibly) non-open file. This is used by places that need
363   to update the pvfs_filename stat information, and by pvfs_open()
364 */
365 NTSTATUS pvfs_resolve_name_fd(struct pvfs_state *pvfs, int fd,
366                               struct pvfs_filename *name)
367 {
368         if (fstat(fd, &name->st) == -1) {
369                 return NT_STATUS_INVALID_HANDLE;
370         }
371
372         name->exists = True;
373         
374         return pvfs_fill_dos_info(pvfs, name);
375 }