/*
 * File:	spool.c
 *
 * Author:	Ulli Horlacher (framstag@rus.uni-stuttgart.de)
 *
 * History:	 
 * 
 *   1995-09-01	 Framstag	initial version
 *   1995-10-10	 Framstag	open bug fixed
 *   1995-11-01	 Framstag	added pgp signature (dummy)
 *   1995-11-05	 Framstag	added NeXT support
 *   1995-11-15	 Framstag	fixed memory leak (oops :-) )
 *   1996-01-24	 Framstag	new (received) DATEFORMAT
 *   1996-01-25	 Framstag	added keep and deljunk config
 *   1996-02-06	 Framstag 	added ATTRIBUTE=EXE
 *   1996-03-16	 Framstag	recognize file size 0 correctly
 *   1996-04-02	 Framstag	FROM now in UTF-7
 *   1996-04-12	 Framstag	sign is now in filelist
 * 				sign, comment and fname are now dynamic
 * 				added pgp support
 *   1996-04-20	 Framstag	build sender list only for requested sender
 *				(wildcards are ok)
 *   1996-04-24	 Framstag	added scanoutspool function
 *   1996-06-24	 Framstag	fixed bug when FROM contains no real name
 *   1997-01-07	 Framstag	fixed bug when spool data file does not exist
 *   1997-02-03	 Framstag       sprintf() -> snprintf()
 *   1997-01-20	 Framstag	fixed bug with TEXT=charset attribute
 *   1997-02-23	 Framstag	modified str_* function names
 *   				extended with TYPE=MIME
 *   1997-03-19	 Framstag	fixed TYPE=TEXT parsing bug
 *   1997-09-12	 Framstag	empty date header is now allowed
 *   1997-09-14	 Framstag	moved spoolid() from sendfiled.c to spool.c
 *   1997-09-17	 Framstag	sender comparision ignores now real name
 *   1997-09-18	 Framstag	better header line parsing
 *   1997-09-24	 Framstag	more effizient spool junk removing
 *   1997-11-15	 Framstag	expire too big junk files, too
 *   1997-12-10	 Framstag	added compression method for filelist
 *   1998-05-19	 Framstag	fixed maxfiles counting bug in spoolid()
 *   1998-11-12	 Framstag	scanoutspool() now gets file size, too
 *   2005-05-31  Framstag	added largefile support
 *
 * Functions for operations on files in the sendfile spool directory.
 *
 * Copyright © 1995-2005 Ulli Horlacher
 * This file is covered by the GNU General Public License
 */


#include "config.h"	/* various #defines */

#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>

#include "io.h"         /* misc IO routines */
#include "spool.h"	/* operations on files in the sendfile spool */
#include "utf7.h"       /* UTF-7 coding */
#include "message.h"    /* information, warning and error messages */
#include "string.h"     /* extended string functions */
#include "reply.h"	/* the 3 digit reply codes with text messages */


/*
 * scanspool - scan through spool directory, build list-structures and
 *             delete old files if necessary
 *
 * INPUT:   sender  - sender name (wildcards * and ? are allowed)
 *
 * RETURN:  start of sender list
 */
struct senderlist *scanspool(char *sender) {
  extern int client;		/* flag to determine client or server */
  extern char userspool[];	/* user spool directory */
  unsigned char *ucp;		/* simple unsigned character pointer */
  char
    *cp,			/* simple character pointer */
    *arg,			/* the argument(s) of the header line */
    *compress,			/* compression method */
    line[MAXLEN],		/* config line */
    hline[MAXLEN],		/* header line */
    from[MAXLEN],		/* header from line */
    fname[MAXLEN],		/* header file line */
    comment[MAXLEN],		/* file comment (printable string) */
    tmp[MAXLEN],		/* temporary string */
    msg[MAXLEN],		/* information/error message */
    sign[MAXLEN],		/* pgp signature (Latin-1) */
    date[DLEN+1],		/* file date */
    charset[DLEN+1],		/* character set name */
    rdate[DLEN+1],		/* receive date */
    file[FLEN+1];		/* spool file name */
  int
    i,
    id,				/* spool file id number */
    hfc,			/* header file corruption flag */
    keep,			/* keep spool files at least xx days */
    deljunk,			/* delete corrupted spool files after xx days */
    flags;			/* source, text, compress, tar and exe flag */
  off_t
    osize,			/* original file size */
    csize,			/* compressed file size */
    tsize;			/* true spool data file size */
  time_t
    ctime,			/* current time */
    rtime;			/* receiving time */
  FILE
    *hf,			/* header file */
    *inf;			/* config file */
  struct stat finfo;		/* information about a file */
#ifdef NEXT
  FILE *pp;			/* pipe */
#else
  struct dirent *dire;		/* directory entry */
  DIR *dp;			/* directory pointer */
#endif
  struct filelist *flp,		/* file list pointer */
                  *fln;		/* new file list element */
  struct senderlist *sls,	/* sender list start */
                    *sln,	/* sender list next pointer */
                    *slp;	/* sender list pointer */
  static struct senderlist *sll=NULL;	/* last sender list start */


