/*
 * File:        fetchfile.c
 *
 * Author:      Ulli Horlacher (framstag@rus.uni-stuttgart.de)
 * 
 * Contrib.:	Michael Neumayer (eumel@42.org)
 *
 * History:
 *
 *  1997-09-28  Framstag	initial version
 *  1997-10-03  Framstag	added -r autoreceive option
 *  1997-10-05  Framstag	better pgp error handling
 *  1997-10-24  Framstag	added check for pgp key files
 *  1997-12-11  Eumel		fixed pgp my_ID problem
 *  1997-12-19  Framstag	default key length is now 1024 bit
 *  1998-01-03  Framstag	config options id and server merged to o-saft
 *  1998-03-11  Framstag	new default user configuration directory
 *  2001-02-04	Framstag	added secure mktmpdir()
 *
 * The fetchfile client of the sendfile package.
 * Lists or gets files from a SAFT server to put them into the local spool.
 *
 * Copyright © 1997,1998 Ulli Horlacher
 * This file is covered by the GNU General Public License
 */

#include "config.h"		/* autoconf header */

#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pwd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>

#include "string.h"		/* extended string functions */
#include "net.h"		/* the network routines */
#include "io.h"			/* (socket) read/write */
#include "message.h"		/* information, warning and error messages */
#include "spool.h"		/* operations on files in the sendfile spool */
#include "utf7.h"		/* UTF-7 coding */
#include "address.h"            /* address checking */
#include "getline.h"		/* get a line of text from stdin */

#if defined(HAVE_GETOPT_H)
  #include <getopt.h>
#else
  int getopt(int, char * const *, const char *);
  extern int opterr;
  extern int optind;
  extern int optopt;
  extern char *optarg;
#endif

#if defined(SOLARIS2)
  int gethostname(char *, int);
#endif

#if defined(LINUX)
  int gethostname(char *, size_t);
  int symlink(const char *, const char *);
#endif

#ifndef AIX
  #ifndef CONVEXOS
    FILE *popen(const char *, const char *);
  #endif
  int pclose(FILE *);
#endif

#ifdef NEXT
  int shutdown(int, int);
#endif

#if defined(AIX3) || defined(ULTRIX)
  #include "bsd.h"
#endif

/* print short help usage text */
int usage();

/* get file list from server */
int get_list(int, const char *, const char *, FILE *);

/* get file from server */
int get_file(int, int, int);

/* delete a file from the server */
int delete_file(int, int);

/* clean termination routine */
void cleanexit();

/* delete tmp-file */
void cleanup();

/* delete tmp-file */
void init();

/* test local sendfiled */
int sendfiled_test(const char *);

/* exit wit receive evaluation */
void rexit(int);


/* global variables */
int verbose=0,		/* flag for verbose mode */
    client=1,		/* flag to determine client or server */
    quiet=0,		/* quiet mode */
    ignore=0,		/* ignore testing flag */
    packet_size=0;	/* packet size */
char *prg,		/* name of the game */
     *tmpdir,		/* directory for temporary files */
     swd[MAXLEN], 	/* start working directory */
     rfilen[MAXLEN], 	/* receive files for -R option */
     userspool[MAXLEN],	/* user spool directory */
     userconfig[MAXLEN],/* user configuration directory */
     listfile[MAXLEN],  /* file with list command output */
     pgp_bin[MAXLEN],	/* the pgp binary */
     pgptmp[MAXLEN];	/* name of pgp temporary file */
struct passwd *pwe;	/* password entry */
struct senderlist *sls;	/* sender list start */


