mmlib/mmat: replace some variables by literal constants
[mmondor.git] / mmsoftware / mmlib / mmpath.c
1 /* $Id: mmpath.c,v 1.21 2008/01/13 09:10:35 mmondor Exp $ */
2
3 /*
4 * Copyright (C) 2001-2004, Matthew Mondor
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by Matthew Mondor.
18 * 4. The name of Matthew Mondor may not be used to endorse or promote
19 * products derived from this software without specific prior written
20 * permission.
21 * 5. Redistribution of source code may not be released under the terms of
22 * any GNU Public License derivate.
23 *
24 * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
27 * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
30 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36
37
38
39 /* HEADERS */
40
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <ctype.h>
44 #include <stdbool.h>
45 #include <stdio.h>
46 #include <syslog.h>
47 #include <stdarg.h>
48 #include <dirent.h>
49 #include <time.h>
50 #include <fnmatch.h>
51 #include <string.h>
52
53 #include <mmtypes.h>
54 #include <mmpath.h>
55 #include <mmfd.h>
56 #include <mmstring.h>
57 #include <mmlog.h>
58
59
60
61
62 static bool path_ls2(fdbuf *, const char *, const char *, const char *,
63 const char *, int, uid_t, int);
64
65
66
67
68 MMCOPYRIGHT("@(#) Copyright (c) 2001-2004\n\
69 \tMatthew Mondor. All rights reserved.\n");
70 MMRCSID("$Id: mmpath.c,v 1.21 2008/01/13 09:10:35 mmondor Exp $");
71
72
73
74
75 /* GLOBALS */
76
77 static const char *months[] = {
78 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
79 "Oct", "Nov", "Dec", NULL
80 };
81
82 static const char *perms[] = {
83 "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx", NULL
84 };
85
86
87
88
89 /* FUNCTIONS */
90
91 /* Copies src to dst performing path sanity checking and replacing multiple
92 * consecutive '/' by a single one. dst will start with '/' and end without
93 * trailing '/'. Returns TRUE on success, or FALSE if invalid path was supplied
94 * as src.
95 */
96 bool
97 path_copy(char *dst, const char *src, size_t max, bool glob)
98 {
99 const char *srcptr;
100 char *dstptr, *todstptr, l;
101 int i1, i2;
102 bool ok = TRUE;
103
104 srcptr = src;
105 dstptr = todstptr = dst;
106 todstptr += (max - 1);
107 *dstptr++ = l = '/';
108 i1 = i2 = 0;
109 while (*srcptr != '\0' && dstptr < todstptr) {
110 if (l == '/') {
111 /* Only allow globbing characters in the last path element */
112 if (i1 || i2) {
113 ok = FALSE;
114 break;
115 }
116 /* Replace multiple consecutive instances of '/' by one */
117 while (*srcptr == '/')
118 srcptr++;
119 /* Prohibit '.' at start of new path element */
120 if (*srcptr == '.') {
121 ok = FALSE;
122 break;
123 }
124 }
125 if (*srcptr == '\0')
126 break;
127 /* Only allow '*' if globbing is allowed, and a maximum of three */
128 if (*srcptr == '*') {
129 if (glob && i1 < 3)
130 i1++;
131 else {
132 ok = FALSE;
133 break;
134 }
135 /* Only allow these if globbing is allowed */
136 } else if (ISGLOBCHAR(*srcptr)) {
137 if (glob)
138 i2++;
139 else {
140 ok = FALSE;
141 break;
142 }
143 }
144 /* Refuse illegal characters for paths */
145 if (!((isprint((int)*srcptr) || *srcptr == ' ') && *srcptr != '%' &&
146 *srcptr != '\\' && *srcptr != '~')) {
147 ok = FALSE;
148 break;
149 }
150 *dstptr++ = l = *srcptr++;
151 }
152 if (dstptr > (dst + 1) && dstptr[-1] == '/')
153 dstptr[-1] = '\0';
154 else
155 *dstptr = '\0';
156
157 return ok;
158 }
159
160
161 /* Useful function to process user supplied paths securely, into a fake
162 * chroot jail environment. See mmpath(3) man page for more information.
163 */
164 bool
165 path_valid(char *to, char *clean, const char *root, const char *cwd,
166 char *path, bool glob, bool chd, bool empty)
167 {
168 char tcwd[MMPATH_MAX + 1], tmpstr[MMPATH_MAX + 1], *start, *tmp;
169 bool t1 = FALSE;
170
171 *to = '\0';
172 if (chd)
173 glob = FALSE;
174 strncpy(tcwd, cwd, MMPATH_MAX);
175
176 /* First strip starting and trailing spaces from supplied path,
177 * as well as trailing '/' characters. Also get rid of any starting '.',
178 * '~' or '/', in which two last cases causes tcwd to be cleared.
179 * If no path was supplied, refuse the entry.
180 */
181 for (tmp = path; *tmp != '\0' && *tmp == ' '; tmp++) ;
182 if (*tmp == '\0' && !empty)
183 return FALSE;
184 if (*tmp == '~') {
185 *tcwd = '\0';
186 tmp++;
187 } else if (*tmp == '/') {
188 while (*tmp == '/')
189 tmp++;
190 *tcwd = '\0';
191 } else if (tmp[0] == '.' && ((t1 = (tmp[1] == '/')) || tmp[1] == '\0')) {
192 tmp++;
193 if (t1)
194 tmp++;
195 }
196 for (start = tmp; *tmp != '\0'; tmp++) ;
197 for (tmp--; *tmp == ' '; tmp--)
198 *tmp = '\0';
199 for (; *tmp == '/'; tmp--)
200 *tmp = '\0';
201 if (tmp == start && *tmp == '\0' && !empty)
202 return FALSE;
203
204 /* Now process all starting ".." or "../", modifying tcwd */
205 tmp = start;
206 while (tmp[0] == '.' && tmp[1] == '.' &&
207 (tmp[2] == '\0' || (t1 = (tmp[2] == '/')))) {
208 if (!path_parent(tcwd))
209 return FALSE;
210 tmp += 2;
211 if (t1) {
212 while (*tmp == '/')
213 tmp++;
214 }
215 }
216 start = tmp;
217 if (tcwd[0] == '/' && tcwd[1] == '\0')
218 tcwd[0] = '\0';
219
220 if (chd) {
221 /* We were called to supply new safe cwd */
222 snprintf(tmpstr, MMPATH_MAX - 1, "/%s/%s", tcwd, start);
223 if (path_copy(to, tmpstr, MMPATH_MAX, FALSE))
224 return TRUE;
225 return FALSE;
226 }
227
228 /* We should supply caller with both absolute path (system wide) and
229 * clean path having the appearance of an absolute path, into the
230 * fake chrooted environment, suitable to report to the user.
231 */
232 snprintf(tmpstr, MMPATH_MAX - 1, "/%s/%s/%s", root, tcwd, start);
233 if (path_copy(to, tmpstr, MMPATH_MAX, glob)) {
234 if (clean) {
235 snprintf(tmpstr, MMPATH_MAX - 1, "/%s/%s", tcwd, start);
236 if (path_copy(clean, tmpstr, MMPATH_MAX, glob))
237 return TRUE;
238 } else
239 return TRUE;
240 }
241
242 return FALSE;
243 }
244
245
246 /* Returns TRUE if it could go to parent directory, and fixes the string,
247 * or FALSE if it couldn't (reached '/'). cwd is expected to start with '/'
248 */
249 bool
250 path_parent(char *cwd)
251 {
252 register size_t i;
253
254 i = strlen(cwd);
255 if (i < 2 || *cwd != '/')
256 return FALSE;
257
258 /* Strip possible trailing '/' */
259 for (i--; cwd[i] == '/'; i--)
260 cwd[i] = '\0';
261
262 /* Locate next '/' */
263 while (i != 0 && cwd[i] != '/')
264 i--;
265 if (i != 0)
266 cwd[i] = '\0';
267 else
268 cwd[i + 1] = '\0';
269
270 return TRUE;
271 }
272
273
274 /* Returns MMPATH_DIR if file exists and is a directory, MMPATH_FILE if a
275 * normal data file, and returns MMPATH_NONE if file does not exist,
276 * if file is a symbolic link, or is not owned by us (if own is TRUE),
277 * for security considerations.
278 * Of course the filename should have been returned by path_valid()
279 * If size or time pointers are supplied, we fill them. Supplied time buffer
280 * should at least be 16 bytes. The timestamp is returned in the very old
281 * YYYYMMDDHHMMSS format which eventually became ISO 3307 standard.
282 */
283 int
284 path_exists(const char *file, long *size, char *time, bool own)
285 {
286 struct stat st;
287 struct tm tm;
288 register int ret;
289
290 if ((lstat(file, &st)) != -1) {
291 /* Evaluate if file/directory is allowed access to */
292 if (!own || st.st_uid == geteuid()) {
293 if (S_ISDIR(st.st_mode)) {
294 if (access(file, R_OK | X_OK) == 0)
295 ret = MMPATH_DIR;
296 else
297 ret = MMPATH_DENIED;
298 } else if (S_ISREG(st.st_mode)) {
299 if (access(file, R_OK) == 0) {
300 ret = MMPATH_FILE;
301 if (size != NULL)
302 *size = st.st_size;
303 if (time != NULL) {
304 if ((gmtime_r(&st.st_mtime, &tm)) != NULL) {
305 snprintf(time, 15, "%04d%02d%02d%02d%02d%02d",
306 1900 + tm.tm_year, tm.tm_mon + 1,
307 tm.tm_mday, tm.tm_hour, tm.tm_min,
308 tm.tm_sec);
309 time[15] = '\0';
310 }
311 }
312 } else
313 ret = MMPATH_DENIED;
314 } else {
315 ret = MMPATH_DENIED;
316 syslog(LOG_NOTICE,
317 "path_exists() - Special file: '%s'", file);
318 }
319 } else {
320 ret = MMPATH_DENIED;
321 syslog(LOG_NOTICE,
322 "path_exists() - Wrong owner: '%s'", file);
323 }
324 } else
325 ret = MMPATH_NONE;
326
327 return ret;
328 }
329
330
331 /* This function lists the contents of a directory or information on a file,
332 * or of files matching a pattern, outputting the list to fdb.
333 * See mmpath(3) man page for more information.
334 * NOTE: fts_open(3) and companions could have been used; They unfortunately
335 * need to perform lstat(2) calls for each entry anyways if we are to obtain
336 * owner information. Also, lstat(2) will solve the glibc problem about
337 * opendir(3) interface which was not fully implemented, as we can use all
338 * required information from the stat structure.
339 *
340 * Possibly a cache could be maintained of lstat(2) results; This however
341 * would imply that all functions performed on files also update this cache,
342 * using provided wrappers for rename(2), unlink(2), chmod(2) and chown(2)...
343 * And the cache would need to be hashed-based, with absolute paths to not
344 * confuse itself between various filesystems. A similar cache could be
345 * maintained for uid->name conversion if we were to show the real owner
346 * of a file when own is FALSE.
347 */
348 bool
349 path_ls(fdbuf *fdb, const char *path, const char *user, const char *group,
350 int flags)
351 {
352 bool ret = TRUE;
353 int i;
354 char d[MMPATH_MAX], p[MMPATH_MAX], t[MMPATH_MAX], t2[MMPATH_MAX];
355 const char *tmp;
356 DIR *dir;
357 struct dirent *dire;
358 struct tm tm;
359 time_t tim;
360 uid_t uid = 0;
361 size_t len;
362
363 /* First determine directory to opendir() */
364 if ((i = path_exists(path, NULL, NULL, (flags & MMLS_OWNERONLY)))
365 != MMPATH_DENIED) {
366 /* Cache current time to compare year */
367 tim = time(NULL);
368 gmtime_r(&tim, &tm);
369 /* And current uid to compare owner */
370 uid = geteuid();
371 if (i == MMPATH_FILE) {
372 /* Requesting listing for a single file, first isolate
373 * filename from path, then display it
374 */
375 len = strlen(path);
376 for (tmp = &path[(len > 0 ? len - 1 : 0)];
377 *tmp != '\0' && *tmp != '/'; tmp--) ;
378 if (*tmp == '/')
379 tmp++;
380 return (path_ls2(fdb, path, tmp, user, group, tm.tm_year, uid,
381 flags));
382 } else if (i == MMPATH_DIR) {
383 /* Requesting listing of a whole directory */
384 i = mm_strncpy(d, path, MMPATH_MAX - 2);
385 i--;
386 if (i && d[i] == '/')
387 d[i] = '\0';
388 flags &= ~MMLS_GLOB;
389 } else if ((flags & MMLS_GLOB) != 0) { /* MMPATH_NONE */
390 /* Requesting list of files matching a pattern in a directory,
391 * first isolate pattern and directory, make sure we only allow
392 * patterns on files (last path component)
393 */
394 len = strlen(path);
395 for (tmp = &path[(len > 0 ? len - 1 : 0)];
396 *tmp != '\0' && *tmp != '/'; tmp--) ;
397 if (*tmp == '/')
398 tmp++;
399 strncpy(p, tmp, MMPATH_MAX - 1);
400 i = mm_strncpy(d, path, tmp - path);
401 i--;
402 if (i && d[i] == '/')
403 d[i] = '\0';
404 } else
405 ret = FALSE;
406 } else
407 ret = FALSE;
408
409 if (ret) {
410 /* Scan directory listing files */
411 if ((dir = opendir(d)) != NULL) {
412 while ((dire = readdir(dir)) != NULL) {
413 tmp = dire->d_name;
414 /* Skip any "hidden" or curr/prev files */
415 if (*tmp != '.') {
416 if ((flags & MMLS_GLOB) != 0)
417 if (fnmatch(p, tmp, FNM_NOESCAPE | FNM_PATHNAME |
418 FNM_PERIOD) != 0)
419 tmp = NULL;
420 if (tmp != NULL) {
421 snprintf(t2, MMPATH_MAX - 1, "%s/%s", d, tmp);
422 if (path_copy(t, t2, MMPATH_MAX - 1, FALSE)) {
423 if (!(ret = path_ls2(fdb, t, tmp, user, group,
424 tm.tm_year, uid, flags)))
425 break;
426 }
427 }
428 }
429 }
430 closedir(dir);
431 } else
432 syslog(LOG_NOTICE, "* path_ls() - opendir(%s)", d);
433 }
434
435 return ret;
436 }
437
438
439 /* I agree that security cannot be obtained by obfuscation alone; I however
440 * still consider hiding data which could have a misuse potential as part of
441 * a good security plan. We will not show any special files, or potentially
442 * dangerous permission bits (eg: setuid bit). We also cause special files
443 * to be ignored.
444 */
445 static bool
446 path_ls2(fdbuf *fdb, const char *realfile, const char *filetext,
447 const char *user, const char *group, int year, uid_t uid, int flags)
448 {
449 bool ret = TRUE;
450 struct stat st;
451 struct tm tm;
452 mode_t mode;
453 const char *usr, *grp, *oth;
454 char type, hy[8];
455
456 if ((lstat(realfile, &st)) != -1) {
457 mode = st.st_mode;
458 /* Evaluate if we should display file, or hide and log it */
459 if ((((flags & MMLS_OWNERONLY) == 0) || st.st_uid == uid) &&
460 (S_ISREG(mode) || S_ISDIR(mode))) {
461 if ((flags & MMLS_LONG) != 0) {
462 if ((gmtime_r(&st.st_mtime, &tm)) != NULL) {
463 if (tm.tm_year == year)
464 snprintf(hy, 6, "%02d:%02d", tm.tm_hour, tm.tm_min);
465 else
466 snprintf(hy, 6, " %04d", 1900 + tm.tm_year);
467 /* Obfuscation alone obviously doesn't guarantee security;
468 * Good administration and obfuscation are however welcome.
469 */
470 mode &= ~(S_ISUID | S_ISGID | S_ISVTX);
471 if (S_ISDIR(mode)) {
472 type = 'd';
473 mode &= ~S_IFMT;
474 if ((flags & MMLS_READONLY) != 0)
475 mode &= ~0222;
476 } else {
477 type = '-';
478 mode &= ~S_IFMT;
479 if ((flags & MMLS_READONLY) != 0)
480 mode &= ~0333;
481 }
482 usr = perms[(mode & 0700) >> 6];
483 grp = perms[(mode & 0070) >> 3];
484 oth = perms[mode & 0007];
485 ret = fdbprintf(fdb,
486 "%c%s%s%s %3d %s %s %9ld %s %2d %s %s\r\n",
487 type, usr, grp, oth, st.st_nlink, user, group,
488 (long)st.st_size, months[tm.tm_mon],
489 tm.tm_mday, hy, filetext);
490 } else
491 syslog(LOG_NOTICE, "path_ls2() - gmtime_r()");
492 } else
493 ret = fdbprintf(fdb, "%s\r\n", filetext);
494 } else
495 syslog(LOG_NOTICE,
496 "path_ls2() - Special file or wrong owner: '%s'",
497 realfile);
498 } else
499 syslog(LOG_NOTICE, "path_ls2 - lstat(%s)", realfile);
500
501 return (ret);
502 }