  keep=0;
  deljunk=10;
  ctime=time(NULL);
  flp=NULL;

  /* is there already an old senderlist? */
  if (sll)

    /* delete old senderlist and free memory */
    for (slp=sll; slp!=NULL;) {
      for (flp=slp->flist; flp!=NULL; ) {
        fln=flp->next;
	free(flp->sign);
	free(flp->fname);
	free(flp->comment);
	free(flp);
	flp=fln;
      }
      sln=slp->next;
      free(slp);
      slp=sln;
    }

  sls=sll=NULL;

  /* parse the config-file */
  if ((inf=rfopen(CONFIG,"r"))) {
    while ((fgetl(line,inf))) {
     
      /* prepare line to be parsed */
      if ((cp=strchr(line,'#'))) *cp=0;
      if ((cp=strchr(line,'\n'))) *cp=0;
      if ((cp=strchr(line,'='))) *cp=' ';
      str_trim(line);
      str_tolower(line);

      /* is there an option and an argument? */
      if ((cp=strchr(line,' '))) {
        *cp=0; cp++;
	if (str_eq(line,"keep")) {
	  keep=atoi(cp);
	  continue;
	}
	if (str_eq(line,"deljunk")) {
	  deljunk=atoi(cp);
	  continue;
	}
      }

    }
    fclose(inf);
  }

  /* mega stupid NeXT has broken readdir() */
#ifdef NEXT
  /* open spool dir */
  snprintf(MAXS(tmp),"ls %s 2>/dev/null",userspool);
  if ((pp=popen(tmp,"r")) == NULL) return(NULL);

  /* scan through spool directory */
  while (fgetl(tmp,pp)) {
   
    if ((cp=strrchr(tmp,'\n'))) *cp=0;

    /* look for header files */
    if ((cp=strchr(tmp,'.')) && str_eq(cp,".h")) {
     
      /* extract spool id */
      *cp=0;
      id=atoi(tmp);
#ifdef JEDPARSE
    }}
#endif

#else

  /* open spool dir */
  if ((dp=opendir(userspool)) == NULL) return(NULL);