int main(int argc, char *argv[]) {
  int
    i,			/* simple loop counter */
    n,			/* number of files on the server */
    pid,        	/* current proccess id */
    sockfd,     	/* socket file descriptor */
    opt,        	/* option to test for */
    number,        	/* file number */
    wconf,		/* flag for writing config file (0=reading) */
    del,        	/* flag for deleting previous sent files */
    all,        	/* flag for specifying all files */
    numbers,        	/* flag for specifying file numbers */
    ptso,        	/* flag for "pipe to standard output" */
    keep,	     	/* flag for keeping files on server */
    list;  	   	/* flag for listing files in outgoing spool */
  char
    *cp,		/* simple string pointer */
    *argp,		/* argument string pointer */
    *user,	 	/* sending user pattern */
    id[FLEN],		/* user identication for SAFT server */
    server[FLEN],	/* SAFT server */
    alternate[MAXLEN],	/* alternate user and/or SAFT server */
    cmd[MAXLEN], 	/* cmd string for system-call */
    line[MAXLEN], 	/* one line of text */
    from[MAXLEN], 	/* sender address */
    fname[MAXLEN], 	/* file name */
    conffile[MAXLEN], 	/* config file name */
    response[MAXLEN],	/* signed response to authorization challenge */
    tmp[3*MAXLEN];	/* temporary string */
  FILE
    *pp,		/* pipe */
    *listf,		/* list command output file */
    *inf,		/* input file */
    *outf;		/* output file */
  struct stat finfo;	/* information about a file */

  n=0;
  del=0;
  all=0;
  ptso=0;
  list=0;
  keep=0;
  sockfd=0;
  verbose=0;
  numbers=0;
  *id=0;
  *server=0;
  *rfilen=0;
  *conffile=0;
  *response=0;
  *alternate=0;
  user="*";
  listf=NULL;
  pid=(int)getpid();
  getcwd(MAXS(swd));

  prg=argv[0];
  if ((cp=strrchr(prg,'/'))) prg=cp+1;

  /* protect all tmp-files */
  umask(~(S_IRUSR|S_IWUSR));
      
  /* support programs defaults */
  strcpy(pgp_bin,PGP);

  /* look for environment variables */
  if ((cp=getenv("SF_PGP")))	strcpy(pgp_bin,cp);

  /* do the support programs really exist? */
  if (access(pgp_bin,X_OK)<0)	strcpy(pgp_bin,"pgp");

  /* get the own user name and tmpdir */
  if ((pwe=getpwuid(getuid())) == NULL)
    message(prg,'F',"cannot access user system informations");
  tmpdir=mktmpdir(verbose);

  /* set various file names and check user spool and configuration directory */
  snprintf(MAXS(pgptmp),"%s/fetchfile.pgp",tmpdir);
  snprintf(MAXS(userspool),SPOOL"/%s",pwe->pw_name);
  if (stat(userspool,&finfo)<0) sendfiled_test(pwe->pw_name);
  snprintf(MAXS(userconfig),"%s/.sendfile",pwe->pw_dir);
  snprintf(MAXS(tmp),"%s/config",userspool);
  if (stat(userconfig,&finfo)<0 && stat(userspool,&finfo)==0)
    symlink(tmp,userconfig);
  snprintf(MAXS(tmp),"%s/.sfspool",pwe->pw_dir);
  if (stat(tmp,&finfo)==0 && finfo.st_mode&S_IFDIR) strcpy(userspool,tmp);

  /* scan the command line on options */
  while ((opt=getopt(argc, argv, "vVqQ?hrPIliadkns:f:C:")) > 0) {
    switch (opt) {
      case ':':
      case 'h':
      case '?': exit(usage());
      case 'r': strcpy(rfilen,"receive -n "); break;
      case 's': strcpy(alternate,optarg); break;
      case 'C': strcpy(conffile,optarg); break;
      case 'f': user=optarg; break;
      case 'd': del=1; break;
      case 'k': keep=1; break;
      case 'P': ptso=1; break;
      case 'i': ignore=1; break;
      case 'n': numbers=1; break;
      case 'a': all=1; break;
      case 'l': list=1; break;
      case 'q': quiet=1; break;
      case 'Q': quiet=2; break;
      case 'v': verbose=1; break;
      case 'V': message(prg,'I',"version "VERSION" revision "REVISION); exit(0);
      case 'I': init(); exit(0);
    }
  }

  /* too few arguments? */
  if (argc-optind<1 && !all && !list && !*conffile) exit(usage());

  /* incompatible options? */
  if (*conffile && (del || keep || all || list || numbers)) {
    errno=0;
    message(prg,'F',"you cannot mix -C with any other option");
  }
  
  if (del && keep) {
    errno=0;
    message(prg,'F',"you cannot delete and keep a file at the same time");
  }
  
  if (ptso && *rfilen) {
    errno=0;
    message(prg,'F',"you cannot auto-receive and pipe to stdout (-R and -n options)");
  }
  
  if (list && (del || keep || all || *rfilen || ptso)) {  
    errno=0;
    message(prg,'F',"you cannot not specify any other option with -l");
  }

  if (all && argc>optind) {  
    errno=0;
    message(prg,'F',"you cannot specify file names or numbers "
	            "together with -a option");
  }

  /* alternate user or server specified? */
  if (*alternate) {
    if (str_beq_nocase(alternate,"saft://")) saft2rfc822(alternate);
    if ((cp=strchr(alternate,'@'))) {
      *cp=0;
      strcpy(id,alternate);
      strcpy(server,cp+1);
    } else {
      strcpy(server,alternate);
    }
  }
  
  /* check tmp files */
  unlink(pgptmp);
  if (stat(pgptmp,&finfo)==0) {
    snprintf(MAXS(tmp),
	     "tmp-file %s does already exist and cannot be deleted",pgptmp);
    message(prg,'F',tmp);
  }

      
  /* parse the global 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,'='))) *cp=' ';
      str_tolower(str_trim(line));

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

    }
    fclose(inf);
  }

  /* check pgp key files */
  snprintf(MAXS(tmp),"%s/private.pgp",userconfig);
  if (stat(tmp,&finfo)<0) {
    snprintf(MAXS(line),"no access to %s (try 'fetchfile -I' first)",tmp);
    message(prg,'F',line);
  }
  snprintf(MAXS(tmp),"%s/public.pgp",userconfig);
  if (stat(tmp,&finfo)<0) {
    snprintf(MAXS(line),"no access to %s (try 'fetchfile -I' first)",tmp);
    message(prg,'F',line);
  }
  
  /* parse the user config-file */
  snprintf(MAXS(tmp),"%s/config",userconfig);
  if ((inf=rfopen(tmp,"r"))) {
    while (fgetl(line,inf)) {
      
      /* prepare line to be parsed */
      if ((cp=strchr(line,'#'))) *cp=0;
      if ((cp=strchr(line,'='))) *cp=' ';
      str_tolower(str_trim(line));

      /* is there an option and an argument? */
      if ((argp=strchr(line,' '))) {
	*argp=0; argp++;

	if (str_eq(line,"id") && !*id) { /* depreciated */
	  strcpy(id,argp);
	  continue;
	}
	
	if (str_eq(line,"server") && !*server) { /* depreciated */
	  strcpy(server,argp);
	  continue;
	}
	
	if (str_eq(line,"o-saft") && !*server) {
	  if (str_beq_nocase(argp,"saft://")) saft2rfc822(argp);
	  if ((cp=strchr(argp,'@'))) {
	    *cp=0;
	    strcpy(id,argp);
	    strcpy(server,cp+1);
	  } else {
	    strcpy(id,pwe->pw_name);
	    strcpy(server,argp);
	  }
	}

      }
    }
    fclose(inf);
  }

  if (!*id) strcpy(id,pwe->pw_name);
  if (!*server) strcpy(server,"localhost");
  snprintf(MAXS(listfile),"%s/%s@%s:fetch.lis",userspool,id,server);
  
  /* initiate the connection to the server */
  if (!*server) {
    errno=0;
    message(prg,'F',"no SAFT server is defined");
  }
  snprintf(MAXS(tmp),"connecting to SAFT server %s",server);
  if (quiet<2) message(prg,'I',tmp);
  sockfd=open_connection(server,SAFT);
  if (sockfd==-1) snprintf(MAXS(tmp),"cannot create a network socket");
  if (sockfd==-2) snprintf(MAXS(tmp),"cannot open connection to %s",server);
  if (sockfd==-3) snprintf(MAXS(tmp),"%s is unknown",server);
  if (sockfd<0) {
    errno=0;
    message(prg,'F',tmp);
  }

  /* no remote server or protocol error? */
  sock_getline(sockfd,line);
  if (!str_beq(line,"220 ") || !strstr(line,"SAFT")) {
    errno=0;
    snprintf(MAXS(tmp),"No SAFT server on port %d at %s",SAFT,server);
    message(prg,'F',tmp);
  }
  
  /* send ID */
  snprintf(MAXS(tmp),"ID %s",id);
  sock_putline(sockfd,tmp);
  sock_getline(sockfd,line);
  if (str_beq(line,"520")) {
    errno=0;
    snprintf(MAXS(tmp),"user %s is unknown on SAFT-server %s",id,server);
    message(prg,'F',tmp);
  }
  if (!str_beq(line,"331")) {
    errno=0;
    snprintf(MAXS(tmp),"server error: %s",line+4);
    message(prg,'F',tmp);
  }
  str_trim(line);
  cp=strrchr(line,' ');
  if (!cp) {
    errno=0;
    message(prg,'F',"bad answer from server");
  }
  outf=rfopen(pgptmp,"w");
  if (!outf) {
    errno=0;
    snprintf(MAXS(tmp),"cannot open/write to %s",pgptmp);
    message(prg,'F',tmp);
  }
  fprintf(outf,"%s",cp+1);
  fclose(outf);

  /* goto user spool directory */
  if (chdir(userspool)<0) {
    snprintf(MAXS(tmp),"cannot change to %s",userspool);
    message(prg,'F',tmp);
  }
  
  /* call pgp */
  /* DONT REMOVE 2>/dev/null IN THE FOLLOWING LINE! */
  snprintf(MAXS(cmd),"cd %s; PGPPATH='.' %s -sbaf "
	   "+secring=private.pgp +pubring=public.pgp <%s 2>/dev/null",
	   userconfig,pgp_bin,pgptmp);
  if (verbose) printf("call: %s\n",cmd);
  if (!(pp=popen(cmd,"r"))) message(prg,'F',"call to pgp failed");
  while (fgetl(line,pp) && 
	 strlen(response)+strlen(line)+3 < sizeof(response)) {
    if ((cp=strchr(line,'\n'))) *cp=0;
    strcat(response,line);
    strcat(response,"\r\n");
  }
  pclose(pp);
  
  /* wrong pgp output? */
  if (!strstr(response,"BEGIN PGP MESSAGE")) {
    errno=0;
    message(prg,'E',"corrupt pgp reply:");
    cp=strrchr(cmd,'2');
    *cp=0;
    system(cmd);
    message(prg,'F',"corrupt pgp reply");
  }
  
  iso2utf(tmp,response);
  snprintf(MAXS(response),"AUTH %s",tmp);
  sendheader(sockfd,response);

  /* config file transfer? */
  if (*conffile) {
    
    if (*conffile=='w') wconf=1; else wconf=0;

    /* extract real file name */
    if (!(cp=strchr(conffile+1,'='))) cp=conffile;
    strcpy(tmp,cp+1);
    if (*tmp == '/')
      strcpy(conffile,tmp);
    else
      snprintf(MAXS(conffile),"%s/%s",swd,tmp);

    /* write config file */
    if (wconf) {
      
      if ((cp=strrchr(conffile,'/')))
	cp++;
      else
	cp=conffile;
      snprintf(MAXS(tmp),"CONF WRITE %s",cp);
      sock_putline(sockfd,tmp);
      sock_getline(sockfd,line);
      if (!str_beq(line,"302 ") && !str_beq(line,"200 ")) {
	errno=0;
	snprintf(MAXS(tmp),"server error: %s",line+4);
	message(prg,'F',tmp);
      }
    
      inf=rfopen(conffile,"r");
      if (!inf) {
	snprintf(MAXS(tmp),"cannot open %s",conffile);
	message(prg,'F',tmp);
      }

      if (quiet<2) {
	snprintf(MAXS(tmp),"transfering %s",conffile);
	message(prg,'I',tmp);
      }
      
      /* send the file */
      while (fgetl(line,inf)) {
	if ((cp=strchr(line,'\n'))) *cp=0;
	sock_putline(sockfd,line);
      }
      sock_putline(sockfd,"\004"); /* End Of Transmission */
      fclose(inf);

      sock_getline(sockfd,line);
      if (!str_beq(line,"201 ")) {
	errno=0;
	snprintf(MAXS(tmp),"server error: %s",line+4);
	message(prg,'F',tmp);
      }
    
    } else { /* read config file */
      
      if ((cp=strrchr(conffile,'/')))
	cp++;
      else
	cp=conffile;
      snprintf(MAXS(tmp),"CONF READ %s",cp);
      sock_putline(sockfd,tmp);
      while (sock_getline(sockfd,line)) {
	if (!str_beq(line,"250")) {
	  errno=0;
	  snprintf(MAXS(tmp),"server error: %s",line+4);
	  message(prg,'F',tmp);
	}
	if (str_beq("250 ",line)) break;
	utf2iso(0,NULL,tmp,NULL,line+4);
	printf("%s\n",tmp);
      }
    }
    
    sock_putline(sockfd,"QUIT");
    getreply(sockfd);
    close(sockfd);
    cleanup();
    exit(0);
    
  }
    
  /* enable simple interrupt handler */
  signal(SIGTERM,cleanexit);	     
  signal(SIGABRT,cleanexit);
  signal(SIGQUIT,cleanexit);
  signal(SIGHUP,cleanexit);
  signal(SIGINT,cleanexit);
				     
  /* list files in outgoing spool */ 
  if (list) {
    n=get_list(sockfd,server,id,listf);
    sock_putline(sockfd,"QUIT");
    getreply(sockfd);
    close(sockfd);
    cleanup();
    exit(n);
  }

  /* set tcp packet length */
  if (packet_size<1) packet_size=PACKET;

  /* scan local spool */
  sls=scanspool("");
  
  if (numbers) {
    for (i=optind; i<argc; i++) {
      number=atoi(argv[i]);
      if (del) {
	if (delete_file(sockfd,number)<0) {
	  snprintf(MAXS(tmp),"cannot delete file #%d from server",number);
	  errno=0;
	  message(prg,'E',prg);
	} else {
	  snprintf(MAXS(tmp),"file #%d deleted from server",number);
	  if (quiet<2) message(prg,'I',tmp);
	  n++;
	}
      } else {
	if (get_file(sockfd,number,ptso)<0) break;
	if (!keep && delete_file(sockfd,number)<0)
	  message(prg,'W',"cannot delete this file from server");
	n++;
      }
    }
    rexit(n);
  }

  /* get actual list of files from server */
  listf=rfopen(listfile,"w");
  if (listf) {
    get_list(sockfd,server,id,listf);
    fclose(listf);
  } else {
    snprintf(MAXS(tmp),"cannot open %s for writing",listfile);
    message(prg,'F',tmp);
  }

  /* fetch (or delete) all files */
  if (all) {
    listf=rfopen(listfile,"r");
    if (!listf) {
      snprintf(MAXS(tmp),"cannot open %s for reading",listfile);
      message(prg,'F',tmp);
    }
    while (fgetl(line,listf)) {
      if ((cp=strchr(line,' '))) {
	*cp=0;
	strcpy(tmp,cp+1);
	if ((cp=strchr(tmp,' '))) *cp=0;
	utf2iso(1,NULL,fname,NULL,tmp);
	strcpy(tmp,cp+1);
	if ((cp=strchr(tmp,' '))) *cp=0;
	utf2iso(1,NULL,from,NULL,tmp);
	if ((cp=strchr(from,' '))) *cp=0;
      }
      if (simplematch(from,user,0)==1) {
	number=atoi(line);
	if (del) {
	  if (delete_file(sockfd,number)<0) {
	    snprintf(MAXS(tmp),"cannot delete file #%d (%s) from server",number,fname);
	    errno=0;
	    message(prg,'E',prg);
	  } else {
	    snprintf(MAXS(tmp),"file #%d (%s) deleted from server",number,fname);
	    if (quiet<2) message(prg,'I',tmp);
	    n++;
	  }
	} else {
	  if (get_file(sockfd,number,ptso)<0) break;
	  if (!keep && delete_file(sockfd,number)<0)
	    message(prg,'W',"cannot delete this file from server");
	  n++;
	}
      }
    }
    fclose(listf);
    rexit(n);
  }
    
  /* fetch specified files */
  for (i=optind;i<argc;i++) {
    listf=rfopen(listfile,"r");
    if (!listf) {
      snprintf(MAXS(tmp),"cannot open %s for reading",listfile);
      message(prg,'F',tmp);
    }
    while (fgetl(line,listf)) {
      number=atoi(line);
      if (!(cp=strchr(line,' '))) continue;
      strcpy(tmp,cp+1);
      if ((cp=strchr(tmp,' '))) *cp=0;
      utf2iso(1,NULL,fname,NULL,tmp);
      strcpy(tmp,cp+1);
      if ((cp=strchr(tmp,' '))) *cp=0;
      utf2iso(1,NULL,from,NULL,tmp);
      if ((cp=strchr(from,' '))) *cp=0;
      if (simplematch(fname,argv[i],0)==1 && simplematch(from,user,0)==1) {
	number=atoi(line);
	if (del) {
	  if (delete_file(sockfd,number)<0) {
	    snprintf(MAXS(tmp),"cannot delete file #%d (%s) from server",number,fname);
	    errno=0;
	    message(prg,'E',prg);
	  } else {
	    snprintf(MAXS(tmp),"file #%d (%s) deleted from server",number,fname);
	    if (quiet<2) message(prg,'I',tmp);
	    n++;
	  }
	} else {
	  if (get_file(sockfd,number,ptso)<0) break;
	  if (!keep && delete_file(sockfd,number)<0)
	    message(prg,'W',"cannot delete this file from server");
	  n++;
	}
      }
    }
    fclose(listf);
  }
  rexit(n);
  exit(0);
}


