/*****************************************************************************\
* 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