  /* scan through spool directory */
  while ((dire=readdir(dp))) {

    /* look for header files */
    if ((cp=strchr(dire->d_name,'.')) && str_eq(cp,".h")) {
     
      /* extract spool id */
      *cp=0;
      id=atoi(dire->d_name);
#endif

      /* open header file */
      snprintf(MAXS(file),"%s/%d.h",userspool,id);
      hf=rfopen(file,"r");
      
      /* error? */
      if (!hf) {
       
	/* called from receive client? */
	if (client) {
	  snprintf(MAXS(msg),"cannot open spool file %s",file);
	  message("",'E',msg);
	}

	/* skip this spool file */
        continue;
      }

      /* initialisize header entries */
      flags=0;
      csize=0;
      tsize=0;
      osize=0;
      *from=0;
      *date=0;
      *sign=0;
      *fname=0;
      *charset=0;
      *comment=0;
      compress="";

      /* does the spool data file exist? */
      snprintf(MAXS(file),"%s/%d.d",userspool,id);
      if (stat(file,&finfo)<0) {
	snprintf(MAXS(file),"%s/%d.h",userspool,id);
	unlink(file);
	continue;
      }

      /* get time of receiving (local) */
      rtime=finfo.st_mtime;

      /* spool file expired? */
      if (keep>0 && (ctime-rtime)/DAYSEC>=keep) {
        fclose(hf);
	unlink(file);
	snprintf(MAXS(file),"%s/%d.h",userspool,id);
	unlink(file);
	continue;
      }

      strftime(rdate,21,DATEFORMAT,localtime(&rtime));

      /* read header file */
      hfc=0;
      while ((fgetl(hline,hf)) && hfc==0) {
       
	/* prepare the header line */
        if ((cp=strchr(hline,'\n'))) *cp=0;
        cp=strchr(hline,'\t');

	/* corrupt header file line? */
	if (cp==NULL) {
	  hfc=1;
	  continue;
	}

	str_trim(hline);
        cp=strchr(hline,' ');
	if (cp==NULL) continue;
	
        arg=cp+1;
        *cp=0;

        /* extract the header-name and the argument */

        if (str_eq(hline,"FROM")) {
	  if ((cp=strchr(arg,' '))) {
	    *cp=0;
	    snprintf(MAXS(tmp),"%s (%s)",arg,cp+1);
	  } else
	    strcpy(tmp,arg);
	  utf2iso(0,from,NULL,NULL,tmp);
          continue;
        }

        if (str_eq(hline,"FILE")) {
	  strcpy(fname,arg);
          continue;
        }

        if (str_eq(hline,"DATE")) {
	  strncpy(date,arg,DLEN);
          continue;
        }

        if (str_eq(hline,"SIZE")) {
	  sscanf(arg,"%lld %lld",&csize,&osize);
	  tsize=finfo.st_size;
          continue;
        }

        if (str_eq(hline,"ATTR")) {
	  str_toupper(arg);
	  if (str_eq(arg,"TAR")) flags=flags|F_TAR;
	  if (str_eq(arg,"EXE")) flags=flags|F_EXE;
          continue;
	}

        if (str_eq(hline,"TYPE")) {
	  str_toupper(arg);
	  if (strstr(arg,"SOURCE"))	flags=flags|F_SOURCE;
          if (strstr(arg,"TEXT"))	flags=flags|F_TEXT;
          if (strstr(arg,"MIME"))	flags=flags|F_MIME;
          if (strstr(arg,"CRYPTED"))    flags=flags|F_CRYPT;
          if (strstr(arg,"COMPRESSED")) {
	    flags=flags|F_COMPRESS;
	    if (strstr(arg,"COMPRESSED=BZIP2"))
	      compress=S_BZIP2;
	    else
	      compress=S_GZIP;
	  }
	  if (strstr(arg,"TEXT=")) 	{
	    strncpy(charset,strchr(arg,'=')+1,DLEN);
	    charset[DLEN]=0;
	    if ((cp=strchr(charset,' '))) *cp=0;
	  }
          continue;
        }

	/* comment and signature are only needed for receive client */
	if (client) {
	 
	  if (str_eq(hline,"COMMENT")) {
	   
	    /* save comment in printable code */
	    utf2iso(0,comment,NULL,NULL,arg);
	    for (ucp=(unsigned char *)comment,i=0; *ucp; ucp++)
	      if (*ucp==9 || *ucp==10 || (*ucp>31 && *ucp<127) || *ucp>159)
	        comment[i++]=*ucp;
	    comment[i]=0;
	    continue;
	  }

	  if (str_eq(hline,"SIGN")) {
	    utf2iso(0,sign,NULL,NULL,arg);
	    continue;
	  }

	}

      }

      fclose(hf);

      /* junk file expired? */
      if (*from==0 || *fname==0 || 
	  (tsize!=csize && deljunk>0 && (ctime-rtime)/DAYSEC>=deljunk)) {
        snprintf(MAXS(file),"%s/%d.d",userspool,id);
	unlink(file);
	snprintf(MAXS(file),"%s/%d.h",userspool,id);
	unlink(file);
	continue;
      }

      /* bad header file? */
      if (*from==0 || *fname==0) continue;

      /* is it a requested sender name? (ignore real name) */
      strcpy(line,from);  if ((cp=strchr(line,' '))) *cp=0;
      strcpy(tmp,sender); if ((cp=strchr(tmp,' ')))  *cp=0;
      if (sender==NULL || *sender==0 || simplematch(line,tmp,1)) {
       
        /* create new file list element */
	if ((fln=(struct filelist *) malloc(sizeof(struct filelist))) == NULL ||
	    (fln->fname=    (char *) malloc(strlen(fname)+1))         == NULL ||
	    (fln->comment=  (char *) malloc(strlen(comment)+1))       == NULL ||
	    (fln->sign=     (char *) malloc(strlen(sign)+1))          == NULL) {
	  if (client) message("",'F',"cannot allocate memory"); else reply(453);
	}
	
	/* fill in new file list element */
	fln->id=id;
	fln->next=NULL;
	fln->osize=osize;
	fln->csize=csize;
	fln->tsize=tsize;
	fln->flags=flags;
	fln->rtime=rtime;
	strcpy(fln->date,date);
	strcpy(fln->sign,sign);
	strcpy(fln->fname,fname);
	strcpy(fln->rdate,rdate);
	strcpy(fln->charset,charset);
	strcpy(fln->comment,comment);
	strcpy(fln->compress,compress);

        /* first sender? */
        if (sls==NULL)
          sls=newsle(fln,from);
        else {
	 
	  /* search for sender in senderlist */
          slp=sls;
          while (slp->next && !str_eq(slp->from,from)) slp=slp->next;

          /* sender not found in sender list? */
          if (!str_eq(slp->from,from))

            /* create new sender list element and append it */
            slp->next=newsle(fln,from);

          else {
	   
	    /* is it the smallest id? */
            if (id < slp->flist->id) {
	      fln->next=slp->flist;
              slp->flist=fln;
            } else {
	     
             /* insert into files list */
              for (flp=slp->flist; flp->next!=NULL; flp=flp->next) {
	        if (id < flp->next->id) {
		  fln->next=flp->next;
                  flp->next=fln;
                  break;
                }
              }

              /* last element? */
              if (flp->next==NULL) flp->next=fln;

            }
          }
        }
      }
    }
  }

#ifdef NEXT
  pclose(pp);
#else
  closedir(dp);
#endif
  sll=sls;
  return(sls);
}