/*
 * cleanexit  - clean termination routine
 *
 * A very simple interrupt handler
 */
void cleanexit() {
  
  /* ignore all relevant signals */
  signal(SIGTERM,SIG_IGN);
  signal(SIGABRT,SIG_IGN);
  signal(SIGQUIT,SIG_IGN);
  signal(SIGHUP,SIG_IGN);
  signal(SIGINT,SIG_IGN);

  printf("\r\n");
  cleanup();
  exit(0);
}


/*
 * cleanup  - delete tmp files
 */
void cleanup() {

#ifndef DEBUG
  rmtmpdir(tmpdir);
#endif

}


/*
 * rexit  - exit with receive evaluation
 */
void rexit(int n) {
  char tmp[MAXLEN];	/* temporary string */
  
  cleanup();
  
  /* are there any files to be received automaticly? */
  if (strcmp(rfilen,"receive -n ")>0) {
    
    /* change back to starting directory */
    if (chdir(swd)<0) {
      snprintf(MAXS(tmp),"cannot change back to %s",swd);
      message(prg,'E',tmp);
    } else 
      if (verbose) printf("shell-call: %s\n",rfilen);
    
    /* call receive */
    system(rfilen);
    
  }
  exit(n);
}


/*
 * delete_file - delete a file from the server
 * 
 * INPUT:  sockfd   - socket file descriptor
 *         number   - spool file number
 * 
 * RETURN: 0 on success, -1 on error
 */
