1 /*
2  * ProFTPD: mod_vroot Path API
3  * Copyright (c) 2016 TJ Saunders
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
18  *
19  * As a special exemption, the respective copyright holders give permission
20  * to link this program with OpenSSL, and distribute the resulting
21  * executable, without including the source code for OpenSSL in the source
22  * distribution.
23  */
24 
25 #include "path.h"
26 #include "alias.h"
27 
28 static char vroot_base[PR_TUNABLE_PATH_MAX + 1];
29 static size_t vroot_baselen = 0;
30 
31 static const char *trace_channel = "vroot.path";
32 
33 /* Support routines note: some of these support functions are borrowed from
34  * pure-ftpd.
35  */
36 
strmove(register char * dst,register const char * src)37 static void strmove(register char *dst, register const char *src) {
38   if (dst == NULL ||
39       src == NULL) {
40     return;
41   }
42 
43   while (*src != 0) {
44     *dst++ = *src++;
45   }
46 
47   *dst = 0;
48 }
49 
vroot_path_have_base(void)50 int vroot_path_have_base(void) {
51   if (*vroot_base == '\0') {
52     return FALSE;
53   }
54 
55   return TRUE;
56 }
57 
vroot_path_get_base(pool * p,size_t * baselen)58 const char *vroot_path_get_base(pool *p, size_t *baselen) {
59   if (p == NULL) {
60     errno = EINVAL;
61     return NULL;
62   }
63 
64   if (baselen != NULL) {
65     *baselen = vroot_baselen;
66   }
67 
68   return pstrdup(p, vroot_base);
69 }
70 
vroot_path_set_base(const char * base,size_t baselen)71 int vroot_path_set_base(const char *base, size_t baselen) {
72   if (base == NULL) {
73     errno = EINVAL;
74     return -1;
75   }
76 
77   memset(vroot_base, '\0', sizeof(vroot_base));
78   memcpy(vroot_base, base, sizeof(vroot_base)-1);
79   vroot_baselen = baselen;
80 
81   return 0;
82 }
83 
vroot_path_clean(char * path)84 void vroot_path_clean(char *path) {
85   char *ptr = NULL;
86 
87   if (path == NULL ||
88       *path == 0) {
89     return;
90   }
91 
92   ptr = strstr(path, "//");
93   while (ptr != NULL) {
94     strmove(ptr, ptr + 1);
95     ptr = strstr(path, "//");
96   }
97 
98   ptr = strstr(path, "/./");
99   while (ptr != NULL) {
100     strmove(ptr, ptr + 2);
101     ptr = strstr(path, "/./");
102   }
103 
104   while (strncmp(path, "../", 3) == 0) {
105     path += 3;
106   }
107 
108   ptr = strstr(path, "/../");
109   if (ptr != NULL) {
110     if (ptr == path) {
111       while (strncmp(path, "/../", 4) == 0) {
112         strmove(path, path + 3);
113       }
114 
115       ptr = strstr(path, "/../");
116     }
117 
118     while (ptr != NULL) {
119       char *next_elem = ptr + 4;
120 
121       if (ptr != path &&
122           *ptr == '/') {
123         ptr--;
124       }
125 
126       while (ptr != path &&
127              *ptr != '/') {
128         ptr--;
129       }
130 
131       if (*ptr == '/') {
132         ptr++;
133       }
134 
135       strmove(ptr, next_elem);
136       ptr = strstr(path, "/../");
137     }
138   }
139 
140   ptr = path;
141 
142   if (*ptr == '.') {
143     ptr++;
144 
145     if (*ptr == '\0') {
146       return;
147     }
148 
149     if (*ptr == '/') {
150       while (*ptr == '/') {
151         ptr++;
152       }
153 
154       strmove(path, ptr);
155     }
156   }
157 
158   if (*ptr == '\0') {
159     return;
160   }
161 
162   ptr = path + strlen(path) - 1;
163   if (*ptr != '.' ||
164       ptr == path) {
165     return;
166   }
167 
168   ptr--;
169   if (*ptr == '/' ||
170       ptr == path) {
171     ptr[1] = '\0';
172     return;
173   }
174 
175   if (*ptr != '.' ||
176       ptr == path) {
177     return;
178   }
179 
180   ptr--;
181   if (*ptr != '/') {
182     return;
183   }
184 
185   *ptr = '\0';
186   ptr = strrchr(path, '/');
187   if (ptr == NULL) {
188     *path = '/';
189     path[1] = '\0';
190     return;
191   }
192 
193   ptr[1] = '\0';
194 }
195 
vroot_realpath(pool * p,const char * path,int flags)196 char *vroot_realpath(pool *p, const char *path, int flags) {
197   char *real_path = NULL;
198   size_t real_pathlen;
199 
200   if (flags & VROOT_REALPATH_FL_ABS_PATH) {
201     /* If not an absolute path, prepend the current location. */
202     if (*path != '/') {
203       real_path = pdircat(p, pr_fs_getvwd(), path, NULL);
204 
205     } else {
206       real_path = pstrdup(p, path);
207     }
208 
209   } else {
210     real_path = pstrdup(p, path);
211   }
212 
213   vroot_path_clean(real_path);
214 
215   /* If the given path ends in a slash, remove it.  The handling of
216    * VRootAliases is sensitive to such things.
217    */
218   real_pathlen = strlen(real_path);
219   if (real_pathlen > 1 &&
220       real_path[real_pathlen-1] == '/') {
221     real_path[real_pathlen-1] = '\0';
222     real_pathlen--;
223   }
224 
225   return real_path;
226 }
227 
vroot_path_lookup(pool * p,char * path,size_t pathlen,const char * dir,int flags,char ** alias_path)228 int vroot_path_lookup(pool *p, char *path, size_t pathlen, const char *dir,
229     int flags, char **alias_path) {
230   char buf[PR_TUNABLE_PATH_MAX + 1], *bufp = NULL;
231 
232   memset(buf, '\0', sizeof(buf));
233   memset(path, '\0', pathlen);
234 
235   if (strcmp(dir, ".") != 0) {
236     sstrncpy(buf, dir, sizeof(buf));
237 
238   } else {
239     sstrncpy(buf, pr_fs_getcwd(), sizeof(buf));
240   }
241 
242   vroot_path_clean(buf);
243 
244   bufp = buf;
245 
246   if (strncmp(bufp, vroot_base, vroot_baselen) == 0) {
247     bufp += vroot_baselen;
248   }
249 
250 loop:
251   pr_signals_handle();
252 
253   if (bufp[0] == '.' &&
254       bufp[1] == '.' &&
255       (bufp[2] == '\0' ||
256        bufp[2] == '/')) {
257     char *tmp = NULL;
258 
259     tmp = strrchr(path, '/');
260     if (tmp != NULL) {
261       *tmp = '\0';
262 
263     } else {
264       *path = '\0';
265     }
266 
267     if (strncmp(path, vroot_base, vroot_baselen) == 0 ||
268          path[vroot_baselen] != '/') {
269       snprintf(path, pathlen, "%s/", vroot_base);
270     }
271 
272     if (bufp[0] == '.' &&
273         bufp[1] == '.' &&
274         bufp[2] == '/') {
275       bufp += 3;
276       goto loop;
277     }
278 
279   } else if (*bufp == '/') {
280     snprintf(path, pathlen, "%s/", vroot_base);
281     bufp += 1;
282     goto loop;
283 
284   } else if (*bufp != '\0') {
285     size_t buflen, tmplen;
286     char *ptr = NULL;
287 
288     ptr = strstr(bufp, "..");
289     if (ptr != NULL) {
290       size_t ptrlen;
291 
292       /* We need to watch for path components/filenames which legitimately
293        * contain two or more periods in addition to other characters.
294        */
295 
296       ptrlen = strlen(ptr);
297       if (ptrlen >= 3) {
298 
299         /* If this ".." occurrence is the start of the buffer AND the next
300          * character after the ".." is a slash, then deny it.
301          */
302         if (ptr == bufp &&
303             ptr[2] == '/') {
304           errno = EPERM;
305           return -1;
306         }
307 
308         /* If this ".." occurrence is NOT the start of the buffer AND the
309          * characters preceeding and following the ".." are slashes, then
310          * deny it.
311          */
312         if (ptr != bufp &&
313             ptr[-1] == '/' &&
314             ptr[2] == '/') {
315           errno = EPERM;
316           return -1;
317         }
318       }
319     }
320 
321     buflen = strlen(bufp) + 1;
322     tmplen = strlen(path);
323 
324     if (tmplen + buflen >= pathlen) {
325       errno = ENAMETOOLONG;
326       return -1;
327     }
328 
329     path[tmplen] = '/';
330     memcpy(path + tmplen + 1, bufp, buflen);
331   }
332 
333   /* Clean any unnecessary characters added by the above processing. */
334   vroot_path_clean(path);
335 
336   if (!(flags & VROOT_LOOKUP_FL_NO_ALIAS)) {
337     int alias_count;
338 
339     /* Check to see if this path is an alias; if so, return the real path. */
340     alias_count = vroot_alias_count();
341     if (alias_count > 0) {
342       char *start_ptr = NULL, *end_ptr = NULL;
343       const char *src_path = NULL;
344 
345       /* buf is used here for storing the "suffix", to be appended later when
346        * aliases are found.
347        */
348       bufp = buf;
349       start_ptr = path;
350 
351       while (start_ptr != NULL) {
352         char *ptr = NULL;
353 
354         pr_signals_handle();
355 
356         pr_trace_msg(trace_channel, 15, "checking for alias for '%s'",
357           start_ptr);
358 
359         src_path = vroot_alias_get(start_ptr);
360         if (src_path != NULL) {
361           pr_trace_msg(trace_channel, 15, "found '%s' for alias '%s'", src_path,
362             start_ptr);
363 
364           /* If the caller provided a pointer for wanting to know the full
365            * alias path (not the true path), then fill that pointer.
366            */
367           if (alias_path != NULL) {
368             if (end_ptr != NULL) {
369               *alias_path = pdircat(p, start_ptr, end_ptr + 1, NULL);
370 
371             } else {
372               *alias_path = pstrdup(p, start_ptr);
373             }
374 
375             pr_trace_msg(trace_channel, 19, "using alias path '%s' for '%s'",
376               *alias_path, start_ptr);
377           }
378 
379           sstrncpy(path, src_path, pathlen);
380 
381           if (end_ptr != NULL) {
382             /* Now tack on our suffix from the scratchpad. */
383             sstrcat(path, bufp, pathlen);
384           }
385 
386           break;
387         }
388 
389         ptr = strrchr(start_ptr, '/');
390 
391         if (end_ptr != NULL) {
392           *end_ptr = '/';
393         }
394 
395         if (ptr == NULL) {
396           break;
397         }
398 
399         /* If this is the start of the path, we're done. */
400         if (ptr == start_ptr) {
401           break;
402         }
403 
404         /* Store the suffix in the buf scratchpad. */
405         sstrncpy(buf, ptr, sizeof(buf));
406         end_ptr = ptr;
407         *end_ptr = '\0';
408       }
409     }
410   }
411 
412   return 0;
413 }
414