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