/*****************************************************************************\
* 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;
}
syntax highlighted by Code2HTML, v. 0.9.1