/*********************************************************************\
    *  Copyright (c) 2003 by Radim Kolar (hsn@cybermail.net)              *
    *  Copyright (c) 1991 by Wen-King Su (wen-king@vlsi.cs.caltech.edu)   *
    *                                                                     *
    *  You may copy or modify this file in any manner you wish, provided  *
    *  that this notice is always included, and that you hold the author  *
    *  harmless for any loss or damage resulting from the installation or *
    *  use of this software.                                              *
    \*********************************************************************/

#include "tweak.h"
#include "server_def.h"
#include "s_extern.h"
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_UTIME_H
#include <utime.h>
#endif
#include "my-string.h"
#include "fifocache.h"

/****************************************************************************
* This is a modified server_file that uses cache for directory listings and
* directory status information.                                             *
*****************************************************************************/

static struct FifoCache *dirlistcache;
static struct FifoCache *dirstatcache;
/* open file handles cache */
static struct FifoCache *fpcache;

#define FOURGIGS 0xffffffffUL
#define TWOGIGS  0x7fffffffUL

static FPCACHE *search_fpcache (unsigned long inet_num,
	                        unsigned short port_num,
				const char * fname)
{
  unsigned int i;
  FPCACHE *entry;
  const char **key;

  for (i=0;i<fp_cache_limit;i++)
  {
      /* search for file in cache */
    entry=(FPCACHE *)(fpcache->e_head+i*sizeof(FPCACHE));
    if (port_num==entry->port_num && inet_num==entry->inet_num)
    {
	key=f_cache_get_key(fpcache,entry);
	if(!strcmp(fname,*key))
	{
		/* file found in cache, return cache-pointer */
	        fpcache->hit++;
                return entry;
	}
    }
  }
  fpcache->miss++;
  return((FPCACHE *)0);
}

static void fpcache_free_entry (void *entry)
{
    FPCACHE *f=entry;

    if(f->fp)
	fclose(f->fp);
}

static unsigned int fpcache_entry_profiler (void *entry)
{
    FPCACHE *f=entry;

    if(f->fp)
	return sizeof(FILE);
    else
	return 0;
}


static void dirlistcache_free_entry (void * entry)
{
    DIRLISTING *d=entry;

    if(d->listing)
	free(d->listing);
}

static unsigned int dirlistcache_entry_profiler (void *entry)
{
    DIRLISTING *d=entry;

    return d->listing_size;
}

static void dirstatcache_free_entry (void * entry)
{
    DIRINFO *d=entry;

    if(d->realname)
	free(d->realname);
    if(d->owner_password)
	free(d->owner_password);
    if(d->public_password)
	free(d->public_password);
    if(d->owner)
	free_ip_table(d->owner);
    if(d->readme)
	free(d->readme);
}

static unsigned int dirstatcache_entry_profiler  (void *entry)
{
   /* TODO profiling for owner ip_table */
   DIRINFO *d=entry;
   unsigned int res=0;
   if(d->realname)
   {
       res=strlen(d->realname);
       res++;
   }
    if(d->owner_password)
    {
	res+=strlen(d->owner_password);
	res++;
    }
    if(d->public_password)
    {
	res+=strlen(d->public_password);
	res++;
    }
    if(d->readme)
    {
	res+=strlen(d->readme);
	res++;
    }

   return res;
}

static void string_free (void * entry)
{
    char **s=entry;
    if(*s!=NULL)
	free(*s);
}

static unsigned int string_profiler (void *entry)
{
    char **s=entry;

    if(*s!=NULL)
	return(strlen(*s));
    else
	return 0;
}

static int string_compare (const void *e1,const void *e2)
{

    char *const *s1=e1;
    char *const *s2=e2;

    /* strcmp do not likes NULLs */
    if(*s1 && *s2)
    {
       return strcmp(*s1,*s2);
    }else
	return 1;
}