int delete_file(int sockfd, int number) {
  char line[MAXLEN]; 	/* one line of text */

  /* send LIST command */
  snprintf(MAXS(line),"DEL %d",number);
  sock_putline(sockfd,line);
  
  sock_getline(sockfd,line);
  if (str_beq(line,"200 "))
    return(0);
  else
    return(-1);
}


/*
 * get_list - get list of files from server
 * 
 * INPUT:  sockfd  - socket file descriptor
 *         server  - server name
 *         id      - user name
 *         listf   - list command output file
 * 
 * RETURN: number of files, -1 on error
 */
int get_list(int sockfd, const char *server, const char *id, FILE *listf) {
  int 
    i,		/* simple loop counter */
    n,		/* number of files */
    number;	/* file number */
  unsigned long
    size;	/* file size */
  char 
    *cp,		/* simple string pointer */
    tmp[MAXLEN], 	/* temporary string */
    line[MAXLEN], 	/* one line of text */
    from[MAXLEN];	/* address and name of sender */
  char 
    *flist[6];		/* file list elements */

  n=0;
  *from=0;

  /* send LIST command */
  sock_putline(sockfd,"LIST");
  
  for (;;) {
    
    /* get one line of answer from server */
    sock_getline(sockfd,line);
    str_trim(line);

    /* invalid answer? */
    if (!str_beq(line,"250")) {
      errno=0;
      snprintf(MAXS(tmp),"invalid answer from server: %s",line+4);
      message(prg,'E',tmp);
      return(n);
    }

    /* last answer line? */
    if (str_beq(line,"250 ")) {
      if (!listf) printf("\n");
      return(n);
    }
    
    /* write only to list file? */
    if (listf) {
      fprintf(listf,"%s\n",line+4);
      continue;
    }
    
    /* print server ID on top */
    if (!*from) {
      sprintf(tmp,"Files on SAFT-server %s for user %s:",server,id);
      printf("\n%s\n",tmp);
      for (i=1; i<80 && i<=strlen(tmp); i++) printf("=");
      printf("\n");
    }

    /* split file list */
    cp=line+4;
    str_trim(cp);
    for(i=1;i<6;i++) {
      flist[i]=cp;
      if ((cp=strchr(cp,' '))) {
	*cp=0;
	cp++;
      }
    }

    /* new from? */
    if (!str_eq(from,flist[3])) {
      strcpy(from,flist[3]);
      utf2iso(0,NULL,tmp,NULL,from);
      printf("\nFrom %s\n",tmp);
      for (i=1; i<80 && i<=strlen(tmp)+5; i++) printf("-");
      printf("\n");
    }

    /* build and print list line */
    if ((cp=strrchr(flist[5],':')) > strchr(flist[5],':')) *cp=0;
    number=atoi(flist[1]);
    size=atol(flist[4]);
    size=(size+1023)/1024;
    sprintf(tmp,"%3d) %s  %8lu kB  %s",number,flist[5],size,flist[2]);
    utf2iso(1,NULL,line,NULL,tmp);
    printf("%s\n",line);
    n++;
  }

}
  