/*
 * scanoutspool - scan through outgoing spool directory, build list-structures
 *		  and delete bad files if necessary
 *
 * INPUT:   sender  - sender name
 *
 * RETURN:  start of host list, NULL on error
 * 
 * REMARK:  if no files in outgoing spool are found a special hostlist structure
 *          will be returned with all zeros values.
 *
 * All header lines in the outgoing spool files are SAFT-conform (strings are
 * in UTF-7, etc).
 */
struct hostlist *scanoutspool(char *sender) {
  extern int client;		/* flag to determine client or server */
  char
    *cp,			/* simple character pointer */
    *arg,			/* the argument(s) of the header line */
    hline[MAXLEN],		/* header line */
    host[MAXLEN],		/* header recipient host line */
    to[MAXLEN],			/* header recipient user line */
    from[MAXLEN],		/* header from line */
    fname[MAXLEN],		/* header file line */
    tmp[MAXLEN],		/* temporary string */
    msg[MAXLEN],		/* information/error message */
    hfn[MAXLEN],		/* outgoing spool header file name */
    dfn[MAXLEN];		/* outgoing spool data file name */
  int hfc;			/* header file corruption flag */
  off_t size;			/* size of outgoing spool data file */
  FILE *hf;			/* header file */
  struct stat finfo;		/* information about a file */
#ifdef NEXT
  FILE *pp;			/* pipe */
#else
  struct dirent *dire;		/* directory entry */
  DIR *dp;			/* directory pointer */
#endif
  struct outfilelist
    *oflp,			/* outgoing file list pointer */
    *ofln;			/* new file list element */
  struct hostlist
    *hls,			/* host list start */
    *hln,			/* host list next pointer */
    *hlp;			/* host list pointer */
  static struct hostlist
    *hll=NULL;			/* last host list start */

  oflp=NULL;

  /* is there already an old hostlist? */
  if (hll) {

    /* delete old hostlist and free memory */
    for (hlp=hll; hlp;) {
      for (oflp=hlp->flist; oflp;) {
        ofln=oflp->next;
	free(oflp->to);
	free(oflp->oshfn);
	free(oflp->fname);
	free(oflp);
	oflp=ofln;
      }
      hln=hlp->next;
      free(hlp);
      hlp=hln;
    }
    
  }
  
  /* create first empty host list element */
  hls=hll=newhle(NULL,"");

  /* mega stupid NeXT has broken readdir() */
#ifdef NEXT
  /* open spool dir */
  snprintf(MAXS(tmp),"cd %s;ls %s_*.h 2>/dev/null",OUTGOING,sender);
  if ((pp=popen(tmp,"r")) == NULL) return(NULL);

  /* scan through spool directory */
  while (fgetl(hfn,pp)) {
   
    if ((cp=strrchr(hfn,'\n'))) *cp=0;
#ifdef JEDPARSE
    }
#endif

#else
  /* open spool dir */
  if (!(dp=opendir(OUTGOING))) return(NULL);