/* should init all types of caches in future */
int init_caches (void)
{
   dirlistcache=f_cache_new(dir_cache_limit,sizeof(DIRLISTING),dirlistcache_free_entry,sizeof(char *),string_free,string_compare);
   dirstatcache=f_cache_new(stat_cache_limit,sizeof(DIRINFO),dirstatcache_free_entry,sizeof(char *),string_free,string_compare);
   fpcache=f_cache_new(fp_cache_limit,sizeof(FPCACHE),fpcache_free_entry,sizeof(char *),string_free,string_compare);

   if(dirlistcache && dirstatcache && fpcache)
   {
       f_cache_set_memory_profilers(dirlistcache,string_profiler,dirlistcache_entry_profiler);
       f_cache_set_memory_profilers(dirstatcache,string_profiler,dirstatcache_entry_profiler);
       f_cache_set_memory_profilers(fpcache,string_profiler,fpcache_entry_profiler);
       return 0;
   }
   else
   {
       f_cache_destroy(dirlistcache);
       f_cache_destroy(dirstatcache);
       f_cache_destroy(fpcache);
       return -1;
   }
}

void stat_caches (FILE *fp)
{
          if(fp==NULL) fp=stderr;
 	  fprintf(fp,"DIRLISTCACHE ");
 	  f_cache_stats(dirlistcache,fp);
 	  fprintf(fp,"DIRSTATCACHE ");
 	  f_cache_stats(dirstatcache,fp);
 	  fprintf(fp,"FPCACHE ");
	  f_cache_stats(fpcache,fp);
}

/* should init all types of caches in future */
void shutdown_caches (void)
{
#ifdef LAMERPACK
    fclose(stderr);
#endif        
    fprintf(stderr,"DIRLISTCACHE ");
    f_cache_stats(dirlistcache,stderr);
    f_cache_clear(dirlistcache);
    f_cache_destroy(dirlistcache);
    fprintf(stderr,"DIRSTATCACHE ");
    f_cache_stats(dirstatcache,stderr);
    f_cache_clear(dirstatcache);
    f_cache_destroy(dirstatcache);
    fprintf(stderr,"FPCACHE ");
    f_cache_stats(fpcache,stderr);
    f_cache_clear(fpcache);
    f_cache_destroy(fpcache);
}



/*****************************************************************************
 * Validate path - check that path does not fall outside the bounds of the
 * FSP root directory, weed out references to / and ../.. type links.
 * Input:     fullp - pointer to full filename
 *         lenfullp - length of full filename (including \0)
 *         *di      - where to return DIRINFO information about directory
 *         want_directory - want to operate on directory, not a file
 *****************************************************************************/
const char *validate_path (char * fullp, unsigned lenfullp, PPATH * pp,DIRINFO **di, int want_directory)
{
    char work [NBSIZE];
    const char *err;
    char *s;
    struct stat sd;
    DIRINFO newdir;

    if(dbug)
    {
 	  fprintf(stderr,"Validating path = %s len=%u\n",lenfullp>0? fullp:NULL,lenfullp);
    }
    /* split fullpath into parts */
    if( (err=parse_path(fullp,lenfullp,pp)) ) return err;
    if(want_directory)
    {
	/* everything in path is a directory */
	pp->d_ptr=pp->fullp;
	pp->d_len=pp->d_len+pp->f_len+1;
    }
    /* extract dir from path to work */
    memcpy(work,pp->d_ptr,pp->d_len);
    work[pp->d_len]='\0';
    if(dbug)
    {
        fprintf(stderr,"looking for directory %s in statcache.\n",work);
    }
    err=work;
    *di=f_cache_find(dirstatcache,&err);
    if(*di)
    {
	if(dbug) fprintf(stderr,"hit!\n");
	/* need to recheck a cached directory? */
	if((*di)->lastcheck+stat_cache_timeout>cur_time)
	{
	    if(dbug) fprintf(stderr,"...and fresh\n");
	    return NULL; /* no, we have fresh cache entry */
	}
	else
	    if(dbug)
		fprintf(stderr,"...but stale\n");
    }
    /* try to stat directory */
    if(FSP_STAT(work,&sd))
	return("No such directory");
    if(!S_ISDIR(sd.st_mode))
	return("Not a directory");
    /* recheck directory */
    if(*di)
    {
	if(use_directory_mtime && sd.st_mtime==(*di)->mtime)
	{
	    /* rechecked ok */
	    (*di)->lastcheck=cur_time;
	    return NULL;
	} else
	{
	    /* throw out old directory */
	    f_cache_delete_entry(dirstatcache,*di);
	    *di=NULL;
	}
    }

   /* build a new directory info */
  if (chdir(work))
      return("Can't change directory");
  /* save fixed input directory to err */
  s=strdup(work);
  if(!getcwd(work,NBSIZE))
  {
      chdir(home_dir);
      free(s);
      return("Can't get current directory");
  }
  /* check namespace */
  if(homedir_restricted)
      if (strncmp(work,home_dir,strlen(home_dir))) {
	  chdir(home_dir);
	  free(s);
	  return("Not following link out of restricted area");
      }
  /*  init. dir structure */
  newdir.realname=strdup(work);
  if(!newdir.realname)
  {
      free(s);
      chdir(home_dir);
      return("Memory exhausted");
  }
  if(use_directory_mtime)
      newdir.mtime=sd.st_mtime;
  else
      newdir.mtime=cur_time;
  newdir.lastcheck=cur_time;
  /* load access perms */
  load_access_rights(&newdir);
  chdir(home_dir);
  /* put new record into cache */
  if(dbug) fprintf(stderr,"putting into statcache: %s\n",s);
  *di=f_cache_put(dirstatcache,&s,&newdir);
  return NULLP;
}

