/*****************************************************************************\ * Copyright (c) 2002-2003 Pelle Johansson. * * All rights reserved. * * * * This file is part of the moftpd package. Use and distribution of * * this software is governed by the terms in the file LICENCE, which * * should have come with this package. * \*****************************************************************************/ /* $moftpd: file.c 1224 2004-10-28 22:42:00Z morth $ */ #include "system.h" #include "file.h" #include "memory.h" #include "utf8.h" static char currRoot[4097] = "/"; int fakeChroot = 0; access_t *access_chain; int defaultHardLink = 0; int external_rfd = -1, external_wfd = -1; /*@null@*/ const char *set_root(const char *root) { if(strcmp(root, currRoot)) { if(!fakeChroot && strcmp(currRoot, "/")) { errno = EINVAL; return NULL; } strcpy(currRoot, root); if (chdir (currRoot)) return NULL; if(!fakeChroot) { int res; uid_t euid; euid = geteuid(); (void)seteuid (0); res = chroot(currRoot); (void)seteuid(euid); if (res) return NULL; } else { // Read back the dir to avoid any symlinks encountered. (void)getcwd (currRoot, sizeof (currRoot) - 1); } } return currRoot; } char *full_path(const char *path, /*@null@*/const char *cwd, /*@null@*/const char *home) { static char res[4097]; char tmp[4097], *dstart, *dsep, *dcurr, *dnext, *rres; const char *tdcurr; struct passwd *pwd; size_t i; struct stat st; int nsym; char *ls = NULL, *tls; /* Check for ~ expansion. */ if(path[0] == '~') { /* If no home directory was specified, we expand ~ back to itself. */ if(!home) home = "~"; switch(path[1]) { case 0: /* Home directory as-is. */ strncpy(res, home, sizeof(res) - 1); break; case '/': /* Home directory followed by path. */ strncpy(res, home, sizeof(res) - 1); strncat(res, &path[1], sizeof(res) - 1 - strlen(res)); break; default: /* Home directory of specified user. Only available when not chrooted. */ if(!strcmp(currRoot, "/")) { dsep = strchr(path, '/'); if(dsep) i = dsep - path - 1; else i = strlen(path) - 1; if(i > 4096) i = 4096; strncpy(tmp, &path[1], i); tmp[i] = 0; pwd = getpwnam(tmp); if(pwd) { strcpy(res, pwd->pw_dir); if(dsep) strncat(res, dsep, sizeof(res) - 1 - strlen(res)); } else { /* If no such user, keep the path unmodified. */ strncpy(res, path, sizeof(res) - 1); } } else strncpy(res, path, sizeof(res) - 1); break; } } else strncpy(res, path, sizeof(res) - 1); if(res[0] != '/') { /* * Prepend the current directory, but strip out any faked chroot. * The root is supposed to be the chrooted directory after all. */ if (!cwd) cwd = ""; snprintf(tmp, sizeof(tmp) - 1, "%s/%s", cwd, res); if (fakeChroot) { if (strcmp(currRoot, "/") && !strncmp(tmp, currRoot, strlen(currRoot))) memmove(tmp, &tmp[strlen(currRoot)], strlen(tmp) - strlen(currRoot) + 1); } } else strcpy(tmp, res); /* * Finally remove any .., . and // entries. They could otherwise be used to * escape a faked chroot, and besides, it looks nicer to not have them. */ if (fakeChroot && strcmp (currRoot, "/")) strcpy (res, currRoot); else res[0] = 0; rres = dstart = res + strlen (res); nsym = 0; for(dcurr = tmp + 1; dcurr; dcurr = dnext) { dnext = strchr(dcurr, '/'); if(dnext) *dnext++ = 0; if(!strlen(dcurr) || !strcmp(dcurr, ".")) continue; if(!strcmp(dcurr, "..")) { dstart = strrchr(rres, '/'); if(dstart) *dstart = 0; else dstart = rres + strlen (rres); } else { i = 0; *dstart++ = '/'; strcpy (dstart, dcurr); if (lstat (res, &st)) { // If it doesn't exist, try the UTF-8 NFC variant. tdcurr = make_utf8 (dcurr, 0, 0); if (tdcurr != dcurr && strcmp (tdcurr, "..")) { strcpy (dstart, tdcurr); if (!lstat (res, &st)) i = 1; } if (!i) { // We've tried UTF-8 NFC, now try NFD. tdcurr = make_utf8 (dcurr, 0, 1); if (tdcurr != dcurr && strcmp (tdcurr, "..")) { strcpy (dstart, tdcurr); if (!lstat (res, &st)) i = 1; } if (!i) { // Nope, now try native charset. tdcurr = unmake_utf8 (dcurr); if (tdcurr && strcmp (tdcurr, dcurr) && strcmp (tdcurr, "..")) { strcpy (dstart, tdcurr); if (!lstat (res, &st)) i = 1; } if (!i) strcpy (dstart, make_utf8 (dcurr, 0, 0)); // If all else fail, use NFC } } } else i = 1; if (i && S_ISLNK(st.st_mode) && !check_hardlink (res)) { if (++nsym > 256) { errno = ELOOP; return NULL; } if (!ls) ls = talloc (MAXPATHLEN + 1); i = readlink (res, ls, MAXPATHLEN); if (i == -1) return NULL; ls[i] = 0; tls = ls; if (*tls == '/') { // Start all over again. *rres = 0; dstart = rres + 1; tls++; i--; } if (dnext) { int l; l = strlen (dnext) + 1; if (sizeof (tmp) - i - 1 < l) l = sizeof (tmp) - i - 1; memmove (tmp + i + 1, dnext, l); tmp[sizeof (tmp) - 1] = 0; } strcpy (tmp, tls); if (dnext) tmp[i] = '/'; dnext = tmp; *--dstart = 0; } else dstart += strlen (dstart); } } /* Make sure there's something in there. */ if(!strlen(rres)) strcpy(rres, "/"); return rres; } char *chroot_path(const char *path, const char *cwd, const char *home) { static char res[4097]; char *fpath; /* * If we are chrooted, we want to strip it out of the home directory. */ if(home) home = strip_path (home); fpath = full_path(path, cwd, home); if (fakeChroot && strcmp (currRoot, "/")) { snprintf(res, sizeof(res) - 1, "%s%s", currRoot, fpath); return res; } return fpath; } const char *strip_path (const char *path) { if(fakeChroot) { if(strcmp(currRoot, "/") && !strncmp(path, currRoot, strlen(currRoot))) { if(strlen(path) == strlen(currRoot)) return "/"; return &path[strlen(currRoot)]; } } return path; } const char *print_path(const char *path) { return make_utf8 (strip_path (path), 0, 0); } char *make_file_list(const char *mask, const char *cwd, const char *home, int verbose, const char *prepend) { int endOfFlags; int dotFiles = 0; char *path, *maskstart; int i; char wpath[4097]; while(mask[0] == '-') { /* There are flags present. */ endOfFlags = 0; while(!endOfFlags) { switch(*++mask) { case 0: endOfFlags = 1; break; case ' ': mask++; endOfFlags = 1; break; case 'a': dotFiles = 1; break; default: /* Silently ignore unknown flags. */ break; } } } if(!strlen(mask)) mask = "."; path = chroot_path(mask, cwd, home); /* * We only want to look for wildcards in the mask part of the path, so find * out where it starts. Some of the mask might have been cut out, so start * at the end and work backwards. */ maskstart = path + strlen(path); for(i = strlen(mask); i >= 0; i--, maskstart--) { if(mask[i] != *maskstart) break; } /* maskstart is pointing to the last mismatch, we want the first match. */ maskstart++; i = maskstart - path; strncpy(wpath, path, i); wpath[i] = 0; if(!prepend) prepend = ""; return recurse_file_list(wpath, maskstart, verbose, dotFiles, prepend, 0); } static char *make_time(time_t t) { static char res[13]; char *str = ctime(&t); int i; time_t now = time(NULL); for(i = 0; i < 7; i++) res[i] = str[i + 4]; if(now - t > 60 * 60 * 24 * 182) /* Half a year. */ { res[i++] = ' '; for(; i < 12; i++) res[i] = str[i + 12]; } else { for(; i < 12; i++) res[i] = str[i + 4]; } res[i] = 0; return res; } static const char *get_uid(const char *path, int uid) { struct passwd *pwd; static char uidStr[12]; access_t *acc; size_t apl; char rpath[4096]; const char *fn; /* If not faked, prepend the root. */ if(!fakeChroot && strcmp(currRoot, "/")) sprintf(rpath, "%s%s", currRoot, path); else strcpy(rpath, path); fn = strrchr (rpath, '/'); if (fn) fn++; else fn = rpath; for(acc = access_chain; acc; acc = acc->next) { apl = strlen(acc->path); if(apl <= strlen(rpath) && !strncmp(rpath, acc->path, apl)) { if(acc->path[apl - 1] != '/' && rpath[apl] && rpath[apl] != '/') continue; if (acc->numMasks) { for (apl = 0; apl < acc->numMasks; apl++) { if (match_pattern (acc->masks[apl], fn)) break; } if (apl == acc->numMasks) continue; } if(acc->fakeUser) { if((long)acc->fakeUser == -1) break; return acc->fakeUser; } } } pwd = getpwuid(uid); if(pwd) return pwd->pw_name; sprintf(uidStr, "%u", uid); return uidStr; } static const char *get_gid(const char *path, int gid) { struct group *gr; static char gidStr[12]; access_t *acc; size_t apl; char rpath[4096]; const char *fn; /* If not faked, prepend the root. */ if(!fakeChroot && strcmp(currRoot, "/")) sprintf(rpath, "%s%s", currRoot, path); else strcpy(rpath, path); fn = strrchr (rpath, '/'); if (fn) fn++; else fn = rpath; for(acc = access_chain; acc; acc = acc->next) { apl = strlen(acc->path); if(apl <= strlen(rpath) && !strncmp(rpath, acc->path, apl)) { if(acc->path[apl - 1] != '/' && rpath[apl] && rpath[apl] != '/') continue; if (acc->numMasks) { for (apl = 0; apl < acc->numMasks; apl++) { if (match_pattern (acc->masks[apl], fn)) break; } if (apl == acc->numMasks) continue; } if(acc->fakeGroup) { if((long)acc->fakeGroup == -1) break; return acc->fakeGroup; } } } gr = getgrgid(gid); if(gr) return gr->gr_name; sprintf(gidStr, "%u", gid); return gidStr; } static const char *get_mode(const char *path, int mode) { static char mStr[11]; access_t *acc; size_t apl; char rpath[4096]; const char *fn; if(S_ISDIR(mode) || S_ISREG(mode)) { /* If not faked chroot, prepend the root. */ if(!fakeChroot && strcmp(currRoot, "/")) sprintf(rpath, "%s%s", currRoot, path); else strcpy(rpath, path); fn = strrchr (rpath, '/'); if (fn) fn++; else fn = rpath; for(acc = access_chain; acc; acc = acc->next) { apl = strlen(acc->path); if(apl <= strlen(rpath) && !strncmp(rpath, acc->path, apl)) { if(acc->path[apl - 1] != '/' && rpath[apl] && rpath[apl] != '/') continue; if (acc->numMasks) { for (apl = 0; apl < acc->numMasks; apl++) { if (match_pattern (acc->masks[apl], fn)) break; } if (apl == acc->numMasks) continue; } if(S_ISDIR(mode)) { if(acc->fakeDir) { if((long)acc->fakeDir == -1) break; return acc->fakeDir; } } else if(acc->fakeFile) { if((long)acc->fakeFile == -1) break; return acc->fakeFile; } } } } #ifdef HAVE_STRMODE strmode(mode, mStr); #else // Not complete, but it'll do. sprintf (mStr, "%c%c%c%c%c%c%c%c%c%c", mode & S_IFDIR? 'd' : '-', mode & S_IRUSR? 'r' : '-', mode & S_IWUSR? 'w' : '-', mode & S_IXUSR? 'x' : '-', mode & S_IRGRP? 'r' : '-', mode & S_IWGRP? 'w' : '-', mode & S_IXGRP? 'x' : '-', mode & S_IROTH? 'r' : '-', mode & S_IWOTH? 'w' : '-', mode & S_IXOTH? 'x' : '-'); #endif return mStr; } static char *append_file_info(char *buf, char *name, const char *path, const char *prepend, int verbose) { static char tmp[4197]; struct stat st; if(stat(path, &st) || check_hidden(path)) return buf; if(verbose) { sprintf (tmp, "%s%s %3d %-8s %-8s %8lld %-12s %s\r\n", prepend, get_mode (path, st.st_mode), (int)st.st_nlink, get_uid (path, st.st_uid), get_gid (path, st.st_gid), (long long)st.st_size, make_time (st.st_mtime), make_utf8 (name, 0, 0)); } else sprintf(tmp, "%s%s\r\n", prepend, make_utf8 (name, 0, 0)); if(buf) { buf = trealloc(buf, strlen(buf) + strlen(tmp) + 1); strcat(buf, tmp); } else buf = tstring(tmp); return buf; } char *recurse_file_list(char *path, char *mask, int verbose, int dotFiles, const char *prepend, int recurse_level) { char *slash, *nslash, *mp, *pstart; char *wpath, *wend; char *res = NULL, *append = NULL; char *tapp; DIR *dir; struct dirent *dp; struct stat st; if(recurse_level >= 20) { errno = EINVAL; return NULL; } if(recurse_level) { res = talloc(strlen(prepend) + strlen(path) + 6); sprintf(res, "\r\n%s%s:\r\n", prepend, make_utf8 (path, 0, 0)); } #define MAXWPATH 4096 wpath = talloc (MAXWPATH + 1); if(!wpath) return NULL; if(!mask || !strlen(mask)) { strcpy(wpath, path); if(wpath[strlen(wpath) - 1] != '/') strcat(wpath, "/"); wend = wpath + strlen(wpath); dir = open_dir_listing(path); if(!dir) return NULL; while((dp = readdir(dir))) { if(dp->d_name[0] == '.' && !dotFiles) continue; if(wend - wpath + strlen(dp->d_name) > MAXWPATH) continue; /* Not much we can do. */ strcpy(wend, dp->d_name); res = append_file_info(res, dp->d_name, wpath, prepend, verbose); } closedir(dir); if(!res) res = tstring(""); return res; } strcpy(wpath, path); if(strlen(wpath) < MAXWPATH && wpath[strlen(wpath) - 1] != '/') strcat(wpath, "/"); slash = NULL; for(mp = mask; mp && *mp; mp++) { switch(*mp) { case '/': *mp = 0; if(slash) { *slash = '/'; if(strlen(wpath) + strlen(slash + 1) + 1 > MAXWPATH) break; /* Not much we can do. */ strcat(wpath, slash + 1); strcat(wpath, "/"); } else { if(strlen(wpath) + strlen(mask) + 1 > MAXWPATH) break; /* Not much we can do. */ strcat(wpath, mask); if(wpath[strlen(wpath) - 1] != '/') strcat(wpath, "/"); } slash = mp; break; case '\\': if(mp[1] && mp[1] != '/') memmove(mp, mp + 1, strlen(mp)); break; case '*': case '?': nslash = strchr(mp, '/'); if(nslash) *nslash = 0; if(slash) pstart = slash + 1; else pstart = mask; dir = open_dir_listing(wpath); if(!dir) return NULL; wend = wpath + strlen(wpath); while((dp = readdir(dir))) { if(dp->d_name[0] == '.' && pstart[0] != '.') continue; if (wend - wpath + strlen (dp->d_name) > MAXWPATH) continue; /* Not much we can do. */ strcpy(wend, dp->d_name); #ifdef HAVE_STRUCT_DIRENT_D_TYPE if(nslash && dp->d_type != DT_DIR) continue; #else if (lstat (wpath, &st)) continue; if (nslash && !(st.st_mode & S_IFDIR)) continue; #endif if(match_pattern(pstart, dp->d_name)) { #ifdef HAVE_STRUCT_DIRENT_D_TYPE if(dp->d_type == DT_DIR) #else if (st.st_mode & S_IFDIR) #endif { tapp = recurse_file_list(wpath, nslash ? nslash + 1 : NULL, verbose, dotFiles, prepend, recurse_level + 1); if(tapp) { if(append) { append = trealloc(append, strlen(append) + strlen(tapp) + 1); strcat(append, tapp); } else append = tapp; } } if(!nslash) res = append_file_info(res, dp->d_name, wpath, prepend, verbose); } } *wend = 0; closedir(dir); mp = NULL; break; } if(!mp) break; } if(mp) { if(slash) mask = slash + 1; /* There wasn't any wildcards. Check the path. */ if(strlen(wpath) + strlen(mask) > MAXWPATH) { errno = EINVAL; return NULL; } wend = wpath + strlen(wpath); strcpy(wend, mask); if(lstat(wpath, &st)) return NULL; if(S_ISDIR(st.st_mode)) return recurse_file_list(wpath, NULL, verbose, dotFiles, prepend, recurse_level); else res = append_file_info(res, mask, wpath, prepend, verbose); } if(append) { if(res) { res = trealloc(res, strlen(res) + strlen(append) + 1); strcat(res, append); } else res = append; } if(!res) res = tstring(""); return res; } const char *tagged_file_data (const char *path, const char *name, int tags) { struct stat st; static char buf[4097]; char *bp = buf; if(check_hidden(path) || stat(path, &st)) return NULL; name = make_utf8 (name, 0, 0); if (tags & tfType) { switch (st.st_mode & S_IFMT) { case S_IFIFO: strcpy (bp, "type=OS.Unix=FIFO;"); // Not registered break; case S_IFCHR: strcpy (bp, "type=OS.Unix=CHAR;"); // Not registered break; case S_IFDIR: if (tags & tfPartOfList) { if (!strcmp (name, ".")) strcpy (bp, "type=cdir;"); else if (!strcmp (name, "..")) { if (tags & tfRootDir) { errno = ENOENT; return NULL; } strcpy (bp, "type=pdir;"); } else strcpy (bp, "type=dir;"); } else strcpy (bp, "type=dir;"); break; case S_IFBLK: strcpy (bp, "type=OS.Unix=BLOCK;"); // Not registered break; case S_IFREG: strcpy (bp, "type=file;"); break; case S_IFSOCK: strcpy (bp, "type=OS.Unix=SOCKET;"); // Not registered break; default: break; } bp += strlen (bp); } if (tags & tfUnique) { char uniq[((sizeof (st.st_dev) + sizeof (st.st_ino)) * 4 + 2) / 3 + 1], *up = uniq; char saved = 0; int numSaved = 0, i; st.st_dev ^= 0xAAAAAAAA; for (i = 0; i < (int)sizeof (st.st_dev); i++) { numSaved += 2; *up++ = 0x3E + saved + (((unsigned char *)&st.st_dev)[i] >> numSaved & 0x3F); saved = ((unsigned char *)&st.st_dev)[i] << (6 - numSaved) & 0x3F; if (numSaved == 6) { *up++ = 0x3E + saved; saved = numSaved = 0; } } st.st_ino ^= 0xAAAAAAAA; for (i = 0; i < (int)sizeof (st.st_ino); i++) { numSaved += 2; *up++ = 0x3E + saved + (((unsigned char *)&st.st_ino)[i] >> numSaved & 0x3F); saved = ((unsigned char *)&st.st_ino)[i] << (6 - numSaved) & 0x3F; if (numSaved == 6) { *up++ = 0x3E + saved; saved = numSaved = 0; } } if (numSaved) *up++ = 0x3E + saved; *up = 0; sprintf (bp, "unique=%s;", uniq); bp += strlen (bp); } if (tags & tfModify) { struct tm *tm = gmtime (&st.st_mtime); // TODO: fractions sprintf (bp, "modify=%04d%02d%02d%02d%02d%02d;", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); bp += strlen (bp); } #if 0 if (tags & tfCreate) { } #endif if (tags & tfPerm) { strcpy (bp, "perm="); bp += strlen (bp); if (!check_access (path, acSearch)) { char *pp = strrchr (path, '/'); const char *ppath; if (pp && pp != path) { char *ppa = talloc (pp - path + 1); strncpy (ppa, path, pp - path); ppa[pp - path] = 0; ppath = ppa; } else ppath = "/"; // TODO: access() uses real user/group id, so will only work when that's correct. if (S_ISDIR (st.st_mode)) { if (!access (path, X_OK)) { *bp++ = 'e'; if (!check_access (path, acCreateFile) && !access (path, W_OK)) *bp++ = 'c'; if (!check_access (path, acListing) && !access (path, R_OK)) *bp++ = 'l'; if (!check_access (path, acCreateDir) && !access (path, W_OK)) *bp++ = 'm'; } if (!check_access (path, acDelete)) { if (st.st_nlink <= 2 && !access (ppath, W_OK | X_OK)) *bp++ = 'd'; if (!access (path, W_OK | X_OK)) *bp++ = 'p'; } } else { if (!check_access (path, acAppend) && !access (path, W_OK)) *bp++ = 'a'; if (!check_access (path, acDelete) && !access (ppath, W_OK | X_OK)) *bp++ = 'd'; if (!check_access (path, acReadFile) && !access (path, R_OK)) *bp++ = 'r'; if (!check_access (path, acOverwrite) && !access (path, W_OK)) *bp++ = 'w'; } if (!check_access (path, acRename) && !access (ppath, W_OK | X_OK)) *bp++ = 'f'; } *bp++ = ';'; } #if 0 if (tags & tfLang) { } #endif if ((tags & tfSize) && !S_ISDIR (st.st_mode)) { sprintf (bp, "size=%lld;", (long long)st.st_size); bp += strlen (bp); } #if 0 if (tags & tfMediaType) { } #endif #if 0 // TODO if (tags & tfCharset) { } #endif *bp++ = ' '; strcpy (bp, name); return buf; } const char *tagged_file_list (const char *path, int tags) { DIR *dir; struct dirent *dp; const char *entry; char *res = NULL, *rp = NULL; char wpath[4097], *wend; dir = open_dir_listing (path); if (!dir) return NULL; tags |= tfPartOfList; strcpy (wpath, path); wend = wpath + strlen (wpath); if (wend - wpath > 1 && *(wend - 1) == '/') *--wend = 0; if (!strcmp (wpath, currRoot)) tags |= tfRootDir; if (wend - wpath > 1) *wend++ = '/'; while ((dp = readdir (dir))) { if(wend - wpath + strlen(dp->d_name) > 4096) continue; /* Not much we can do. */ strcpy(wend, dp->d_name); entry = tagged_file_data (wpath, dp->d_name, tags); if (entry) { int roff = rp - res, elen = strlen (entry); res = trealloc (res, roff + elen + 3); if (!res) return NULL; rp = res + roff; strcpy (rp, entry); rp += elen; strcpy (rp, "\r\n"); rp += 2; } } closedir (dir); return res; } const char *set_cwd(const char *dir, const char *home) { if (home) dir = chroot_path(dir, NULL, home); if(fakeChroot) { if (strncmp (dir, currRoot, strlen (currRoot))) dir = currRoot; } if(check_access(dir, acSearch)) return NULL; if (chdir (dir)) return NULL; return dir; } int open_file_reading(const char *path, long long offset) { int fd; if(check_access(path, acReadFile)) return -1; fd = open(path, O_RDONLY | O_NONBLOCK); if(fd < 0) return -1; if(offset && lseek(fd, offset, SEEK_SET) == -1) { close(fd); return -1; } return fd; } int open_file_writing(const char *path, long long offset, int flags) { struct stat st; int fd; if(lstat(path, &st) && errno == ENOENT) { if(check_access(path, acCreateFile)) return -1; } else if(offset) { if(!S_ISREG(st.st_mode)) { errno = EINVAL; return -1; } if(offset >= st.st_size) { if(check_access(path, acAppend)) return -1; } else if(check_access(path, acOverwrite)) return -1; } else { if(check_access(path, acOverwrite)) return -1; flags |= O_TRUNC; } flags |= O_WRONLY | O_CREAT; fd = open(path, flags, 0666); if(fd < 0) return -1; if(offset) { if(lseek(fd, offset, SEEK_SET) == -1 || ftruncate(fd, offset)) { int serrno = errno; close(fd); errno = serrno; return -1; } } return fd; } int open_file_appending(const char *path) { struct stat st; if(lstat(path, &st) && errno == ENOENT) { if(check_access(path, acCreateFile)) return -1; } else if(check_access(path, acAppend)) return -1; return open(path, O_WRONLY | O_CREAT | O_APPEND, 0666); } int open_temp_file(char *path) { if(check_access(path, acCreateFile)) return -1; return mkstemp(path); } DIR *open_dir_listing(const char *path) { if(check_access(path, acListing)) return NULL; return opendir(path); } int rename_file(const char *from, const char *to) { if(check_access(from, acRename) || check_access(to, acRename)) return -1; return rename(from, to); } int delete_file(const char *path) { struct stat st; if(check_access(path, acDelete)) return -1; if(lstat(path, &st)) return -1; if(S_ISDIR(st.st_mode)) { errno = EISDIR; return -1; } return unlink(path); } int delete_dir(const char *path) { if(check_access(path, acDelete)) return -1; return rmdir(path); } int create_dir(const char *path) { if(check_access(path, acCreateDir)) return -1; return mkdir(path, 0777); } void set_access (access_t *chain, int defHardLink, int rFd, int wFd) { access_chain = chain; external_rfd = rFd; external_wfd = wFd; defaultHardLink = defHardLink; } #define CHECKADDFLAG(f,s) \ if (tests & f) \ { \ *pp++ = ' '; \ strcpy (pp, s); \ pp += strlen (s); \ } int check_access(const char *path, int tests) { access_t *acc; int apl; char rpath[4096]; const char *fn; /* No search access, no access. */ tests |= acSearch; if (external_wfd >= 0 && external_rfd >= 0) { char *pp = rpath; strcpy (pp, strip_path (path)); pp += strlen (pp); *pp++ = '\n'; CHECKADDFLAG(acSearch, "search"); CHECKADDFLAG(acReadFile, "readfile"); CHECKADDFLAG(acListing, "listing"); CHECKADDFLAG(acCreateFile, "createfile"); CHECKADDFLAG(acCreateDir, "createdir"); CHECKADDFLAG(acAppend, "append"); CHECKADDFLAG(acOverwrite, "overwrite"); CHECKADDFLAG(acDelete, "delete"); CHECKADDFLAG(acRename, "rename"); CHECKADDFLAG(acEncrypted, "encrypted"); CHECKADDFLAG(acSigned, "signed"); *pp++ = '\n'; write (external_wfd, rpath, pp - rpath); do apl = read (external_rfd, rpath, sizeof (rpath) - 1); while (apl == -1 && errno == EINTR); if (apl >= 0) { rpath[apl] = 0; pp = strchr (rpath, '\n'); if (pp) *pp = 0; if (!strcasecmp (rpath, "REQUIRE")) return 1; if (!strcasecmp (rpath, "ALLOW")) return 0; } errno = EPERM; return -1; } /* If not faked, prepend the root. */ if(!fakeChroot && strcmp(currRoot, "/")) sprintf(rpath, "%s%s", currRoot, path); else strcpy(rpath, path); fn = strrchr (rpath, '/'); if (fn) fn++; else fn = rpath; for(acc = access_chain; acc && tests; acc = acc->next) { apl = strlen(acc->path); if(apl <= (int)strlen(rpath) && !strncmp(rpath, acc->path, apl)) { if(acc->path[apl - 1] != '/' && rpath[apl] && rpath[apl] != '/') continue; if (acc->numMasks) { for (apl = 0; apl < acc->numMasks; apl++) { if (match_pattern (acc->masks[apl], fn)) break; } if (apl == acc->numMasks) continue; } /* * Check the required perms. * One required is enough for us to return 1. */ if (acc->require & tests) return 1; /* One denied permission is enough. */ if(acc->deny & tests) { errno = EPERM; return -1; } /* Remove all tests specifically allowed */ tests &= ~acc->allow; } } /* If we got here, we grant access. */ return 0; } int check_hidden(const char *path) { access_t *acc; char rpath[4096]; const char *fn; int apl; /* If not faked, prepend the root. */ if(!fakeChroot && strcmp(currRoot, "/")) sprintf(rpath, "%s%s", currRoot, path); else strcpy(rpath, path); fn = strrchr (rpath, '/'); if (fn) fn++; else fn = rpath; for(acc = access_chain; acc; acc = acc->next) { apl = strlen (acc->path); if (apl <= strlen (rpath) && !strncmp(rpath, acc->path, apl)) { if(acc->path[apl - 1] != '/' && rpath[apl] && rpath[apl] != '/') continue; if (acc->numMasks) { for (apl = 0; apl < acc->numMasks; apl++) { if (match_pattern (acc->masks[apl], fn)) break; } if (apl == acc->numMasks) continue; } else if (rpath[apl]) continue; return acc->hidden; } } return 0; } int check_hardlink (const char *path) { access_t *acc; char rpath[4096]; int apl; const char *fn; /* If not faked, prepend the root. */ if(!fakeChroot && strcmp(currRoot, "/")) sprintf(rpath, "%s%s", currRoot, path); else strcpy(rpath, path); fn = strrchr (rpath, '/'); if (fn) fn++; else fn = rpath; for(acc = access_chain; acc; acc = acc->next) { apl = strlen (acc->path); if (apl <= strlen (rpath) && !strncmp(rpath, acc->path, apl)) { if(acc->path[apl - 1] != '/' && rpath[apl] && rpath[apl] != '/') continue; if (acc->numMasks) { for (apl = 0; apl < acc->numMasks; apl++) { if (match_pattern (acc->masks[apl], fn)) break; } if (apl == acc->numMasks) continue; } else if (rpath[apl]) continue; if (acc->hardlink == -1) return 0; if (acc->hardlink) return 1; } } return defaultHardLink; } const char *dir_msg_file(const char *path) { access_t *acc; int apl; char rpath[4096]; const char *fn; /* If not faked, prepend the root. */ if(!fakeChroot && strcmp(currRoot, "/")) sprintf(rpath, "%s%s", currRoot, path); else strcpy(rpath, path); fn = strrchr (rpath, '/'); if (fn) fn++; else fn = rpath; for(acc = access_chain; acc; acc = acc->next) { apl = strlen(acc->path); if(apl <= (int)strlen(rpath) && !strncmp(rpath, acc->path, apl)) { if(acc->path[apl - 1] != '/' && rpath[apl] && rpath[apl] != '/') continue; if (acc->numMasks) { for (apl = 0; apl < acc->numMasks; apl++) { if (match_pattern (acc->masks[apl], fn)) break; } if (apl == acc->numMasks) continue; } if (acc->dirMsgFile) return acc->dirMsgFile; } } return NULL; } int open_shared (const char *path) { // TODO Make it shared... return open (path, O_RDONLY); } void close_shared (int fd) { close (fd); } const char *scan_strings (int fd, const char *search) { char buf[1024]; char *rp, *nbp; int bl = 0, l; static char line[1024]; // TODO hash table? lseek (fd, 3, SEEK_SET); // First 3 bytes is UTF-8 bom. while ((l = read (fd, buf + bl, 1024 - bl)) > 0 || bl > 0) { if (l > 0) bl += l; nbp = memchr (buf, '\n', bl); if (!nbp) return search; memcpy (line, buf, nbp - buf); line[nbp - buf] = 0; rp = strstr (line, " => "); if (rp) { *rp = 0; if (!strcmp (line, search)) return rp + 4; } while (nbp - buf < 1024 && *++nbp == '\n') ; bl -= nbp - buf; memmove (buf, nbp, bl); } return search; } int match_pattern(const char *pat, const char *str) { const char *pp, *sp; char ch; int res, i; pp = pat; sp = str; res = 1; while(*pp && res) { switch(ch = *pp++) { case '?': if(!*sp++) res = 0; break; case '*': for (i = strlen(sp); i >= 0; i--) { if(match_pattern(pp, sp + i)) return 1; } break; default: if(ch != *sp++) res = 0; break; } } if(res && !*sp) return 1; return 0; } void *read_file (const char *file, size_t *sz) { int fd; void *buf; struct stat st; if (stat (file, &st)) return NULL; buf = talloc (*sz = st.st_size); if (!buf) return NULL; fd = open (file, O_RDONLY); if (fd < 0) return NULL; if (read (fd, buf, st.st_size) != st.st_size) return NULL; close (fd); return buf; }