/*
 * get_file - get a file from the server
 * 
 * INPUT:  sockfd  - socket file descriptor
 *         number  - file number
 *         ptso    - flag for "pipe to standard output"
 * 
 * RETURN: 0 if ok, -1 on error
 */
int get_file(int sockfd, int number, int ptso) {
  int 
    percent,		/* what percentage of file has been transmitted */
    chunk,		/* number of bytes in one read chunk */
    shfd,		/* spool header file descriptor */
    sdfd,		/* spool data file descriptor */
    id;			/* local spool id */
  unsigned long
    msec,		/* milliseconds */
    size,		/* total file size */
    rsize,		/* rest file size */
    offset,		/* bytes that have been already transfered */
    bytes;		/* bytes to receive */
  char
    *cp,		/* simple string pointer */
    date[DLEN],		/* date string */
    shfile[FLEN],	/* spool header file */
    sdfile[FLEN],	/* spool data file */
    packet[OVERSIZE], 	/* one line of text */
    line[MAXLEN], 	/* one line of text */
    from[MAXLEN], 	/* sender name (address) */
    fname[MAXLEN], 	/* file name */
    tmp[MAXLEN];	/* temporary string */
  time_t sec1,sec2;	/* unix time */
  float 
    thruput;		/* net throughput */
  struct filelist *
    flp;		/* file list pointer */
  struct senderlist
    *slp;		/* sender list pointer */
  struct timeval tv1,tv2;
#if !defined(SOLARIS2) && !defined(IRIX)
  struct timezone tz;
#endif

  id=0;
  shfd=0;
  sdfd=0;
  size=0;
  offset=0;
  *date=0;
  flp=NULL;
  
  /* get time normal */
#if defined(SOLARIS2) || defined(IRIX)
   #ifdef _SVID_GETTOD
     gettimeofday(&tv1);
   #else
     gettimeofday(&tv1,NULL);
   #endif
#else
  gettimeofday(&tv1,&tz);
#endif
  sec1=0;

  /* get next spool id */
  if (!ptso) {
    id=spoolid(999999);
    if (!id) message(prg,'F',"cannot create local spool file");
  
    /* open spool header and data files */
    snprintf(MAXS(shfile),"%d.h",id);
    snprintf(MAXS(sdfile),"%d.d",id);
    sdfd=open(sdfile,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);
    shfd=open(shfile,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);
    if (shfd<0 || sdfd<0) message(prg,'F',"cannot create local spool file");
  }
  
  snprintf(MAXS(tmp),"GET HEADER %d",number);
  sock_putline(sockfd,tmp);
  
  for (;;) {
    
    /* get one line of answer from server */
    sock_getline(sockfd,line);
    str_trim(line);
    
    /* invalid answer? */
    if (!str_beq(line,"250")) {
      if (!ptso) {
	close(sdfd);
	close(shfd);
	unlink(sdfile);
	unlink(shfile);
      }
      errno=0;
      snprintf(MAXS(tmp),"server-error: %s",line+4);
      message(prg,'E',tmp);
      return(-1);
    }
    
    /* last answer line? */
    if (str_beq(line,"250 ")) break;

    /* make SAFT commands uppercase */
    if ((cp=strchr(line+4,' '))) {
      *cp=0;
      str_toupper(line+4);
      *cp='\t';
    }
    
    /* store attributes */
    if (str_beq(line+4,"FILE")) strcpy(fname,line+9);
    if (str_beq(line+4,"SIZE")) size=atol(line+9);
    if (str_beq(line+4,"DATE")) utf2iso(1,date,NULL,NULL,line+9);
    if (str_beq(line+4,"FROM")) {
      if ((cp=strchr(line+10,' '))) {
	*cp=0;
	snprintf(MAXS(tmp),"%s (%s)",line+9,cp+1);
	*cp=' ';
      } else
	strcpy(tmp,line+9);
      utf2iso(1,from,NULL,NULL,tmp);
    }
    
    strcat(line,"\n");
    if (ptso)
      printf("%s",line+4);
    else 
      write(shfd,line+4,strlen(line+4));
  }
  
  close(shfd);

  /* check if file has been already fetched and how much of it */
  if (!ignore) {
    
    /* loop over sender list */
    for (slp=sls, offset=0; slp!=NULL; slp=slp->next) {

      /* same sender? */
      if (str_eq(slp->from,from)) {
      
	/* loop over files list */
	for (flp=slp->flist; flp!=NULL; flp=flp->next) {

	  /* same file? */
	  if (str_eq(flp->fname,fname) && flp->csize==size && 
	      (*date==0 || str_eq(flp->date,date))) {
	    offset=flp->tsize;
	    break;
	  }
	}
      }
      
      /* leave loop if found */
      if (offset) break;
    }
  }

  utf2iso(1,NULL,tmp,NULL,fname);
  strcpy(fname,tmp);

  if (quiet<2) {
    if (flp && flp->csize==offset) {
      snprintf(MAXS(tmp),"file %d (%s) has been already fetched",number,fname);
      message(prg,'I',tmp);
    } else {
      if (quiet==1) {
	if (offset)
	  snprintf(MAXS(tmp),"resuming fetching file %d (%s) with %ld kB",
		   number,fname,(size+1023)/1024);
	else
	  snprintf(MAXS(tmp),"fetching file %d (%s) with %ld kB",
		   number,fname,(size+1023)/1024);
	message(prg,'I',tmp);
      }
    }
  }

  if (flp && flp->csize==offset) {
    close(sdfd);
    unlink(sdfile);
    unlink(shfile);
    return(0);
  }
  
  snprintf(MAXS(tmp),"GET FILE %d %ld",number,offset);
  sock_putline(sockfd,tmp);
  sock_getline(sockfd,line);

  /* invalid answer? */
  if (!str_beq(line,"231 ")) {
    if (!ptso) {
      close(sdfd);
      unlink(sdfile);
      unlink(shfile);
    }
    errno=0;
    snprintf(MAXS(tmp),"server-error: %s",line+4);
    message(prg,'E',tmp);
    return(-1);
  }
  
  if (ptso) printf("DATA\n");
  fflush(stdout);
  
  rsize=atol(line+4);
  
  /* resend active? */
  if (offset && quiet<2) {
    snprintf(MAXS(tmp),"resuming at byte %ld",offset);
    message("",'I',tmp);
  }

  bytes=0;
  while (bytes<rsize) {
    chunk=read(sockfd,packet,packet_size);
    if (chunk<=0) {
      if (!ptso) close(sdfd);
      errno=0;
      message(prg,'E',"received too few data from server");
      return(-1);
    }
    bytes+=chunk;
    if (ptso) {
      write(fileno(stdout),packet,chunk);
    } else {
      percent=(bytes+offset)*100.0/(size);
      if (!quiet) {
	sec2=time(0);
	if (sec2>sec1) {
	  fprintf(stderr,"%s: %3d%%  (%ld of %ld kB)\r",
		  fname,percent,(bytes+offset-1)/1024+1,(size-1)/1024+1);
	  fflush(stderr);
	  sec1=sec2;
	}
      }
      write(sdfd,packet,chunk);
    }
  }
  if (!ptso) close(sdfd);

  /* note spool number for auto-receive */
  if (*rfilen) {
    sprintf(tmp," %d",id);
    strcat(rfilen,tmp);
  }
  
  if (quiet<2) {
   
    /* get time difference */
#if defined(SOLARIS2) || defined(IRIX)
   #ifdef _SVID_GETTOD
     gettimeofday(&tv2);
   #else
     gettimeofday(&tv2,NULL);
   #endif
#else
    gettimeofday(&tv2,&tz);
#endif
    msec=(tv2.tv_usec-tv1.tv_usec)/1000+(tv2.tv_sec-tv1.tv_sec)*1000;
    if (msec<1) msec=1;
    thruput=bytes*1000.0/msec;

    /* print transfer statistics */
    if (quiet==1) {
      
      if (thruput>9999)
	snprintf(MAXS(tmp),
		 "transfer of %s completed: %.1f kB/s",fname,thruput/1024);
      else
	snprintf(MAXS(tmp),
		 "transfer of %s completed: %d byte/s",fname,(int)thruput);
      message("",'I',tmp);

    } else {
      
      if (bytes>9999)
	fprintf(stderr,"%s: 100%%  (%ld kB, ",fname,bytes/1024);
      else
	fprintf(stderr,"%s: 100%%  (%ld byte, ",fname,bytes);
      if (thruput>9999)
	fprintf(stderr,"%.1f kB/s)            \n",thruput/1024);
      else
	fprintf(stderr,"%d byte/s)            \n",(int)thruput);
      fflush(stderr);
    }
  }

  return(0);
}