/* copy file : from -> to */
static const char *copy_file (const char * n1, const char * n2)
{
    FILE *ft,*fp;
    size_t bytes;
    char buf[1024];

  if(!(ft = fopen(n1,"rb"))) {
    return("Can't open temporary file");
  }

  if(!(fp = fopen(n2,"wb"))) {
    fclose(ft);
    return("Can't open file for output");

  }
  /* copy temporary file to actual fput file */
  while( (bytes = fread(buf,1,sizeof(buf),ft)))
  {    
      if ( bytes != fwrite(buf,1,bytes,fp)) {
	  break;
      }	  
  }

  if ( ferror(ft) || ferror(fp) )
  {
      fclose(ft);
      fclose(fp);
      unlink(n2);
      return ("Write error");
  }

  fclose(ft);
  fclose(fp);
  return NULL;
}

/* appends new packet to directory listing, return 0 on success */
static int append_dir_listing (DIRLISTING * dl,const char * buf,unsigned int size)
{
      BYTE *newbuf;

      /* append this buffer */
      newbuf=realloc(dl->listing,dl->listing_size+size);
      if(newbuf==NULL)
      {
	  if(dl->listing) free(dl->listing);
	  dl->listing=NULL;
	  return -1;
      }
      memcpy(newbuf+dl->listing_size,buf,size);
      dl->listing_size+=size;
      dl->listing=newbuf;

      return 0;
}

/* builds directory listing into DIRLISTING structure, in case of any
 * error. nulls dl->listing
 */
static void build_dir_listing (DIRLISTING * dl,const char *directory)
{
  int skip;            /* how many bytes skip to next 4byte boundary */
  unsigned int rem;    /* remaining free space in UDP data space */
  DIR *dir_f;          /* opened directory for listing */
  struct dirent *dp;   /* record in directory */
  struct stat    sb;   /* stat data of record */
  register char  *s;   /* pointer to filename */
  size_t nlen;         /* filename length including zero terminator */
  char buffer[UBUF_SPACE]; /* buffer for building dirblock packet */
  char name[NBSIZE];   /* buffer for stat name */
  int namelen;         /* directory name length */
  unsigned int bufpos; /* current write pos. in buffer */
  unsigned int dirblocksize;

  /* init pointers */
  dl->listing=NULL;
  dl->listing_size=0;
  bufpos=0;

  if(!(dir_f = opendir(directory)))  {
    fprintf(stderr,"Can't open dir during listing initialization\n");
    return;
  }
  /* do not build longer directory blocks than 1024 bytes */
  if(packetsize > UBUF_SPACE)
      dirblocksize=UBUF_SPACE;
  else
      dirblocksize = packetsize;    
  memset(buffer,0,dirblocksize); /* clear memory on the stack */
  strcpy(name,directory);
  namelen=strlen(directory);
  name[namelen++]='/'; /* add directory separator to name */

  for(rem = dirblocksize; (dp = readdir(dir_f)); ) {
    if (dp->d_ino == 0) continue;
    s = dp->d_name;
	
    /* hide dot files, but allow . or .. */
    if((s[0]=='.') && ((s[1]!=0) && (s[1] != '.' || s[2] != 0))) continue;

    strcpy(name+namelen,s);
    if(FSP_STAT(name,&sb)) continue;
    if(!S_ISDIR(sb.st_mode) && !S_ISREG(sb.st_mode)) continue;
    if(sb.st_size>FOURGIGS) sb.st_size=FOURGIGS;
	
    nlen = strlen(s)+1;

    /* do we have space in buffer for entire entry?  */
    if(rem < RDHSIZE + nlen) {
      /* fill rest of buffer with '*' */
      memset(buffer+bufpos,RDTYPE_SKIP,rem);
      /* append this buffer */
      if(append_dir_listing(dl,buffer,dirblocksize))
      {
	  closedir(dir_f);
	  return;
      }
      rem = dirblocksize;
      bufpos = 0;
    }
	
    BB_WRITE4(buffer+bufpos,sb.st_mtime);
    bufpos+=4;
    BB_WRITE4(buffer+bufpos,sb.st_size );
    bufpos+=4;
    buffer[bufpos++]=S_ISDIR(sb.st_mode) ? RDTYPE_DIR : RDTYPE_FILE;
    memcpy(buffer+bufpos,s,nlen);
    bufpos+=nlen;
    rem -= (nlen + RDHSIZE);

    /* pad to 4byte boundary */
    if((skip = (nlen + RDHSIZE) & 0x3)) {
      skip=4-skip;
      bufpos+=skip;
      rem   -=skip;
    }
  }
  closedir(dir_f);

  /* do we have space for final END entry? */
  if(rem <RDHSIZE )
  {
      /* no, make a new packet */
      memset(buffer+bufpos,RDTYPE_SKIP,rem);
      /* append this buffer */
      if(append_dir_listing(dl,buffer,dirblocksize))
	  return;

      bufpos = 0;
  }

  /* add final entry */
  bufpos+=8;
  buffer[bufpos++]=RDTYPE_END;
  /* append last buffer */
  append_dir_listing(dl,buffer,bufpos);
  dl->mtime=cur_time;
}