  /* scan through outgoing spool directory */
  while ((dire=readdir(dp))) {
   
    strcpy(hfn,dire->d_name);
#endif

    /* look for header files */
    snprintf(MAXS(tmp),"%s*.h",sender);
    if (simplematch(hfn,tmp,1)==0) continue;

    strcpy(tmp,hfn);
    snprintf(MAXS(hfn),OUTGOING"/%s",tmp);

    /* open header file */
    if ((hf=rfopen(hfn,"r")) == NULL) {
     
      /* called from receive client? */
      if (client) {
        snprintf(MAXS(msg),"cannot open outgoing spool file %s",hfn);
	message("",'E',msg);
      }

      continue;
    }
    
    /* initialisize header entries */
    size=0;
    *to=0;
    *host=0;
    *from=0;
    *fname=0;

    /* read header file */
    hfc=0;
    while (fgetl(hline,hf) && hfc==0) {
     
      /* prepare the header line */
      if ((cp=strchr(hline,'\n'))) *cp=0;
      cp=strchr(hline,'\t');

      /* corrupt header file line? */
      if (cp==NULL) {
        hfc=1;
	continue;
      }

      arg=cp+1;
      *cp=0;

      /* extract the header-name and the argument */

      /* check the from line */
      if (str_eq(hline,"FROM")) {
        if ((cp=strchr(arg,' '))) *cp=0;
	utf2iso(0,from,NULL,NULL,arg);

	/* wrong from line? */
	if (*sender && !str_eq(from,sender)) hfc=1;

	continue;
      }

      /* check the to line */
      if (str_eq(hline,"TO")) {
        if (!(cp=strchr(arg,'@')))
	  hfc=1;
	else {
	  strcpy(host,cp+1);
	  *cp=0;
	  utf2iso(0,to,NULL,NULL,arg);
	}
	continue;
      }

      if (str_eq(hline,"FILE")) {
	strcpy(fname,arg);
	continue;
      }

      if (str_eq(hline,"SIZE")) {
        sscanf(arg,"%lld",&size);
	strcpy(dfn,hfn);
	dfn[strlen(dfn)-1]='d';

	/* wrong size? */
	if (stat(dfn,&finfo)<0 || finfo.st_size!=size) hfc=1;

	continue;
      }

    }

    fclose(hf);

    /* delete bad files */
    if (*from==0 || *fname==0 || *to==0 || *host==0 || hfc) {
      strcpy(dfn,hfn);
      dfn[strlen(dfn)-1]='d';
      unlink(dfn);
      unlink(hfn);
      continue;
    }

    /* create new outgoing file list element */
    if ((ofln=(struct outfilelist *)malloc(sizeof(struct outfilelist)))==NULL||
	(ofln->fname=       (char *)malloc(strlen(fname)+1))           ==NULL||
	(ofln->to=          (char *)malloc(strlen(to)+1))              ==NULL||
	(ofln->oshfn=       (char *)malloc(strlen(hfn)+1))             ==NULL||
	(ofln->from=        (char *)malloc(strlen(from)+1))            ==NULL){
      if (client) message("",'F',"cannot allocate memory"); else reply(453);
    }

    /* fill in new outgoing file list element */
    ofln->next=NULL;
    ofln->size=size;
    strcpy(ofln->to,to);
    strcpy(ofln->from,from);
    strcpy(ofln->oshfn,hfn);
    strcpy(ofln->fname,fname);

    /* first host? */
    if (hls->flist==NULL) {
      hls->flist=ofln;
      strcpy(hls->host,host);
      
    } else {
     
      /* search for host in hostlist */
      hlp=hls;
      while (hlp->next && !str_eq(hlp->host,host)) hlp=hlp->next;

      /* host not found in host list? */
      if (!str_eq(hlp->host,host))

	/* create new host list element */
	hlp->next=newhle(ofln,host);

      else  /* append to outgoing file list */ {
        for (oflp=hlp->flist; oflp->next!=NULL; oflp=oflp->next);
	oflp->next=ofln;
      }

    }
  }

#ifdef NEXT
  pclose(pp);
#else
  closedir(dp);
#endif
  hll=hls;
  return(hls);
}


/*
 * newsle - create new sender list element and fill it out
 *
 * INPUT:  flp  - first file list element
 *         from - sendername
 *
 * RETURN: start of sender list
 */
struct senderlist *newsle(struct filelist *flp, const char *from) {
  struct senderlist *sln;	/* new sender list element */
  extern int client;		/* flag to determine client or server */

  /* create new sender list element */
  if ((sln=(struct senderlist *)malloc(sizeof(struct senderlist))) == NULL) {
    if (client) message("",'F',"cannot allocate memory"); else reply(453);
  }

  /* fill it out */
  sln->next=NULL;
  sln->flist=flp;
  strcpy(sln->from,from);

