1 /* vi:ai:et:ts=8 sw=2
2 */
3 /*
4 * wzdftpd - a modular and cool ftp server
5 * Copyright (C) 2002-2004 Pierre Chifflier
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 *
21 * As a special exemption, Pierre Chifflier
22 * and other respective copyright holders give permission to link this program
23 * with OpenSSL, and distribute the resulting executable, without including
24 * the source code for OpenSSL in the source distribution.
25 */
26
27 /** \file wzd_dir.c
28 * \brief Utilities functions to manipulate file and dir names
29 */
30
31 #include "wzd_all.h"
32
33 #ifndef WZD_USE_PCH
34
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <errno.h>
41
42 #ifdef WIN32
43 #include <winsock2.h>
44 #include <io.h>
45 #include <direct.h> /* _mkdir */
46 #else
47 #include <unistd.h>
48
49 #include <sys/types.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #include <arpa/inet.h>
53
54 #include <dirent.h>
55 #endif
56
57 #include <fcntl.h> /* O_RDONLY */
58
59 #include "wzd_structs.h"
60
61 #include "wzd_file.h"
62 #include "wzd_fs.h"
63 #include "wzd_log.h"
64 #include "wzd_misc.h"
65 #include "wzd_dir.h"
66 #include "wzd_vfs.h"
67
68 #include "wzd_debug.h"
69
70 #endif /* WZD_USE_PCH */
71
dir_open(const char * name,wzd_context_t * context)72 struct wzd_dir_t * dir_open(const char *name, wzd_context_t * context)
73 {
74 struct wzd_dir_t * _dir=NULL;
75 struct wzd_file_t * entry, * it, *itp, ** insertion_point;
76 struct wzd_file_t * perm_list = NULL;
77 wzd_vfs_t * vfs = mainConfig->vfs;
78 short vfs_pad=0; /* is 1 if name has a trailing '/' */
79 char * perm_file_name;
80 size_t length;
81 char * ptr;
82 const char * dir_filename;
83 char buffer_file[WZD_MAX_PATH+1];
84 int ret;
85 unsigned short sorted = 0;
86 unsigned long watchdog = 0;
87
88 fs_dir_t * dir;
89 fs_fileinfo_t * finfo;
90 fs_filestat_t st;
91
92
93 if ( fs_dir_open(name,&dir) ) return NULL;
94
95 if (name[strlen(name)-1] != '/') vfs_pad = 1;
96
97 _dir = malloc(sizeof(struct wzd_dir_t));
98 _dir->dirname = path_getbasename(name,NULL); /** \bug XXX FIXME if name has a trailing /, this will return "" */
99 _dir->first_entry = NULL;
100
101 length = strlen(name);
102 perm_file_name = malloc(length+strlen(HARD_PERMFILE)+2);
103 memcpy(perm_file_name,name,length);
104 ptr = perm_file_name + length - 1;
105 if ( *ptr != '/' ) { *++ptr = '/'; }
106 ptr++;
107 memcpy(ptr,HARD_PERMFILE,strlen(HARD_PERMFILE));
108 *(ptr + strlen(HARD_PERMFILE)) = '\0';
109
110 /* try to read permission file */
111 if ( (ret=readPermFile(perm_file_name,&perm_list)) && ret != E_FILE_NOEXIST)
112 { free(perm_file_name); free(_dir->dirname); free(_dir); return NULL; }
113 free(perm_file_name);
114
115 wzd_strncpy(buffer_file, name, WZD_MAX_PATH);
116 length = strlen(buffer_file);
117 if (length > 1 && buffer_file[length-1] != '/')
118 { buffer_file[length] = '/'; buffer_file[++length] = '\0'; }
119 ptr = buffer_file + length;
120
121 insertion_point = &_dir->first_entry;
122
123 /* loop on all directory entries and create child structs */
124 while ( !fs_dir_read(dir,&finfo) ) {
125 dir_filename = fs_fileinfo_getname(finfo);
126
127 if (watchdog++ > 65535) {
128 out_log(LEVEL_HIGH, "watchdog: detected infinite loop in dir_open\n");
129 fs_dir_close(dir);
130 return NULL;
131 }
132
133 /* XXX remove hidden files and special entries '.' '..' */
134
135 if (strcmp(dir_filename,".")==0 ||
136 strcmp(dir_filename,"..")==0 ||
137 is_hidden_file(dir_filename) )
138 continue;
139
140 /* search element in list */
141 it = perm_list;
142 itp = NULL;
143 entry = NULL;
144 while (it)
145 {
146 if ( ! DIRCMP(dir_filename,it->filename) )
147 {
148 /* remove from perm_list and insert at (*insertion_point) */
149 if (!itp) { /* first element */
150 entry = perm_list;
151 perm_list = perm_list->next_file;
152 entry->next_file = NULL;
153 } else {
154 entry = it;
155 itp->next_file = it->next_file;
156 it->next_file = NULL;
157 }
158 break;
159 }
160 itp = it;
161 it = it->next_file;
162 }
163
164
165 if (!entry) { /* not listed in permission file */
166
167 /* if entry is a directory, we must query dir for more infos */
168 wzd_strncpy(ptr, dir_filename, WZD_MAX_PATH- (ptr-buffer_file));
169 if (fs_file_lstat(buffer_file,&st)) {
170 /* we have a big problem here ! */
171 out_err(LEVEL_HIGH,"lstat(%s) FAILED ! (errno: %d %s)\n",dir_filename,errno,strerror(errno));
172 itp = it;
173 it = (it)?it->next_file:NULL;
174 continue;
175 }
176 if (S_ISDIR(st.mode)) {
177 /* if this is a dir, we look inside the directory for infos
178 * NULL here is no problem, if will be handled by the next test
179 */
180 entry = file_stat(buffer_file, context);
181 if (entry) { /* we correct the name (currently .) */
182 wzd_strncpy(entry->filename, dir_filename, sizeof(entry->filename));
183 }
184 }
185
186 if (!entry) {
187 entry = wzd_malloc(sizeof(struct wzd_file_t));
188
189 wzd_strncpy(entry->filename,dir_filename,sizeof(entry->filename));
190 entry->owner[0] = '\0';
191 entry->group[0] = '\0';
192 entry->permissions = mainConfig->umask; /** \todo FIXME default permission */
193 entry->acl = NULL;
194 entry->kind = FILE_NOTSET; /* can be reg file or symlink */
195 entry->data = NULL;
196 entry->next_file = NULL;
197 }
198 } /* not listed in permission file */
199
200 if (entry->kind == 3) {
201 /* file exist AND is a symlink ?! */
202 }
203
204 /* sorted insertion */
205 if (sorted) {
206 file_insert_sorted(entry,&_dir->first_entry);
207 } else {
208 (*insertion_point) = entry;
209 insertion_point = &entry->next_file;
210 }
211
212 } /* for all directory entries */
213 fs_dir_close(dir);
214
215 /* add vfs entries */
216 {
217 wzd_user_t * user = GetUserByID(context->userid);
218 char * buffer_vfs = wzd_malloc(WZD_MAX_PATH+1);
219 while (vfs)
220 {
221 if (watchdog++ > 65535) {
222 out_log(LEVEL_HIGH, "watchdog: detected infinite loop in dir_open (in vfs)\n");
223 return NULL;
224 }
225
226 entry = NULL;
227 ptr = vfs_replace_cookies(vfs->virtual_dir,context);
228 if (!ptr) {
229 out_log(LEVEL_CRITICAL,"vfs_replace_cookies returned NULL for %s\n",vfs->virtual_dir);
230 vfs = vfs->next_vfs;
231 continue;
232 }
233 wzd_strncpy(buffer_vfs,ptr,WZD_MAX_PATH);
234 wzd_free(ptr);
235 if (DIRNCMP(buffer_vfs,name,strlen(name))==0)
236 { /* ok, we have a candidate. Now check if user is allowed to see it */
237 if (!vfs_match_perm(vfs->target,user)) { vfs = vfs->next_vfs; continue; }
238 ptr = buffer_vfs + strlen(name) + vfs_pad;
239 if (strchr(ptr,'/')==NULL) {
240 /* read vfs permissions, set to default if no permissions set */
241 entry = file_stat(vfs->physical_dir,context);
242 if (!entry) {
243 entry = wzd_malloc(sizeof(struct wzd_file_t));
244 entry->owner[0] = '\0';
245 entry->group[0] = '\0';
246 entry->permissions = mainConfig->umask;
247 entry->acl = NULL;
248 }
249 wzd_strncpy(entry->filename,ptr,sizeof(entry->filename));
250 entry->kind = FILE_VFS;
251 entry->data = wzd_strdup(vfs->physical_dir);
252 entry->next_file = NULL;
253 }
254 }
255
256 if (entry) {
257 /* sorted insertion */
258 if (sorted) {
259 file_insert_sorted(entry,&_dir->first_entry);
260 } else {
261 (*insertion_point) = entry;
262 insertion_point = &entry->next_file;
263 }
264 }
265
266 vfs = vfs->next_vfs;
267 } /* while (vfs) */
268 wzd_free(buffer_vfs);
269 } /* add vfs entries */
270
271 /* add symlinks */
272 {
273 it = perm_list;
274 itp = NULL;
275 while (it)
276 {
277 if (watchdog++ > 65535) {
278 out_log(LEVEL_HIGH, "watchdog: detected infinite loop in dir_open (in symlinks check)\n");
279 return NULL;
280 }
281
282 if (it->kind == FILE_LNK)
283 {
284 entry = it;
285
286 if (!itp) { /* first element */
287 it = perm_list = perm_list->next_file;
288 } else {
289 itp->next_file = it->next_file;
290 it = itp;
291 }
292 entry->next_file = NULL;
293 /* sorted insertion */
294 if (sorted) {
295 file_insert_sorted(entry,&_dir->first_entry);
296 } else {
297 (*insertion_point) = entry;
298 insertion_point = &entry->next_file;
299 }
300 if (it == perm_list) {
301 itp = NULL;
302 continue;
303 }
304 }
305 else
306 {
307 /** \todo warn user, useless entries in perm file. clean up ? */
308 if (strcmp(it->filename,".") &&
309 strcmp(it->filename,"..") &&
310 ! is_hidden_file(it->filename) )
311 out_log(LEVEL_FLOOD, "permission file for %s: useless entry %s\n", name, it->filename);
312 }
313 itp = it;
314 it = it->next_file;
315 }
316 } /* add symlinks */
317
318 _dir->current_entry = _dir->first_entry;
319
320 /** \todo these are useless entries */
321 free_file_recursive(perm_list);
322
323 return _dir;
324 }
325
326
dir_close(struct wzd_dir_t * dir)327 void dir_close(struct wzd_dir_t * dir)
328 {
329 if (!dir) return;
330
331 if (dir->dirname) free(dir->dirname);
332 if (dir->first_entry) free_file_recursive(dir->first_entry);
333 free(dir);
334 }
335
336
337
dir_read(struct wzd_dir_t * dir,wzd_context_t * context)338 struct wzd_file_t * dir_read(struct wzd_dir_t * dir, wzd_context_t * context)
339 {
340 struct wzd_file_t * entry;
341
342 if (!dir || !dir->current_entry) return NULL;
343 entry = dir->current_entry;
344 dir->current_entry = entry->next_file;
345 return entry;
346 }
347
348
349
350
351 /* strip non-directory suffix from file name
352 * returns file without its trailing /component removed, if name contains
353 * no /'s, returns "." (meaning the current directory).
354 * caller MUST free memory !
355 */
path_getdirname(const char * file)356 char * path_getdirname(const char *file)
357 {
358 char * dirname;
359 const char * ptr;
360 unsigned int length;
361
362 if (!file) return NULL;
363 ptr = file + strlen(file);
364 while ( (ptr > file) && (*ptr != '/')) ptr--;
365
366 if (ptr == file)
367 {
368 dirname = malloc(2);
369 dirname[0] = (*ptr == '/') ? '/' : '.';
370 dirname[1] = '\0';
371 }
372 else
373 {
374 length = (ptr - file);
375 dirname = malloc(length+1);
376 strncpy(dirname,file,length);
377 dirname[length] = '\0';
378 }
379
380 return dirname;
381 }
382
383 /* \brief strip directory and suffix from filename
384 *
385 * Return file with any leading directory components removed. If specified,
386 * also remove a trailing suffix.
387 * Caller MUST free memory !
388 */
path_getbasename(const char * file,const char * suffix)389 char * path_getbasename(const char *file, const char *suffix)
390 {
391 char * basename;
392 const char * ptr;
393 unsigned int length;
394
395 if (!file) return NULL;
396 ptr = file + strlen(file);
397 while ( (ptr > file) && (*ptr != '/')) ptr--;
398
399 if (ptr == file)
400 {
401 /* if file starts with '/', skip the '/' and return the name
402 * except if it is exactly '/', which we return unmodified
403 */
404 if (*ptr == '/' && *(ptr+1) != '\0') basename = strdup(file+1);
405 else basename = strdup(file);
406 }
407 else
408 {
409 length = strlen(file) - (ptr - file);
410 basename = malloc(length+1);
411 strncpy(basename,ptr+1,length);
412 basename[length] = '\0';
413 }
414
415 /* remove suffix if specified */
416 if (suffix && *suffix != '\0') {
417 size_t length_base, length_suffix;
418 length_base = strlen(basename);
419 length_suffix = strlen(suffix);
420 if (length_base >= length_suffix) {
421 if (strcmp(basename + length_base - length_suffix, suffix) == 0) {
422 *(basename + length_base - length_suffix) = '\0';
423 }
424 }
425 }
426
427 return basename;
428 }
429
430 /* \brief get the trailing n parts of a filename
431 *
432 * Return file with any leading directory components removed, until
433 * it has n components.
434 * Caller MUST free memory !
435 */
path_gettrailingname(const char * file,unsigned int n)436 char * path_gettrailingname(const char *file, unsigned int n)
437 {
438 char * name;
439 const char * ptr;
440 unsigned int length;
441 unsigned int count;
442
443 if (!file) return NULL;
444 ptr = file + strlen(file);
445 count = 0;
446 while ( (ptr > file) && (count < n))
447 {
448 if (*ptr == '/')
449 if (++count >= n) break;
450 ptr--;
451 }
452
453 if (ptr == file)
454 {
455 /* if file starts with '/', skip the '/' and return the name
456 * except if it is exactly '/', which we return unmodified
457 */
458 if (*ptr == '/' && *(ptr+1) != '\0') name = strdup(file+1);
459 else name = strdup(file);
460 }
461 else
462 {
463 length = strlen(file) - (ptr - file);
464 name = malloc(length+1);
465 strncpy(name,ptr+1,length);
466 name[length] = '\0';
467 }
468
469 return name;
470 }
471
472 /* \brief remove // /./ and /../ from filename
473 *
474 * Return filename with any useless component removed: double /
475 * /./ or /../
476 * Modifications does not check that filename is valid.
477 * WARNING: this function does NOT check anything on filename, it just
478 * operates on the raw string (i.e it is the responsability of the caller
479 * eo check that there is no path injection in string
480 * (eg: "c:/../d:/pathname" )
481 * This function modify filename !
482 */
path_simplify(char * filename)483 char * path_simplify(char *filename)
484 {
485 int pos, pos2;
486
487 if (!filename) return filename;
488
489 pos = pos2 = 0;
490
491 while(filename[pos] != '\0')
492 {
493 switch (filename[pos])
494 {
495 case '/':
496 if (filename[pos+1] == '/') ;
497 else if ((strncmp(filename + pos, "/./", 3) == 0) ||
498 (strcmp(filename + pos, "/.") == 0))
499 pos++;
500 else if ((strncmp(filename + pos, "/../", 4) == 0) ||
501 (strcmp(filename + pos, "/..") == 0))
502 {
503 if (pos2 > 1)
504 pos2--;
505 while ((pos2 > 0) && (filename[pos2] != '/'))
506 pos2--;
507 pos += 2; /* /.. */
508 if (filename[pos2] != '/') /* ex: toto/../dir */
509 pos++;
510 }
511 else
512 {
513 filename[pos2] = '/';
514 pos2++;
515 }
516 break;
517 default:
518 filename[pos2] = filename[pos];
519 pos2++;
520 }
521 pos++;
522 }
523
524 if (pos2 == 0)
525 {
526 filename[pos2] = '/';
527 pos2++;
528 }
529 filename[pos2] = '\0';
530
531 return filename;
532 }
533
534