const char *server_get_dir (DIRLISTING ** dl, const DIRINFO * di)
{
  struct stat sf;
  char   list_p[NBSIZE];

  /* get directory from memory cache */
  if(dbug) fprintf(stderr,"finding %s in dirlistcache\n",di->realname);
  *dl=f_cache_find(dirlistcache,&(di->realname));
  if(*dl && (*dl)->mtime < di->mtime)
  {
      /* expired */
      f_cache_delete_entry(dirlistcache,*dl);
      *dl=NULL;
      if(dbug) fprintf(stderr,"... directory was updated, list needs rebuild.\n");
  }
  if(*dl==NULL)
  {
      /* need to build a directory listing */
      DIRLISTING dlnew;
      char *key;
      unsigned int ok;

      if(dbug) fprintf(stderr," miss.\n");
      ok=0;
      if(use_prebuild_dirlists)
      {
          sprintf(list_p,"%s/"FSP_DIRLISTING,di->realname);
	  /* we have up-to-date directory listing somewhere? */
          if(!FSP_STAT(list_p,&sf))
	    if(sf.st_mtime>=di->mtime) {
	      /* try to load it */
	      FILE *f;
	
	      dlnew.listing_size=sf.st_size;
	      dlnew.listing=malloc(dlnew.listing_size);
	      if(dlnew.listing)
	      {
		  if( (f=fopen(list_p,"rb")) )
		  {
	              if(dlnew.listing_size==fread(dlnew.listing,1,dlnew.listing_size,f))
		         ok=1;
		      fclose(f);
		  }
		  if(!ok)
		  {
		      free(dlnew.listing);
		      dlnew.listing=NULL;
		  }
	      }
	      if(ok)
	      {
		 if(dbug) fprintf(stderr,"cached content sucessfully used.\n");
	         dlnew.mtime=sf.st_mtime;
	      }
	  }
      }
      /* build list if we do not have it */
      if(!ok)
	  build_dir_listing (&dlnew,di->realname);
      if(!dlnew.listing)
	  return("Server can't list directory");
      /* put new list into cache */
      key=strdup(di->realname);
      *dl=f_cache_put(dirlistcache,&key,&dlnew);
      if(use_prebuild_dirlists && !ok)
      {
	  /*  try to write a cache directory content */
	  FILE *f;
	  ok=0;
	  f=fopen(list_p,"wb");
	  if(f)
	  {
	      ok=fwrite(dlnew.listing,1,dlnew.listing_size,f);
	      if(ok==dlnew.listing_size)
		 ok=1;
	      fclose(f);
	  }
	  if(!ok)
	      unlink(list_p);
	  else
	     (*dl)->mtime=cur_time;
      }
  }
  else
  {
    if(dbug) fprintf(stderr," hit!\n");
  }
  return(NULLP);
}