/*
 * reply  - dummy function, only needed for linking
 */
void reply(int x) { }


/*
 * init - initialize spool and pgp for fetchfile
 */
void init() {
  char
    answer[FLEN],       /* answer string */
    server[FLEN],	/* SAFT server */
    configf[MAXLEN],	/* config file */
    tmp[MAXLEN];        /* temporary string */
  struct stat finfo;    /* information about a file */
  FILE *outf;		/* file to create */

  *server=0;
  sprintf(configf,"%s/config",userconfig);
  umask(~S_IRWXU);
  
  printf("\nThis is the init routine for %s.\n",prg);
  printf("It will create the necessary pgp files and the spool directory.\n");
  printf("You can press Ctrl-C at any time to stop this procedure.\n\n");
  snprintf(MAXS(userspool),SPOOL"/%s",pwe->pw_name);
  if (stat(userspool,&finfo)<0 || !(finfo.st_mode&S_IFDIR)) {
    printf("User spool %s does not exist.\n",userspool);
    snprintf(MAXS(userspool),"%s/.sfspool",pwe->pw_dir);
    printf("May I create local spool %s? ",userspool);
    fgetl(answer,stdin);
    if (*answer!='y' && *answer!='Y') {
      errno=0;
      message(prg,'F',"cannot continue with answer 'no'.");
    }
    unlink(userspool);
    if (mkdir(userspool,S_IRWXU)<0) {
      sprintf(tmp,"cannot create directory %s",userspool);
      message(prg,'F',tmp);
    }
  }

  if (stat(userconfig,&finfo)<0) {
    if (mkdir(userconfig,S_IRWXU)<0) {
      sprintf(tmp,"cannot create config directory %s",userconfig);
      message(prg,'F',tmp);
    }
  }

  if (chdir(userconfig)<0) {
    sprintf(tmp,"cannot change to directory %s",userconfig);
    message(prg,'F',tmp);
  }

  if (!(outf=rfopen(configf,"a"))) {
    snprintf(MAXS(tmp),"cannot open %s",configf);
    message(prg,'F',tmp);
  }
  printf("What is the address of your SAFT server where you want to "
	 "poll your files?\n");
  while (getpromptline(MAXS(server))==NULL || *server=='\n' || *server==0)
    printf("Illegal address! Please repeat: ");
  fprintf(outf,"\n# The address of your default SAFT-server for fetchfile\n");
  fprintf(outf,"server = %s\n",server);
  fclose(outf);
      
  if (stat("public.pgp",&finfo)==0 || stat("private.pgp",&finfo)==0) {
    printf("There are already pgp files in your config directory %s\n",userconfig);
    printf("Overwrite them? ");
    fgetl(answer,stdin);
    if (*answer!='y' && *answer!='Y') {
      errno=0;
      message(prg,'F',"cannot continue with answer 'no'.");
    }
  }
  
  printf("\npgp will now be started to create a new sendfile-only key pair.\n");
  printf("When asked, you can enter your normal e-mail address.\n\n");
  printf("YOU MUST ENTER AN EMPTY PASS PHRASE, ");
  printf("OR FETCHFILE WILL NOT WORK!\n\n");
  if (system("PGPPATH='.' "
	     " pgp -kg +pubring=public.pgp +secring=private.pgp 1024"))
    message(prg,'F',"pgp call failed");

  printf("\nYou should now send %s/public.pgp to\n",userconfig);
  printf("root@%s, because this file has to be stored in your\n",server);
  printf("sendfile spool on %s\n",server);
  printf("For example you can type:\n");
  printf("sendfile -c 'this is my SAFT/fetchfile public key' \\\n");
  printf("  %s/public.pgp root@%s\n\n",userconfig,server);
  printf("If you want to change your default SAFT server, then edit\n%s\n\n",
	 configf);
  
  exit(0);
}


