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