/**********************************************************************/
/* assume path and ACL is validated */
const char *server_del_file (PPATH * pp, DIRINFO * di)
{
  struct stat sb;

  if(FSP_STAT(pp->fullp,&sb)) return("unlink: file not accessible");
  if(!(S_ISREG(sb.st_mode))) return("unlink: not an ordinary file");

  if(unlink(pp->fullp) == -1) return("unlink: cannot unlink");
  di->mtime=cur_time;
  di->lastcheck=cur_time;

  return(NULLP);
}

/**********************************************************************/

const char *server_del_dir (PPATH * pp, DIRINFO *di)
{
  struct stat sb;
  DIRINFO null;

  if(FSP_STAT(pp->fullp,&sb)) return("rmdir: directory not accessible");
  if(!(S_ISDIR(sb.st_mode))) return("rmdir: not an ordinary directory");

  memset(&null,0,sizeof(DIRINFO));

  chdir(pp->fullp);
  save_access_rights(&null);
  chdir(home_dir);
  if(rmdir(pp->fullp) != 0) {
    chdir(pp->fullp);
    save_access_rights(di);
    chdir(home_dir);
    return("rmdir: cannot unlink");
  }
  else
  {
      di->lastcheck=0;
  }

  return(NULLP);
}

/**********************************************************************/

const char *server_make_dir (PPATH * pp, unsigned long inet_num,DIRINFO **di)
{
  DIRINFO newdir;
  char temp_p[NBSIZE];
  char *name;

  /* make directory and place ownerfile in it */
  if(mkdir(pp->fullp,0777) != 0) return("Can't create directory");
  memset(&newdir,0,sizeof(DIRINFO));
  newdir.protection=(*di)->protection;
  if((*di)->owner_password)
      newdir.owner_password=strdup((*di)->owner_password);
  if((*di)->public_password)
      newdir.public_password=strdup((*di)->public_password);
  newdir.lastcheck=cur_time;
  newdir.mtime=cur_time;
  /* make owner record */
  sprintf(temp_p,"%d.%d.%d.%d O Creator\n",
	      ((unsigned char *)(&inet_num))[0],
	      ((unsigned char *)(&inet_num))[1],
	      ((unsigned char *)(&inet_num))[2],
	      ((unsigned char *)(&inet_num))[3]);
  add_ipline(temp_p,&newdir.owner);
  chdir(pp->fullp);
  save_access_rights(&newdir);
  getcwd(temp_p,NBSIZE);
  chdir(home_dir);
  newdir.realname=strdup(temp_p);
  name=strdup(pp->fullp);
  *di=f_cache_put(dirstatcache,&name,&newdir);
  return(NULLP);
}

/**********************************************************************/

const char *server_get_file (PPATH * pp,
	                     FILE ** fp,
			     unsigned long  inet_num,
			     unsigned short port_num,
			     DIRINFO * di
			     )
{
  struct stat sb;
  char   realfn[NBSIZE];
  FPCACHE *cache_f;

  sprintf(realfn,"%s/%s",di->realname,pp->f_ptr);
  cache_f=search_fpcache(inet_num,port_num,realfn);

  if(!cache_f) {
    FPCACHE newfile;
    char *key;
    /* file not found in cache? */
	
    if (FSP_STAT(realfn,&sb)) return("No such file");
    if(!(S_ISREG(sb.st_mode)))
    {
	if(S_ISDIR(sb.st_mode))
	    return ("Is a directory");
	else
	    return("Not a regular file");
    }
    /* open new file */
    if(!(*fp = fopen(pp->fullp,"rb"))) return("Can't open file");

    /* add it to the file-cache */
    newfile.inet_num=inet_num;
    newfile.port_num=port_num;
    newfile.fp=*fp;
    key=strdup(realfn);
    f_cache_put(fpcache,&key,&newfile);
  }
  /* get filepoint from cache */
  else *fp = cache_f->fp;

  return(NULLP);
}

/**********************************************************************/
/* returns number of readme bytes
*/
int server_get_pro (DIRINFO * di, char * result, const char * acc)
{
  int pos=0;  

  if(di->readme) 
  {
      strcpy(result,di->readme);
      pos=strlen(result); /* add readme */
      pos++;              /* add zero terminator char */
  }
  /* append xtra data space area */
  result[pos] = di->protection^DIR_GET;
  if(acc[0]=='O')
            result[pos] |= DIR_OWNER;

  return pos;
}