/*
 * sendfiled_test - test local sendfiled
 * 
 * INPUT: user - user name
 * 
 * RETURN: 0 if ok, -1 if failed
 */
int sendfiled_test(const char *user) {
  int sockfd;		/* socket file descriptor */
  char line[MAXLEN];	/* one line of text */
  
  /* test the local sendfiled */
  if (verbose) printf("testing local SAFT server:\n");
  sockfd=open_connection("127.0.0.1",SAFT);
  sock_getline(sockfd,line);
  
  /* no local server? */
  if (!str_beq(line,"220 ") || !strstr(line,"SAFT")) return(-1);
   
  /* test if you can receive messages */
  snprintf(MAXS(line),"FROM %s",user);
  sock_putline(sockfd,line);
  sock_getline(sockfd,line);
  if (!str_beq(line,"200 ")) return(-1);
  snprintf(MAXS(line),"TO %s",user);
  sock_putline(sockfd,line);
  sock_getline(sockfd,line);
  if (!str_beq(line,"200 ")) return(-1);
  sock_putline(sockfd,"FILE test");
  sock_getline(sockfd,line);
  if (!str_beq(line,"200 ")) return(-1);
  sock_putline(sockfd,"QUIT");
  sock_getline(sockfd,line);
  close(sockfd);
  return(0);
}