  return(sln);
}


/*
 * newhle - create new host list element and fill it out
 *
 * INPUT:  oflp  - first outgoing file list element
 *         host - host name
 *
 * RETURN: start of sender list
 */
struct hostlist *newhle(struct outfilelist *oflp, const char *host) {
  struct hostlist *hln;	/* new host list element */
  extern int client;	/* flag to determine client or server */

  /* create new host list element */
  if ((hln=(struct hostlist *)malloc(sizeof(struct hostlist))) == NULL) {
    if (client) message("",'F',"cannot allocate memory"); else reply(453);
  }

  /* fill it out */
  hln->next=NULL;
  hln->flist=oflp;
  strcpy(hln->host,host);

  return(hln);
}


/*
 * delete_sf - delete a spool file
 *
 * INPUT:  flp   	- file list pointer
 *         verbose	- if set, print success message
 *
 * RETURN: 0 on success, -1 on fail
 */
int delete_sf(struct filelist *flp, int verbose) {
  char msg[MAXLEN],		/* information/error message */
       file[MAXLEN],		/* spool file */
       fname[MAXLEN];		/* displayble file name */
  extern int client;		/* flag to determine client or server */
  extern char userspool[];	/* user spool directory */

  snprintf(MAXS(file),"%s/%d.d",userspool,flp->id);
  unlink(file);
  snprintf(MAXS(file),"%s/%d.h",userspool,flp->id);
  utf2iso(1,NULL,fname,NULL,flp->fname);
  if(unlink(file)<0) {
    if (client) {
      snprintf(MAXS(msg),"cannot delete spoolfile #%d",flp->id);
      message("",'W',msg);
    }
    return(-1);
  } else {
    if (verbose) {
      snprintf(MAXS(msg),"%s deleted",fname);
      message("",'I',msg);
    }
    return(0);
  }
}


/*
 * spoolid - find the next spool id and touch header and data file
 *
 * INPUT:  maxfiles  - maximum number of allowed spool files
 *
 * RETURN: spool id, 0 if failed, -number of files in spool if maxfiles exceeded
 *
 * The working directory has to be the user spool!
 */
int spoolid(int maxfiles) {
  int
    i,			/* simple loop count */
    n,			/* number of files in spool */
    fd,			/* file descriptor of spool id header file */
    id,			/* id number */
    idmax;		/* biggest id number */
  char
    *cp, 		/* character pointer to '.' in file name */
    file[MAXLEN];	/* complete file name */
#ifdef NEXT
  char tmp[MAXLEN];	/* tmp string */
  FILE *pp;		/* pipe */
#else
  struct dirent *dire;	/* directory entry */
  DIR *dp;		/* directory pointer */
#endif


  /* try to create next spool files */
  for (i=1; i<5; i++) {
    
    /* initialisize */
    n=0;
    id=0;
    fd=0;
    idmax=0;

#ifdef NEXT
    /* stupid NeXT has a broken readdir(); this is a dirty workaround */

    /* open spool dir */
    if ((pp=popen("ls . 2>/dev/null","r")) == NULL) return(NULL);

    /* scan through spool directory */
    while (fgetl(tmp,pp)) {
      
      if ((cp=strrchr(tmp,'\n'))) *cp=0;
      cp=strchr(tmp,'.');
      if (cp && str_eq(cp,".h")) {
	n++;
	*cp=0;
        id=atoi(tmp);
	if (id>idmax) idmax=id;
      }
    }
    pclose(pp);

#else
    /* open spool dir */
    dp=opendir(".");

    /* scan through spool directory and get the highest spool id */
    while ((dire=readdir(dp))) {
      cp=strchr(dire->d_name,'.');
      if (cp && str_eq(cp,".h")) {
	*cp=0;
	n++;
        id=atoi(dire->d_name);
	if (id>idmax) idmax=id;
      }
    }
    closedir(dp);
#endif

    id=idmax+1;
    if (n>maxfiles) return(-n);

    /* try to create header spool file */
    snprintf(MAXS(file),"%d.h",id);
    fd=open(file,O_CREAT|O_EXCL,S_IRUSR|S_IWUSR);

    /* successfull? */
    if (fd>=0) {
      
      close(fd);

      /* create data spool file */
      snprintf(MAXS(file),"%d.d",id);
      close(open(file,O_CREAT|O_LARGEFILE,S_IRUSR|S_IWUSR));

      return(id);
    }

    /* wait and test again */
    sleep(1);
  }

  /* failed */
  return(0);
}