/**********************************************************************/

const char *server_set_pro (DIRINFO *di, const char * key)
{
  unsigned char act;

  switch(key[1]) {
    case 'c':
      act=DIR_ADD;
      break;
    case 'd':
      act=DIR_DEL;
      break;
    case 'g':
      act=DIR_GET;
      break;
    case 'm':
      act=DIR_MKDIR;
      break;
    case 'l':
      act=DIR_LIST;
      break;
    case 'r':
      act=DIR_RENAME;
      break;
    default:
      return("Invalid syntax. <+|-> <c|d|g|m|l|r>");
  }

  switch(key[0]) {
    case '+':
      di->protection|=act;
      break;
    case '-':
      di->protection&=~act;
      break;
    default:
      return("Invalid syntax. <+|-> <c|d|g|m|l|r>");
    }

  di->mtime=cur_time;
  di->lastcheck=cur_time;

  chdir(di->realname);
  save_access_rights (di);
  chdir(home_dir);

  return(NULLP);
}

/**********************************************************************
 *  These two are used for file uploading.
 **********************************************************************/

const char *server_up_load (char * data, unsigned int len, unsigned long pos,
			    unsigned long inet_num, unsigned short port_num)
{
  FILE *fp;
  char  tname[NBSIZE];
  FPCACHE *cache_f;
  char *tmp;
  struct stat sf;

  sprintf(tname, "%s/.T%08lX%04X", tmp_dir,inet_num, port_num);

  tmp=tname;
  cache_f=f_cache_find(fpcache,&tmp);
  if(! cache_f ) {
    /* file not found in cache */
    FPCACHE newfile;
    if (pos) {
      fp = fopen(tname, "r+b");
    } else {
      unlink(tname);
      fp = fopen(tname,"wb");
    }

    if(!fp) return("Cannot open temporary file");

    /* check for symlinks or other junk */
    if(lstat(tname,&sf) || !S_ISREG(sf.st_mode))
    {
	fclose(fp);
	remove(tname);
	return("Temporary file is NOT a regular file");
    }
    /* test if we do not create hole in file which is caused that
       client continues upload across server crash, which causes
       some data loss due to libc stdio write caching */
    /* server do not cleans temporary directory on startup - so
       uploads across restart should work */   
    if(pos > sf.st_size || pos < sf.st_size - UBUF_SPACE)
    {
	fclose(fp);
	unlink(tname);
	return("Non continuous upload detected. Restart upload please.");
    }
    /* seek to starting position */
    if(fseeko(fp, pos, SEEK_SET))
    {
	fclose(fp);
	unlink(tname);
        return("Seeking in file failed");
    }
    /* protect temporary file */
    chmod(tname,S_IRUSR|S_IWUSR);
    /* add it to the file-cache */
    newfile.inet_num=inet_num;
    newfile.port_num=port_num;
    newfile.fp=fp;
    tmp=strdup(tname);
    f_cache_put(fpcache,&tmp,&newfile);
  } else {
      /* get file pointer from cache */
      fp=cache_f->fp;
  }

  /* check for uploading on non-tail of file */
  sf.st_size= ftello(fp);
  if(pos > sf.st_size || pos < sf.st_size - UBUF_SPACE)
  {
        f_cache_delete_entry(fpcache,cache_f);
	unlink(tname);
        if( pos == 0)
	{
	    /* we can retry */
            return server_up_load (data,len,pos,inet_num,port_num);
	}
	return("Non continuous upload detected. Restart upload please.");
  }
  /*
  if(fseeko(fp, pos, SEEK_SET))
      return("Seeking in file failed");
  */
  if(len!=fwrite(data, 1, len, fp))
  {
      f_cache_delete_entry(fpcache,cache_f);
      return("Writing to file failed");
  }
  return(NULLP);
}