/*
 * usage - print short help usage text
 */
int usage() {
  fprintf(stderr,"\n");
  fprintf(stderr,"usage: %s [OPTIONS]\n",prg);
  fprintf(stderr,"   or: %s [OPTIONS] file [...]\n",prg);
  fprintf(stderr,"   or: %s [OPTIONS] -n file-number [...]\n",prg);
  fprintf(stderr,"options: -l                only list files\n");
  fprintf(stderr,"         -a                fetch all files\n");
  fprintf(stderr,"         -k                keep file on SAFT-server after receiving\n");
  fprintf(stderr,"         -d                delete file on SAFT-server without receiving\n");
  fprintf(stderr,"         -n                fetch file number instead of file name\n"); 
  fprintf(stderr,"         -P                pipe file directly to stdout\n");
  fprintf(stderr,"         -r                call receive automatically\n");
  fprintf(stderr,"         -i                ignore testing if file has already been fetched\n");
  fprintf(stderr,"         -q                quiet mode 1: no information messages\n");
  fprintf(stderr,"         -Q                quiet mode 2: no information or transfer messages\n");
  fprintf(stderr,"         -v                verbose output\n");
  fprintf(stderr,"         -V                show version information\n");
  fprintf(stderr,"         -I                initalize fetchfile (pgp and spool)\n");
  fprintf(stderr,"         -f user           fetch only files from this user\n");
  fprintf(stderr,"         -s [user@]server  specify alternate SAFT-server and user name\n");
  fprintf(stderr,"         -Cw=conffile      send config file to server\n");
  fprintf(stderr,"         -Cr=conffile      read config file from server\n");
  fprintf(stderr,"          (conffile is \"config\" or \"restrictions\", see: man sendfile)\n");
  fprintf(stderr,"examples: %s -l\n",prg);
  fprintf(stderr,"          %s -a\n",prg);
  fprintf(stderr,"          %s hoppel.gif\n\n",prg);
  return(2);
}


syntax highlighted by Code2HTML, v. 0.9.1