const char *server_install (PPATH * pp, unsigned long inet_num,
			    unsigned short port_num, const char * acc, DIRINFO *di, unsigned int l2, const char *s2)
{
  char tname[NBSIZE];
  const char *tmp;
  FPCACHE *cache_f;
#ifdef HAVE_UTIME_H
  struct utimbuf ut;
#endif

  sprintf(tname, "%s/.T%08lX%04X", tmp_dir,inet_num, port_num);
  /* if file still in cache, then close it & remove it from cache */
  tmp=tname;
  cache_f=f_cache_find(fpcache,&tmp);
  f_cache_delete_entry(fpcache,cache_f);

  if (dbug)
    fprintf(stderr,"server_install: tname: %s, pp->fullp: %s\n",tname, pp->fullp);
  /* zero length filename */
  if( strcmp(pp->fullp,".") == 0 )
   {
       if (dbug)
	   fprintf(stderr,"server_install: zero length name. aborting upload.\n");
       unlink(tname);
       return (NULLP);
   }

  if(fexist(pp->fullp) &&
	( (di->protection & DIR_DEL) || acc[0]=='O' )
    )
  {
      unlink(tname);
      if(dbug)
	  fprintf(stderr,"File %s already exists, but user is not directory owner and public can't delete files.\n",pp->fullp);
      return("no permission for replacing that file. Not an owner.");
  }

  di->lastcheck=cur_time;
  di->mtime=cur_time;

  /* delete target file, if any */
  unlink(pp->fullp);

  umask(upload_umask);
  /* so just copy the temp file */
  tmp=copy_file(tname,pp->fullp);
  unlink(tname);
  umask(system_umask);
#ifdef HAVE_UTIME_H
  if(l2>=4)
  {
      ut.modtime=BB_READ4(s2);
      ut.actime=cur_time;
      utime(pp->fullp,&ut);
  }
#endif

  return(tmp);
}

/**********************************************************************/
/* assume path is validated */
/* start GRAB OPERATION! */
const char *server_secure_file (PPATH * pp, unsigned long inet_num,
				unsigned short port_num,DIRINFO *di)
{
  struct stat sb;
  char temp_p[NBSIZE];
  const char *tmp;

  if(FSP_STAT(pp->fullp,&sb)) return("grab: file not accessible");
  if(!(S_ISREG(sb.st_mode))) return("grab: not an ordinary file");

  sprintf(temp_p,"%s/.G%08lX%04X", tmp_dir, inet_num,port_num);

  unlink(temp_p);
  /* link emulated as a filecopy */
  tmp=copy_file(pp->fullp,temp_p);
  if(tmp) return tmp;

  if(unlink(pp->fullp) == -1) {
    unlink(temp_p);
    return("grab: cannot unlink original file");
  }

  di->lastcheck=cur_time;
  di->mtime=cur_time;

  return(NULLP);
}

const char *server_grab_file (FILE ** fp,
			      unsigned long inet_num,
			      unsigned short port_num)
{
  struct stat sb;
  char temp_p[NBSIZE];
  FPCACHE *cache_f;
  char *key;

  sprintf(temp_p,"%s/.G%08lX%04X",tmp_dir,inet_num,port_num);
  key=temp_p;
  cache_f=f_cache_find(fpcache,&key);
  if(!cache_f)
  {
      FPCACHE newfile;

      if(FSP_STAT(temp_p,&sb)) return("grab: can't find file");
      if(!(S_ISREG(sb.st_mode))) return("grab: Not a file");
      if(!(*fp = fopen(temp_p,"rb"))) return("grab: can't open file");
      newfile.inet_num=inet_num;
      newfile.port_num=port_num;
      newfile.fp=*fp;
      key=strdup(temp_p);
      f_cache_put(fpcache,&key,&newfile);
  } else
      *fp=cache_f->fp;

  return(NULLP);
}

const char *server_grab_done (unsigned long inet_num,
			      unsigned short port_num)
{
  struct stat sb;
  char temp_p[NBSIZE];
  FPCACHE *cache_f;
  char *key;

  sprintf(temp_p,"%s/.G%08lX%04X",tmp_dir,inet_num,port_num);
  if(FSP_STAT(temp_p,&sb)) return("grabdone: can't find temporary file");
  key=temp_p;
  cache_f=f_cache_find(fpcache,&key);
  f_cache_delete_entry(fpcache,cache_f);
  if(unlink(temp_p) == -1) return("grabdone: can't delete temporary file");
  return(NULLP);
}

/* return STAT info about filesystem object */
const char *server_stat (UBUF * ubuf )
{
  struct stat sb;
  int rc;
  PPATH pp;
  unsigned short len;
  DIRINFO *junk;

  rc=0;
  len=BB_READ2(ubuf->bb_len);
  if(len<2) {
      strcpy(ubuf->buf,"");
      len=1;
  }
  if(parse_path(ubuf->buf,len,&pp))
      rc=1;
  sb.st_mtime=0;
  sb.st_mtime=0;
  if(!rc)
  {
      rc=FSP_STAT(pp.fullp,&sb);
      if(!rc)
      {
	  /* SECURITY CHECK: validate directory */
          if S_ISDIR(sb.st_mode)
	  {
	      if(validate_path(ubuf->buf,len,&pp,&junk,1))
		  rc=1;
	  } else
	      if(S_ISREG(sb.st_mode))
	      {
	        if(validate_path(ubuf->buf,len,&pp,&junk,0))
		    rc=1;
	      }
      }
  }
	
  BB_WRITE4(ubuf->buf,sb.st_mtime);
  BB_WRITE4(ubuf->buf+4,sb.st_size );

  if(rc)
      rc=0;
  else
      if S_ISDIR(sb.st_mode) rc=RDTYPE_DIR;
      else
	  if S_ISREG(sb.st_mode) rc=RDTYPE_FILE;
          else
	      rc=0; /* not a file or directory */

  (ubuf->buf)[8]=rc;
  return(NULLP);
}

/* rename FILE/directory object */
const char *server_rename (PPATH *src,PPATH *dest,DIRINFO *sdir, DIRINFO *tdir)
{
  struct stat sb;
  int issrcdir, istargetdir;
  unsigned n;
  const char *pe;
  
  /* explore type of source object */
  if(FSP_STAT(src->fullp,&sb)) return("can't find source file or directory");
  if(S_ISDIR(sb.st_mode))
      issrcdir=1;
  else
      if(S_ISREG(sb.st_mode))
	  issrcdir=0;
      else
	  return ("Refusing to operate on special files");
  
  /* --- explore Target --- */
  if(FSP_STAT(dest->fullp,&sb))
      istargetdir=-1; /* non - existent! */
  else    
     if(S_ISDIR(sb.st_mode))
         istargetdir=1;
     else
         if(S_ISREG(sb.st_mode))
	     istargetdir=0;
         else
	     return ("Refusing to operate on special files");

  /* --=== now check ACL and do it ===-- */
  
  /* Cross - directory rename? */
  if  (sdir == tdir)
  {
      /* no, do simple rename */
      /* not needed checked at upper level   
      pe=require_access_rights( sdir,DIR_RENAME,inet,src.passwd);
      if(pe[0]!='N' && pe[0]!='O')
	  return ("Permission denied");
      if(istargetdir==0)
         pe=require_access_rights( sdir,DIR_DEL,inet,src.passwd);
      if(pe[0]!='N' && pe[0]!='O')
	  return ("No permission for overwriting files");
      */
      /* now go to the action */	  
      if (rename(src->fullp,dest->fullp))
	  return ("Rename failed");
      /* update dir listing */	  
      sdir->lastcheck=cur_time;
      sdir->mtime=cur_time;
      return NULLP;
  }
  return ("Cross-directory renames are not implemented yet.");
  /*  return(NULLP); */
}
/*********************************************************************
 test and resolve home directory
 *********************************************************************/

void init_home_dir (void)
{
  void *newhd;

  /* test and goto home dir */
  if(chdir(home_dir) == -1) {
    perror(home_dir);
    exit(6);
  }

  /* resolve home dir */
  newhd=realloc(home_dir,UBUF_SPACE);
  if(!newhd)
  {
      perror("realloc1 homedir");
      exit(5);
  }

  if(!getcwd(newhd,UBUF_SPACE))
  {
      perror("getcwd for homedir");
      exit(6);
  }

  home_dir=realloc(newhd,strlen(newhd)+1);
  if(!home_dir)
  {
      perror("realloc2 homedir");
      exit(5);
  }

  if(tmp_dir)
  {
      mkdir(tmp_dir,0700);
      chmod(tmp_dir,0700);
      if(chdir(tmp_dir)==-1)
      {
	  free(tmp_dir);
	  tmp_dir=NULL;
	  read_only=1;
      }
      chdir(home_dir);
  }

#ifndef LAMERPACK
  if(dbug) {
#endif      
    fprintf(stderr,"home on %s\n",home_dir);
    if(tmp_dir)
       fprintf(stderr,"tmpdir on %s\n",tmp_dir);
#ifndef LAMERPACK
  }
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1