/*
 * File:        sendfile.c
 *
 * Author:      Ulli Horlacher (framstag@rus.uni-stuttgart.de)
 *
 * Contrib.:	Rainer Bawidamann (widi@sol.wohnheim.uni-ulm.de)
 * 		Martin Buck (Martin-2.Buck@student.uni-ulm.de)
 * 		Heiko Schlichting (heiko@fu-berlin.de)
 * 		Christoph 'GNUish' Goern (goern@janus.beuel.rhein.de)
 * 		Stefan Scholl (stesch@parsec.inka.de)
 * 		Michael Neumayer (eumel@42.org)
 *
 * History:
 *
 * 1995-08-11	Framstag	initial version
 * 1995-08-12	Framstag	elm alias support
 * 1995-09-10	Framstag	added delete and resend function
 * 1996-02-06	Framstag	added ATTR EXE
 * 1996-02-07	Framstag	check for already compressed files
 * 1996-02-20	Framstag	follow forward address if given
 * 1996-02-21	widi		better Solaris-2 support
 * 1996-02-22	Framstag	added bouncing (forwarding) of files
 * 1995-02-23	mbuck		bug fixes for getting $HOME
 * 1995-02-27	Framstag	added quiet options
 * 1995-03-08	Framstag	catch up signals in cleanup()
 * 1995-03-17	Framstag	set umask (for tmp-files)
 * 1995-03-23	Framstag	added percentage output
 * 1995-03-24	Framstag	$TMPDIR replaces $HOME for tmp-files
 * 1996-03-28	Framstag	extended search for support programs
 * 1996-04-02	Framstag	added forward address to COMMENT
 * 1996-04-06	Framstag	changed transaction message format
 *				added overwrite option
 * 1996-04-10	Framstag	better usage text
 * 1996-04-12	Framstag	added pgp support
 * 1996-04-16	Framstag	better pgp signature creation
 * 1996-04-17	Framstag	new error handling for open_connection
 * 1996-04-18	Framstag	verbose mode displays system() commands
 * 				allowed multiple IDs for pgp encryption
 * 1996-04-20	Framstag	added pgp IDEA encryption option
 * 1996-04-24	Framstag	changed bouncing option syntax
 * 1996-05-08	Framstag	fixed bug when bouncing
 * 1996-05-10	Framstag	allowed multiple forwards
 * 1996-05-14	Framstag	moved send_data() to net.c
 * 1996-05-21	Framstag	fixed bug when sending archive
 * 1996-05-23	Framstag	added check for writeable tmp-files
 * 				added -P option (read from stdin)
 * 1996-08-13	Framstag	fixed wrong "(compressed)" output
 * 1996-09-04	Heiko		some fixes for IRIX
 * 1996-09-11	Heiko		fixed redirection comment bug
 * 1996-11-19	Framstag	fix for broken AIX include-files
 * 1997-01-04	Framstag	moved check_forward() to address.c
 * 1997-01-20	Framstag	fixed bug with TEXT=charset attribute
 * 1997-01-20	GNUish		modified to move to gnu-style
 * 1997-01-25	Framstag	added -X option (extended headers)
 * 				better usage text
 * 				better pgp parsing
 * 1997-02-01	Framstag	set default quiet mode 1 on dump ttys
 * 1997-02-14	GNUish		added long options
 * 1997-02-23	Framstag	modified str_* function names
 *   				extended with TYPE=MIME
 * 1997-02-24	Framstag	sprintf() -> snprintf()
 * 1997-03-24	stesch		fixed buffering bug with -X option
 * 1997-05-15	Framstag	added -c option usage text
 * 1997-05-16	Framstag	added file type guessing
 * 1997-05-17	Framstag	better file type guessing
 * 1997-06-04	Framstag	added SF_TMPDIR 
 * 1997-06-15	Framstag	added -T option for file reading test
 *                              added new line output in cleanexit()
 * 1997-06-17	Framstag	added packet_size config option
 * 1997-06-30	Framstag	better file type guessing
 * 1997-07-04	Framstag	auto dection of pgp ID for encryption
 * 1997-08-20	Framstag	new syntax with option -a=name-of-archive
 * 1997-08-21	Framstag	better handling of reply code 202
 * 1997-12-05	Eumel		fixed file type handling bug
 * 1997-12-11	Framstag	added bzip2 support
 * 1997-12-14	Framstag	fixed bug when sending to /dev/null
 * 1997-12-15	Framstag	added link speed test for auto-compression
 * 1997-12-19	Framstag	added -W option
 * 1998-01-05	Framstag	reactivated outgoing logging
 * 1998-01-17	Framstag	check SAFT-URL for alternative tcp port
 * 1998-01-22	Framstag	check compression method when bouncing
 * 1998-02-27	Framstag	fixed small bug in guess_ftype
 * 				more verbose transfer statistics
 * 1998-03-01	Framstag	changed /dev/null testing recipient to :NULL:
 * 1998-03-07	Framstag	better detection of non-interactive mode
 * 				added -i option for more transfer information
 * 1998-03-08	Framstag	fixed outlog deletion bug
 * 1998-03-16	Framstag	fixed error loop bug in cleanup()
 * 1998-03-20	Framstag	fixed archive+overwrite bug
 * 1998-08-21	Framstag	added maximum thruput option
 * 1998-09-23	Framstag	continue sending on tar errors
 * 1998-11-05	Framstag	do not compress files less than 1 KB
 * 1998-11-12	Framstag	print "spooled" information with size
 *  				fixed spool+archive+overwrite bug
 * 1999-08-03	Framstag	gzip is default compression, back again
 * 2001-01-10	Framstag	fopen() ==> rfopen()
 * 2001-02-04	Framstag	added secure mktmpdir()
 *
 * The sendfile client of the sendfile package.
 * Sends one or more files to the sendfiled of the destination system.
 *
 * Copyright © 1995-2001 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 <ctype.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 and file IO extensions */
#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 */

#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);
#endif

#ifndef AIX3
  #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();

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

/* delete tmp-file and send LOG command to local server */
void cleanup();

/* encrypt a file with pgp */
void pgp_encrypt(int, char *, char *);

/* create detached pgp signature file and send SIGN header command */
void pgp_sign(const char *, const char *, int);

/* create and open outgoing spool header file */
FILE *outspool(const char *, const char *, char *);

/* start local spool daemon for outgoing files */
void start_spooldaemon(char *);

/* create temporary user outgoing log file */
void outlog(char *, char *, char *, char *);

/* forward a file with complete header from stdin */
void forward(char *, float);

/* read a header line from stdin */
void get_header(const char *, char *);

/* guess file type */
char guess_ftype(const char *, char *);

/* check if link to host is fast enough */
int linkspeed(const char *, int, char **);

/* note the link speed for later processing */
void notespeed(const char *, unsigned long, float);

/* list files in outgoing spool */
int list_spool();

/* print information about spooled file */
void spooled_info(const char *, const char *, int);


/* global variables */
int 
  pgppass=0,		/* flag if the pgp password is set as an env variable */
  verbose=0,		/* flag for verbose mode */
  client=1,		/* flag to determine client or server */
  quiet=0,		/* quiet mode flag */
  test=0,		/* flag for file reading testing (send to /dev/null) */
  outlogging=0,		/* flag logging outgoing files */
  packet_size=0;	/* size of a packet in bytes */
char 
  *prg,			/* name of the game */
  *pgpvm,		/* pgp verbose mode string */
  *tmpdir,		/* directory for temporary files */
  *dontcompress[99],	/* list of file extensions which are not compressible */
  pw_name[FLEN],	/* own user name */
  localhost[FLEN], 	/* name of the local host */
  outlogtmp[MAXLEN],	/* user temporary outgoing log file */
  userspool[MAXLEN],	/* user spool directory */
  tar_bin[MAXLEN],	/* the tar binary */
  gzip_bin[MAXLEN],	/* the gzip binary */
  bzip2_bin[MAXLEN],	/* the bzip2 binary */
  zprg[MAXLEN],		/* the compress programm (either gzip or bzip2) */
  pgp_bin[MAXLEN],	/* the pgp binary */
  stdintmp[MAXLEN],	/* name of stdin temporary file */
  tartmp[MAXLEN],	/* name of tar temporary file */
  ziptmp[MAXLEN],	/* name of gzipped temporary file */
  texttmp[MAXLEN],	/* name of text temporary file in NVT telnet format */
  pgptmp[MAXLEN];	/* name of pgp temporary file */


int main(int argc, char *argv[]) {
  int
    i,n,          	/* simple loop count */
    pid,        	/* current proccess id */
    status,		/* return codes */
    sockfd,     	/* socket file descriptor */
    opt,        	/* option to test for */
    fn,         	/* file number in argv */
    tfn,         	/* total file number sent */
    stdinf,		/* read file from stdin */
    ch,         	/* character to read in from file */
    lanspeed,		/* speed in kB/s which defines what is in the LAN */
    pgpcrypt, 		/* pgp public key or IDEA encryption */
    del,        	/* flag for deleting previous sent files */
    tar,        	/* flag for sending files as tar archive */
    zip,        	/* flag for sending files as zip archive */
    exe,        	/* flag for sending executable */
    pgp,        	/* flag for pgp encoding */
    guess,       	/* flag for file type guessing */
    text,       	/* flag for sending text file */
    mime,	     	/* flag for sending mime file */
    source,     	/* flag for sending source code file */
    spool,  	   	/* flag for do outgoing spooling */
    list,  	   	/* flag for listing files in outgoing spool */
    info,		/* flag for information mode */
    bounce,     	/* flag for bouncing files */
    extended,		/* flag for extended command parsing */
    spooling,		/* flag for spooling allowed */
    overwrite,   	/* flag for overwriting already sent files */
    do_compress;	/* flag for really do compressing */
  unsigned long
    size,       	/* size of file to send */
    orgsize;    	/* original file size uncompressed */
  float
    mtp,		/* maximum thruput limit */
    tsize,		/* total size of all sent files */
    attime,		/* actual transfer time */
    tttime,		/* total transfer time */
    thruput;		/* total net throughput */
  char
    *cp,		/* simple string pointer */
    *argp,		/* argument string pointer */
    *pop,		/* pgp option string pointer */
    *fnp,		/* file name pointer */
    *type,	 	/* type of file transfer */
    *compress,   	/* the compression methode */
    mode,		/* guessed file mode */
    to[2*FLEN],		/* user@host from argv */
    file[FLEN],		/* name of file to send */
    tinfo[FLEN],	/* transfer information */
    sdfn[FLEN],		/* spool data file name */
    shfn[FLEN],		/* spool header file name */
    rto[2*FLEN],	/* user@host from spool header file */
    ftype[FLEN],	/* file type mode */
    archive[FLEN],	/* name of archive file */
    where[FLEN],	/* where is userspool or sendfile.cf */
    aopt[FLEN], 	/* alias options */
    recipient[FLEN], 	/* recipient at serverhost */
    bouncearg[DLEN],   	/* bouncing files argument */
    sizes[FLEN],	/* original and compressed file size */
    user[FLEN], 	/* local user name */
    date[DLEN], 	/* date of file */
    host[FLEN], 	/* name of serverhost */
    pgpopt[FLEN], 	/* options for pgp */
    pgprid[FLEN], 	/* pgp recipient id */
    pgpsign[FLEN], 	/* pgp signature option */
    redirect[MAXLEN], 	/* redirected comment */
    cmd[MAXLEN], 	/* cmd string for system-call */
    line[MAXLEN], 	/* one line of text */
    reply[MAXLEN], 	/* reply from the server */
    tmp[MAXLEN],	/* temporary string */
    comment[MAXLEN],	/* file comment */
    outgoing[MAXLEN],	/* outgoing spool directory */
    oshfn[MAXLEN],	/* outgoing spool header file name */
    osdfn[MAXLEN],	/* outgoing spool data file name */
    filelist[OVERSIZE],	/* list of files for tar command */
    force_compress[MAXLEN]; /* force compress with special programm */
const char
    *cft[]=		/* file types which are not compressible 
			   (output from file(1) command */
    { "compress","zip","zoo","frozen","gif","jpg","jpeg","mpg","mpeg","" };
  FILE
    *shf,		/* spool header file */
    *oshf,		/* outgoing spool header file */
    *inf,		/* input file */
    *outf;		/* output file */
  struct passwd *pwe;	/* password entry */
  struct stat finfo;	/* information about a file */
  char
    utf_name[LEN_UNI],	/* name in UTF-7 format */
    iso_name[LEN_ISO];	/* name in ISO Latin-1 format */
  struct hostlist
    *hls, 		/* host list start */
    *hlp; 		/* host list pointer */
  struct outfilelist
    *oflp;		/* outgoing file list pointer */

  /* HAVE_GETOPTLONG_H is dead code for sendfile! */
#if defined(HAVE_GETOPTLONG_H) 
  static struct option long_options[] = {
    { "version", 0, 0, 'V' },
    { "help", 0, 0, 'h' },
    { "delete", 0, 0, 'd' },
    { "text", 0, 0, 't' },
    { "spool", 0, 0, 'S' },
    { "quiet", 0, 0, 'q' },
    { "real-quiet", 0, 0, 'Q' },
    { "source", 0, 0, 's' },
    { "mime", 0, 0, 'm' },
    { "verbose", 0, 0, 'v' },
    { "uncompressed", 0, 0, 'u' },
    { "overwrite", 0, 0, 'o' },
    { "comment", 1, 0, 'c' },
    { "extended", 1, 0, 'X' },
    { "archive", 1, 0, 'a' },
    { "bounce", 1, 0, 'b' },
    { "stdin", 0, 0, 'P' },
    { "pgp-sign", 0, 0, 'ps' },
    { "pgp-encrypt", 1, 0, 'p' }
  };

  int long_index = 0;
#endif

  mtp=0;
  tfn=0;
  del=0;
  tar=0;
  zip=0;
  exe=0;
  pgp=0;
  text=0;
  spool=0;
  quiet=0;
  mime=0;
  list=0;
  info=0;
  guess=0;
  tsize=0;
  source=0;
  bounce=0;
  sockfd=0;
  stdinf=0;
  tttime=0;
  thruput=0;
  verbose=0;
  extended=0;
  spooling=0;
  pgpcrypt=0;
  lanspeed=100;
  overwrite=0;
  do_compress=0;
  *aopt=0;
  *host=0;
  *date=0;
  *zprg=0;
  *where=0;
  *tinfo=0;
  *comment=0;
  *archive=0;
  *redirect=0;
  *filelist=0;
  *pgprid=0;
  *pgpopt=0;
  *pgpsign=0;
  *force_compress=0;
  dontcompress[0]="";
  type="BINARY";
  compress=S_GZIP;
  oshf=NULL;
  pid=(int)getpid();

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

  /* switch off buffering for STDIN for -X option */
  setvbuf(stdin, NULL, _IONBF, 0);

  if (getenv("PGPPASS")) {
    pgppass=1;
    pgpvm="+verbose=0";
  } else {
    pgppass=0;
    pgpvm="+verbose=1";
  }

  /* scann the command line on options */
#if defined(HAVE_GETOPTLONG_H)
  while ((opt=getopt_long(argc, argv, "Vh?dtmSqQsvuoc:X:a:b:p:",
			  long_options, &long_index)) > 0)
#else
  while ((opt=getopt(argc, argv, "ivVTgtsuqoMQPdSlh?a:c:p:b:m:X:C:W:")) > 0)
#endif
  {
    switch (opt) {
      case ':':
      case 'h':
      case '?': exit(usage());
      case 'd': del=1; break;
      case 'g': guess=1; break;
      case 't': text=1; break;
      case 'T': test=1; break;
      case 'S': spool=1; break;
      case 'l': list=1; break;
      case 'i': info++; break;
      case 'q': quiet++; break;
      case 'Q': quiet=2; break;
      case 'P': stdinf=1; break;
      case 's': source=1; break;
      case 'M': mime=1; break;
      case 'm': mtp=atof(optarg); if (mtp<0) mtp=0; break;
      case 'v': verbose++; break;
      case 'u': compress=""; break;
      case 'C': if (*optarg == '=') strcpy(force_compress,optarg+1);
                else strcpy(force_compress,optarg); break;
      case 'o': overwrite=1; break;
      case 'c': if (*optarg == '=') strcpy(comment,optarg+1); 
                else strcpy(comment,optarg); break;
      case 'a': tar=1; strcpy(archive,optarg); break;
      case 'A': zip=1; strcpy(archive,optarg); break;
      case 'b': bounce=1; strcpy(bouncearg,optarg); break;
      case 'X': extended=1; strcpy(host,optarg); break;
      case 'W': if (*optarg == '=') strcpy(where,optarg+1);
                else strcpy(where,optarg); break;
      case 'p': pgp=1; 
                snprintf(tmp,FLEN-1,"%s\n%s",pgpopt,optarg); 
                strcpy(pgpopt,tmp); 
                break;
      case 'V': message(prg,'I',"version "VERSION" revision "REVISION); exit(0);
    }
  }

  /* too few arguments? */
  if (argc-optind<2 && !extended && !list && !*where) {
    if (argc-optind<1) exit(usage());
    errno=0;
    message(prg,'F',"too few arguments: "
	    "you must specify at least a file name and recipient");
  }

  /* non-interactive usage without a tty? */
  if (!quiet) {
    cp=getenv("TERM");
    if (!cp || !*cp || strstr("tty|dumb",cp)) quiet=1;
  }
  
  /* incompatible options? */

  if (test && spool) {
    errno=0;
    message(prg,'F',"you cannot specify :NULL: and -S option together");
  }
  
  if (extended) {  
    if (strchr(host,'@')) {
      errno=0;
      message(prg,'F',"you must specify only a host name with the -X option");
    }
    if (del||text||spool||stdinf||source||mime||overwrite||tar||bounce||pgp||
	*comment) {
      if (quiet<2) message(prg,'W',"you cannot use any other option with "
 		                   "the extended header option - ignored");
      del=text=spool=stdinf=source=mime=overwrite=tar=bounce=pgp=0;
      *comment=0;
    }
  }

  if (bounce) {
    if (!(str_eq(bouncearg,"k=y") || str_eq(bouncearg,"k=n"))) {
      errno=0;
      message(prg,'F',"wrong bouncing argument");
    }
    if (source||mime||text||tar||del|stdinf)
      if (quiet<2) message(prg,'W',"you cannot use any other option "
 		                   "when bouncing a file - ignored");
    text=source=mime=tar=del=stdinf=0;
    compress="";
  }

  if (del) { 
    if (source||mime||text||tar||overwrite||stdinf||pgp||*comment)
      if (quiet<2) message(prg,'W',"you cannot use any other option "
	                           "when deleting a file - ignored");
    text=source=mime=tar=stdinf=0;
    compress="";
  }

  if (guess) { 
    if (source||mime||text||tar)
      if (quiet<2) message(prg,'W',"you cannot use source, text, mime or "
			           "archive option when guessing the file "
			           "type - ignored");
    text=source=mime=tar=0;
  }

  if (stdinf) { 
    if (tar)
      if (quiet<2) message(prg,'W',"you cannot send stdin as an archive file; "
	                           "-a option will be ignored");
    tar=0;
  }

  if (tar&&zip) { 
    errno=0;
    message(prg,'F',"you cannot use the -a and -A archive options together");
  }

  if (tar||zip) { 
    if (source)
      if (quiet<2) message(prg,'W',"option SOURCE is not allowed when "
	                           "sending in archive format - ignored");
    if (mime)
      if (quiet<2) message(prg,'W',"option MIME is not allowed when "
	                           "sending in archive format - ignored");
    if (text)
      if (quiet<2) message(prg,'W',"option TEXT is not allowed when "
	                           "sending in archive format - ignored");
    text=source=mime=0;
  }
  
  /* correct archive option? */
  if (*archive) {
    if (*archive == '=') {
      strcpy(tmp,archive+1);
      strcpy(archive,tmp);
    } else {
      errno=0;
      message(prg,'F',"you have not specified an archive "
                      "name with -a=name-of-archive");
    }
  }

  /* correct comment? */
  if (*comment && argc-optind>2 && !tar) { 
    errno=0;
    message(prg,'F',"you can only comment a single file");
  }

  /* protect all tmp-files */
  umask(~(S_IRUSR|S_IWUSR));

  /* support programs defaults */
  strcpy(tar_bin,TAR);
  strcpy(pgp_bin,PGP);
  strcpy(gzip_bin,GZIP);
  strcpy(bzip2_bin,BZIP2);

  /* look for environment variables */
  if ((cp=getenv("SF_TAR")))	strcpy(tar_bin,cp);
  if ((cp=getenv("SF_PGP")))	strcpy(pgp_bin,cp);
  if ((cp=getenv("SF_GZIP")))	strcpy(gzip_bin,cp);
  if ((cp=getenv("SF_BZIP2")))	strcpy(bzip2_bin,cp);

  /* do the support programs really exist? */
  if (access(tar_bin,X_OK)<0)	strcpy(tar_bin,"tar");
  if (access(pgp_bin,X_OK)<0)	strcpy(pgp_bin,"pgp");
  if (access(gzip_bin,X_OK)<0)	strcpy(gzip_bin,"gzip");
  if (access(bzip2_bin,X_OK)<0)	strcpy(bzip2_bin,"bzip2");

  /* determine which compress programm to use */
  if (*force_compress) {
    compress="";
    if (strstr(force_compress,"gzip"))  compress=S_GZIP;
    if (strstr(force_compress,"bzip2")) compress=S_BZIP2;
    if (!*compress) {
      snprintf(MAXS(tmp),"unsupported compression program %s",force_compress);
      errno=0;
      message(prg,'F',tmp);
    }
    strcpy(zprg,force_compress);
    
  } else if (*compress) {

#if 0
    snprintf(MAXS(tmp),"%s --help 2>&1",bzip2_bin);
    if ((pp=popen(tmp,"r"))) {
      while (fgetl(line,pp)) {
	if (strstr(line,"usage:")) {
	  strcpy(zprg,bzip2_bin);
	  break;
	}
      }
      pclose(pp);
    }
    
    /* is bzip2 available? */
    if (!spool && whereis(bzip2_bin)) {
      strcpy(zprg,bzip2_bin);
      compress=S_BZIP2;
    }
    if (!*zprg) *bzip2_bin=0;
	
    snprintf(MAXS(tmp),"%s --help 2>&1",gzip_bin);
    if ((pp=popen(tmp,"r"))) {
      while (fgetl(line,pp)) if (strstr(line,"usage:")) break;
      pclose(pp);
    } 
    if (strstr(line,"usage:")) {
      if (!*zprg) strcpy(zprg,gzip_bin);
    } else
      *gzip_bin=0;
#endif
    
    /* is gzip available? */
    if (whereis(gzip_bin) && !*zprg) {
      strcpy(zprg,gzip_bin);
      compress=S_GZIP;
    }
    
    if (!*zprg) {
      compress="";
      if (quiet<2) message(prg,'W',"no compression program found - sending uncompressed");
    } 

  }
  
  /* get the local host name */
  if (gethostname(localhost,FLEN-1)<0) strcpy(localhost,"localhost");

  /* extended header feature = read everything from stdin? */
  if (extended) {
    forward(host,mtp);
    exit(0);
  } 

  /* get own user name, recipient name and host and alias options */
  destination(argc,argv,user,recipient,host,aopt);
  /* printf("recipient: %s\nhost: %s\noptions: %s\n",recipient,host,aopt); */
  
  /* name the local host */
  if (str_eq(host,"127.0.0.1") || str_eq(host,"0")) strcpy(host,localhost);

  if (*aopt) {
    snprintf(MAXS(cmd),"%s %s ",argv[0],aopt);
    for(i=1;i<argc-1;i++) {
      strcat(cmd,"'");
      strcat(cmd,argv[i]);
      strcat(cmd,"' ");
    }
    strcat(cmd,"saft://");
    strcat(cmd,host);
    strcat(cmd,"/");
    strcat(cmd,recipient);
    if (verbose) {
      snprintf(MAXS(tmp),"shell-call: %s\n",cmd);
      message(prg,'I',tmp);
    }
    system(cmd);
    exit(0);
  }
  
  /* recipient /dev/null or :NULL: means test mode: read file(s) without sending */
  if (str_eq(recipient,"/dev/null")) strcpy(recipient,":NULL:");
  if (str_eq(recipient,":NULL:") && str_eq(host,localhost)) test=1;

  /* get the own user name and tmpdir */
  if ((pwe=getpwuid(getuid())) == NULL)
    message(prg,'F',"cannot determine own user name");
  strcpy(pw_name,pwe->pw_name);
  tmpdir=mktmpdir(verbose);

  /* check pgp options */
  if (pgp) { 
    pop=pgpopt;

    /* conventional IDEA encryption? */
    if (str_eq(pop,"\nc")) { 
      pgpcrypt='c';
      compress="";
    } else { /* check other pgp options */
      
      while (*pop) { 
	pop++;

	/* pgp encryption? */
	if (*pop=='e') {
	  pgpcrypt='e';
	  compress="";
	  pop++;
	  snprintf(MAXS(pgprid),"%s@%s",recipient,host);

	  /* is there a recipient id? */
	  if (*pop>'\n') {
	    if (*pop=='=') strcpy(pgprid,++pop);

	    /* cut off any more options */
	    if ((cp=strchr(pgprid,'\n'))) {
	      *cp=0;
	      pop=strchr(pop,'\n');
	    } else
	      *pop=0;

	  }
/*	  
	  if (!*pgprid) {
	    errno=0;
	    message(prg,'F',"you have to specify a pgp recipient-ID "
		            "when encrypting");
	  }
*/
	  continue;
	}

	/* pgp signature? */
	if (*pop=='s') {
	  strcpy(pgpsign," ");
	  pop++;

	  /* is there a signature id? */
	  if (*pop>'\n') {
	    if (*pop=='=') pop++;
	    snprintf(MAXS(pgpsign),"-u '%s",pop);

	    /* cut off any more options */
	    if ((cp=strchr(pgpsign,'\n'))) {
	      *cp=0;
	      pop=strchr(pop,'\n');
	    } else
	      *pop=0;

	    strcat(pgpsign,"'");
	  }

	  continue;
	}

	/* wrong pgp options */
	errno=0;
	snprintf(MAXS(tmp),"wrong pgp option, see 'man %s'",prg);
	message(prg,'F',tmp);

      }
    }
  }

  /* set various file names */
  snprintf(MAXS(userspool),SPOOL"/%s",pw_name);
  snprintf(MAXS(outlogtmp),"%s/.sendfile_%d.log",userspool,pid);
  snprintf(MAXS(tartmp),"%s/sendfile.tar",tmpdir);
  snprintf(MAXS(ziptmp),"%s/sendfile.zip",tmpdir);
  snprintf(MAXS(pgptmp),"%s/sendfile.pgp",tmpdir);
  snprintf(MAXS(texttmp),"%s/sendfile.txt",tmpdir);
  snprintf(MAXS(stdintmp),"%s/sendfile.tmp",tmpdir);

  /* where are the files/directories ? */
  if (*where) {
    if (str_eq(where,"config") || str_eq(where,"sendfile.cf")) {
      if (quiet)
	printf(CONFIG"\n");
      else 
	message(prg,'I',"the global configuration file is: "CONFIG);
    } else if (str_eq(where,"spool")) {
      if (quiet)
	printf(SPOOL"\n");
      else 
	message(prg,'I',"the spool directory is: "SPOOL);
    } else if (str_eq(where,"userspool")) {
      if (quiet)
	printf("%s\n",userspool);
      else {
	snprintf(MAXS(tmp),"the user spool directory is: %s",userspool);
	message(prg,'I',tmp);
      } 
    } else {
      snprintf(MAXS(tmp),"%s is an unknown -W argument",where);
      errno=0;
      message(prg,'E',tmp);
      if (quiet<2) message(prg,'I',"you may specify -W=config, -W=spool, or "
			           "-W=userspool");
    } 
    if (argc-optind<1) exit(0);
  }

  /* check tmp files */
  unlink(tartmp);
  unlink(ziptmp);
  unlink(texttmp);
  unlink(pgptmp);
  unlink(stdintmp);
  if (stat(tartmp,&finfo)==0) {
    snprintf(MAXS(tmp),
	     "tmp-file %s does already exist and cannot be deleted",tartmp);
    message(prg,'F',tmp);
  }
  if (stat(ziptmp,&finfo)==0) {
    snprintf(MAXS(tmp),
	     "tmp-file %s does already exist and cannot be deleted",ziptmp);
    message(prg,'F',tmp);
  }
  if (stat(texttmp,&finfo)==0) {
    snprintf(MAXS(tmp),
	     "tmp-file %s does already exist and cannot be deleted",texttmp);
    message(prg,'F',tmp);
  }
  if (stat(pgptmp,&finfo)==0) {
    snprintf(MAXS(tmp),
	     "tmp-file %s does already exist and cannot be deleted",pgptmp);
    message(prg,'F',tmp);
  }
  if (stat(stdintmp,&finfo)==0) {
    snprintf(MAXS(tmp),
	     "tmp-file %s does already exist and cannot be deleted",stdintmp);
    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;
	}
	if (str_eq(line,"lanspeed")) {
	  lanspeed=atoi(argp);
	  if (lanspeed<0) lanspeed=0;
	  continue;
	}
	if (str_eq(line,"log")) {
	  if (str_eq(argp,"out") || str_eq(argp,"both")) outlogging=1;
	  continue;
	}
	if (str_eq(line,"spooling")) {
	  if (str_eq(argp,"nostart")) spooling=1;
	  if (str_eq(argp,"on"))      spooling=2;
	  continue;
	}
	if (str_eq(line,"dontcompress")) {
	  if (*argp) {		    
	    /* save the whole string in element #0 */
	    if (!(dontcompress[0]=strdup(argp))) 
	      message(prg,'F',"out of memory");
	    /* parse and split the list (tricky, eh? :-) ) */
	    for (n=1,cp=strtok(dontcompress[0],", \t");
		 cp && n<99;
		 n++,cp=strtok(NULL,", \t")) { 
	      /*printf("sfconf: >%s<\n",cp); */
	      dontcompress[n]=cp; 
	    }
	    dontcompress[n]=""; /* last element must terminate the list! */
	  }
	  continue;
	}
      }

    }
    fclose(inf);
  }

  /* spooling allowed? */
  if ((spool || list) && !spooling) {
    errno=0;
    message(prg,'F',"outgoing spooling of files is not allowed on this system");
  }

  /* list files in outgoing spool */
  if (list) exit(list_spool());

  /* set tcp packet length */
  if (packet_size<1) packet_size=PACKET;
  if (verbose && !spool && !del) {
    snprintf(MAXS(tmp),"packet size = %d bytes",packet_size);
    message(prg,'I',tmp);
  }

  /* enable simple interrupt handler */
  signal(SIGTERM,cleanexit);
  signal(SIGABRT,cleanexit);
  signal(SIGQUIT,cleanexit);
  signal(SIGHUP,cleanexit);
  signal(SIGINT,cleanexit);

  /* file as stdin data stream? */
  if (stdinf) {
   
    /* write stdin to tmp-file */
    if (!(outf=rfopen(stdintmp,"w"))) {
      snprintf(MAXS(tmp),"cannot open tmp-file %s",stdintmp);
      message(prg,'F',tmp);
    }
    while ((ch=getchar())!=EOF) putc(ch,outf);
    fclose(outf);

  }

  /* determine the file type */
  type="BINARY";
  if (text)   type="TEXT="CHARSET;
  if (mime)   type="MIME";
  if (source) type="SOURCE";

  /* no testing mode */
  if (!test) {
    
    /* prepare sending or spooling */
    if (!spool) {

      /* look for correct SAFT server and open connection */
      sockfd=saft_connect("file",recipient,user,host,redirect);
      
      /* compression mode wanted? */
      if (*compress) do_compress=1;
      if (do_compress && !*force_compress) 
	do_compress=linkspeed(host,lanspeed,&compress);
      
      /* test if server can handle bzip2 */
      if (do_compress && *zprg && strstr(zprg,"bzip2")) {
	sendcommand(sockfd,"TYPE BINARY COMPRESSED=BZIP2",reply);

	if (!str_beq(reply,"200 ") && *gzip_bin && !*force_compress) {
	  compress=S_GZIP;
	  strcpy(zprg,gzip_bin);
	} else
	  compress=S_BZIP2;
	  
      }
      
    } else /* outgoing spooling */ {
   
      /* does the outgoing spool exist? */
      strcpy(outgoing,SPOOL"/OUTGOING");
      if (stat(outgoing,&finfo)<0 || !S_ISDIR(finfo.st_mode)) {
	snprintf(MAXS(tmp),"spool directory %s does not exist",outgoing);
	message(prg,'F',tmp);
      }

      /* and does it have the correct protection? */
      if (!((finfo.st_mode&S_ISVTX) && (finfo.st_mode&S_IRWXO))) {
	snprintf(MAXS(tmp),
		 "spool directory %s has wrong protection (must have 1777)",
		 outgoing);
	message(prg,'F',tmp);
      }

      /* name the local host */
      if (str_eq(host,"127.0.0.1") || str_eq(host,"0")) strcpy(host,localhost);

    }
  }

  /* bouncing files? */
  if (bounce) {

    /* does the spool directory exist? */
    if (chdir(userspool)<0) {
      snprintf(MAXS(tmp),"cannot access spool directory %s",userspool);
      message(prg,'F',tmp);
    }

    /* main loop over the spool file names */
    for (fn=optind; fn<argc-1; fn++) {
      snprintf(MAXS(sdfn),"%s.d",argv[fn]);
      snprintf(MAXS(shfn),"%s.h",argv[fn]);
      if (info) snprintf(MAXS(tinfo),"#%d/%d: ",fn-optind+1,argc-optind-1);
      
      /* try to open spool header file */
      if (!(shf=rfopen(shfn,"r"))) {
        snprintf(MAXS(tmp),"cannot open spool file #%s",argv[fn]);
	message(prg,'E',tmp);
        continue;
      }

      /* write to outgoing spool? */
      if (spool) {
       
	/* open outgoing spool header file */
	if ((oshf=outspool(pw_name,outgoing,oshfn))==NULL)
	  message(prg,'F',"cannot create outgoing spool file");

	/* TAB as whitespace is needed here, because scanspool insists on it */
	fprintf(oshf,"FROM\t%s\n",user);
	fprintf(oshf,"TO\t%s@%s\n",recipient,host);

      }

      *file=0;
      *comment=0;
      compress="";

      /* scan spool header file and forward it */
      while (fgetl(line,shf)) {
        str_trim(line);

	/* corrupt header line? */
	if (!strchr(line,' ')) continue;

	/* store original from as comment */
	if (str_beq(line,"FROM")) {
	  strcpy(comment,strchr(line,' ')+1);
	  if ((cp=strchr(comment,' '))) {
	    *cp=0;
	    snprintf(MAXS(tmp),"%s+ACA-(%s)",comment,cp+1);
	    strcpy(comment,tmp);
	  }
	  continue;
	}

	/* store size and file name for later processing */
	if (str_beq(line,"FILE")) utf2iso(0,NULL,file,NULL,line+5);
	
	/* check if compression method is supported */
	if (str_beq(line,"TYPE") && strstr(line," COMPRESSED")) {
	  for(;;) {
	    type="";
	    
	    /* bzip2 ok? */
	    if (strstr(line," COMPRESSED=BZIP2")) {
	      type=S_BZIP2;
	      compress=S_BZIP2;
	      sendcommand(sockfd,line,reply);
	      if (str_beq(reply,"200 ")) break;
	      cp=strrchr(line,'=');
	      *cp=0;
	    }
	    
	    /* gzip ok? */
	    if (!*type) type=S_GZIP;
	    compress=S_GZIP;
	    sendcommand(sockfd,line,reply);
	    if (str_beq(reply,"200 ")) break;
	    
	    /* no compression ok? */
	    cp=strrchr(line,' ');
	    *cp=0;
	    compress="";
	    sendcommand(sockfd,line,reply);
	    if (str_beq(reply,"200 ")) break;
	    
	    /* error! */
	    snprintf(MAXS(tmp),"cannot send %s : %s",file,reply+4);
	    errno=0;
	    message(prg,'E',tmp);
	    fclose(inf);
	    break;
	  } 
	  
	  /* compression methode accepted */
	  if (!str_eq(type,compress)) {
	  
	    /* recompress spool file */
	    if (str_eq(type,S_BZIP2)) {
	      if (str_eq(compress,S_GZIP))
		snprintf(MAXS(cmd),"%s -d <%s|%s>%s",BZIP2,sdfn,GZIP,ziptmp);
	      else
		snprintf(MAXS(cmd),"%s -d < %s > %s",BZIP2,sdfn,ziptmp);
	    } else
	      snprintf(MAXS(cmd),"%s -dc %s > %s",GZIP,sdfn,ziptmp);
	    
	    /* execute shell-command and close spool header file on error */
	    if (system(cmd)) {
	      snprintf(MAXS(tmp),"cannot recompress spool file #%s",argv[fn]);
	      message(prg,'E',tmp);
	      fclose(inf);
	      break;
	    }
	
	  }
	  continue; /* read next line from spool header file */
	}
	
	/* store size for later processing */
	if (str_beq(line,"SIZE ")) {
	  sscanf(line,"SIZE %ld %ld",&size,&orgsize);
	  strcpy(sizes,line+5);
	}

	/* is there already a comment line? */
	if (str_beq(line,"COMMENT")) {
	  if (*redirect) 
	    snprintf(MAXS(line),"%s+AA0ACg-%s",comment,redirect);
	  else {
	    snprintf(MAXS(tmp),
		     "%s+AA0ACg-forward+ACA-from+ACA-%s",line,comment);
	    strcpy(line,tmp);
	  }
	  if (spool)
	    fprintf(oshf,"%s\n",line);
	  else 
	    sendcommand(sockfd,line,NULL);
	  *comment=0;
	  continue;
	}

	/* all other header lines */
	if (spool)
	  fprintf(oshf,"%s\n",line);
	else
	  sendheader(sockfd,line);
      
      }

      /* if file has been already closed there is an error (see above) */
      if (fclose(shf)) {
	if (spool) fclose(oshf);
	continue;
      }

      /* send comment if not already done */
      if (*comment) {
        iso2utf(tmp,"forward from ");
	snprintf(MAXS(line),"COMMENT %s%s",tmp,comment);
	if (*redirect) {
	  snprintf(MAXS(tmp),"\r\n%s",redirect);
	  iso2utf(comment,tmp);
	  strcat(line,comment);
	}
	if (spool)
	  fprintf(oshf,"%s\n",line);
	else 
	  sendcommand(sockfd,line,NULL);
      }

      /* check the file size */
      if (stat(ziptmp,&finfo)==0) {
	size=finfo.st_size;
	snprintf(MAXS(sizes),"%ld %ld",size,orgsize);
	snprintf(MAXS(line),"SIZE %s",sizes);
	sendcommand(sockfd,line,NULL);
      } else {
	if (stat(sdfn,&finfo)<0 || size!=finfo.st_size) {
	  snprintf(MAXS(tmp),
		   "spool file #%s has wrong size count - ignored",argv[fn]);
	  errno=0;
	  message(prg,'E',tmp);
	  if (spool) fclose(oshf);
	  continue;
	}
      }

      /* copy spool file to outgoing */
      if (spool) {
        fclose(oshf);
	strcpy(osdfn,oshfn);
	osdfn[strlen(osdfn)-1]='d';
	if (fcopy(sdfn,osdfn,S_IRUSR|S_IWUSR)<0) {
	  unlink(oshfn);
	  unlink(osdfn);
	} else {
	  if (str_eq(bouncearg,"k=n")) {
	    unlink(shfn);
	    unlink(sdfn);
	  }
	  strcpy(tmp,oshfn);
	  oshfn[strlen(oshfn)-1]='h';
	  rename(tmp,oshfn);
	  spooled_info(iso_name,osdfn,do_compress);
	}

      } else /* forward the spool data file */ {
				       
	if (stat(ziptmp,&finfo)==0) {
	  status=send_data(sockfd,size,ziptmp,tinfo,file,"",mtp,&attime);
	  unlink(ziptmp);
	} else
	  status=send_data(sockfd,size,sdfn,tinfo,file,"",mtp,&attime);
	
	/* summarize transfer statistics */
	if (attime) {
	  tfn++;
	  tsize+=size;
	  tttime+=attime;
	}

	if (status==0) {
	  
	  /* log data */
	  outlog(recipient,host,file,sizes);
	  notespeed(host,size,attime);
	  
	  /* if not keep file delete the spool files */
	  if (str_eq(bouncearg,"k=n")) {
	    unlink(shfn);
	    unlink(sdfn);
	  }

	}
      }
    }
    
  } else if (tar||zip) { /* sending tar or zip archive */
   
    /* translate the archive name to UTF-7 */
    iso2utf7(utf_name,archive,0);

    /* collect all files */
    for (n=optind; n<argc-1; n++) {
      strcat(filelist," ");
      strcat(filelist,argv[n]);
    }

    /* do the tar, man! :-) */
    if (verbose)
      snprintf(MAXS(cmd),"trap \"rm -f %s 2>/dev/null\" 1 2 3;%s cvf %s %s",
	       tartmp,tar_bin,tartmp,filelist);
    else {
      if (!quiet) printf("making archive...\r");
      fflush(stdout);
      snprintf(MAXS(cmd),"trap \"rm -f %s 2>/dev/null\" 1 2 3;%s cf %s %s",
	       tartmp,tar_bin,tartmp,filelist);
    }
    if (verbose) printf("%s\n",strchr(cmd,';')+1);
    if (system(cmd)) {
      message(prg,'E',"no complete archive file");
      if (!quiet) {
	printf("Continue? ");
	fgetl(tmp,stdin);
	if (toupper(tmp[0])!='Y') exit(1);
      }
    }

    /* get the file name and size */
    strcpy(file,tartmp);
    if (stat(file,&finfo)<0) message(prg,'F',"cannot access tmp file");
    orgsize=finfo.st_size;
    size=orgsize;

    /* compression mode wanted? */
    if (*compress) do_compress=1;

    /* compressed mode? */
    if (do_compress) {
     
      /* compress tar-archive */
      if (!quiet) printf("compressing...       \r");
      fflush(stdout);
      snprintf(MAXS(cmd),"trap \"rm -f %s %s 2>/dev/null\" 1 2 3;%s < %s > %s",
	       tartmp,ziptmp,zprg,tartmp,ziptmp);
      if (verbose) {
	snprintf(MAXS(tmp),"shell-call: %s",strchr(cmd,';')+1);
	message(prg,'I',tmp);
      }
      if (system(cmd)) message(prg,'F',"cannot compress archive file");

      strcpy(file,ziptmp);
    }

    /* pgp encryption? */
    if (pgpcrypt) pgp_encrypt(pgpcrypt,pgprid,file);

    /* pgp signature to add? */
    if (*pgpsign) pgp_sign(pgpsign,file,sockfd);

    /* get the file size */
    if (stat(file,&finfo)<0) message(prg,'F',"cannot access tmp file");
    size=finfo.st_size;
    snprintf(MAXS(sizes),"%ld %ld",size,orgsize);

    /* write to outgoing spool? */
    if (spool) {
     
      /* overwrite archive file? */
      if (overwrite && (hls=scanoutspool(pw_name))) {
	 
	/* create correct to-string */
	if (strchr(argv[argc-1],'*'))
	  snprintf(MAXS(to),"%s",argv[argc-1]);
	else
	  snprintf(MAXS(to),"%s@%s",recipient,host);
	  
	/* search for file in outgoing spool */
	for (hlp=hls; hlp; hlp=hlp->next) {
	  for (oflp=hlp->flist; oflp; oflp=oflp->next) {
	   
	    /* matching file name? */
	    if (simplematch(oflp->fname,utf_name,0)) {
		
	      /* matching recipient? */
	      snprintf(MAXS(rto),"%s@%s",oflp->to,hlp->host);
	      if (simplematch(rto,to,0)) {
		unlink(oflp->oshfn);
		oflp->oshfn[strlen(oflp->oshfn)-1]='d';
		unlink(oflp->oshfn);
	      }
	    
	    }
	  }
	}
      }
	
      /* open outgoing spool header file */
      if ((oshf=outspool(pw_name,outgoing,oshfn))==NULL)
	message(prg,'F',"cannot create outgoing spool file");

      /* TAB as whitespace is needed here, because scanspool insists on it */
      fprintf(oshf,"FROM\t%s\n",user);
      fprintf(oshf,"TO\t%s@%s\n",recipient,host);
      fprintf(oshf,"FILE\t%s\n",utf_name);
      if (*compress) {
	if (str_eq(compress,S_GZIP))
	  fprintf(oshf,"TYPE\tBINARY COMPRESSED\n");
	else
	  fprintf(oshf,"TYPE\tBINARY COMPRESSED=%s\n",compress);
      } else if (pgpcrypt) {
	fprintf(oshf,"TYPE\tBINARY CRYPTED\n");
      } else
	fprintf(oshf,"TYPE\tBINARY\n");
      fprintf(oshf,"SIZE\t%s\n",sizes);
      fprintf(oshf,"ATTR\tTAR\n");
      
    } else { /* send header lines */

      if (overwrite) {
	snprintf(MAXS(tmp),"FILE %s",utf_name);
	sendcommand(sockfd,tmp,reply);
	if (str_beq(reply,"200 ")) sendcommand(sockfd,"DEL",reply);
      }
      snprintf(MAXS(tmp),"FILE %s",utf_name);
      sendcommand(sockfd,tmp,reply);
      if (!test && !str_beq(reply,"200 ") && quiet<2) 
	message(prg,'W',"remote site does not support file names");
      if (*compress) {
	if (str_eq(compress,S_GZIP))
	  snprintf(MAXS(tmp),"TYPE BINARY COMPRESSED");
	else
	  snprintf(MAXS(tmp),"TYPE BINARY COMPRESSED=%s",compress);
	sendcommand(sockfd,tmp,reply);
	if (!test && !str_beq(reply,"200 ") && quiet<2) {
	  errno=0;
	  message(prg,'F',"remote site does not support compressed files");
	}
      } else if (pgpcrypt) {
	sendcommand(sockfd,"TYPE BINARY CRYPTED",reply);
	if (!test && !str_beq(reply,"200 ") && quiet<2) {
	  errno=0;
	  message(prg,'F',"remote site does not support encrypted files");
	}
      } else {
	sendcommand(sockfd,"TYPE BINARY",reply);
	if (!test && !str_beq(reply,"200 ") && quiet<2) 
	  message(prg,'W',"remote site does not support binary files");
      }
      snprintf(MAXS(tmp),"SIZE %s",sizes);
      sendheader(sockfd,tmp);
      sendcommand(sockfd,"ATTR TAR",reply);
      if (!test && !str_beq(reply,"200 ") && quiet<2) {
	errno=0;
	message(prg,'F',"remote site does not support archive file type");
      }
    }

    /* comment to send? */
    if (*comment || *redirect) {
      *line=0;
      if (*comment) strcat(line,comment);
      if (*redirect) {
        if (*line) {
	  snprintf(MAXS(tmp),"%s\r\n%s",line,redirect);
	  strcpy(line,tmp);
	} else
	  strcpy(line,redirect);
      }
      iso2utf(tmp,line);
      if (spool)
	fprintf(oshf,"COMMENT\t%s\n",tmp);
      else {
        snprintf(MAXS(line),"COMMENT %s",tmp);
	sendcommand(sockfd,line,NULL);
      }
    }

    /* send the file data */
    if (spool) {
      fclose(oshf);
      strcpy(osdfn,oshfn);
      osdfn[strlen(osdfn)-1]='d';
      if (fcopy(file,osdfn,S_IRUSR|S_IWUSR)<0) {
        unlink(oshfn);
	unlink(osdfn);
      } else {
        strcpy(tmp,oshfn);
	oshfn[strlen(oshfn)-1]='h';
	rename(tmp,oshfn);
	spooled_info(archive,osdfn,do_compress);
      }
    } else {
      strcpy(tmp,"archive");
      if (*compress) strcat(tmp," compressed");
      if (pgpcrypt)  strcat(tmp," crypted");
      if (send_data(sockfd,size,file,"",archive,tmp,mtp,&attime)==0) {
	
	/* summarize transfer statistics */
	if (attime) {
	  tfn++;
	  tsize+=size;
	  tttime+=attime;
	}
	
	/* do some logging */
	outlog(recipient,host,utf_name,sizes);
	notespeed(host,size,attime);
	
      }
    }

  } else /* sending or deleting single files */ {
   
    /* main loop over the file names */
    for (fn=optind; fn<argc-1; fn++) {
      if (info) snprintf(MAXS(tinfo),"#%d/%d: ",fn-optind+1,argc-optind-1);
     
      /* file from stdin? */
      if (stdinf) {
       
	/* get real file name and transmission file name */
	strcpy(file,stdintmp);
	strcpy(iso_name,argv[fn]);
	stdinf=0;
      } else /* normal file */ {
       
	/* get real file name */
	strcpy(file,argv[fn]);

	/* get the file name without path */
	fnp=strrchr(file,'/');
	if (!fnp) fnp=file; else fnp++;

	/* get transmission file name */
	strcpy(iso_name,fnp);

      }

      /* translate the file name into UTF-7 */
      iso2utf7(utf_name,iso_name,0);

      /* write to outgoing spool? */
      if (spool) {
       
	/* delete file? */
	if ((del || overwrite) && (hls=scanoutspool(pw_name))) {
	 
	  /* create correct to-string */
	  if (strchr(argv[argc-1],'*'))
	    snprintf(MAXS(to),"%s",argv[argc-1]);
	  else
	    snprintf(MAXS(to),"%s@%s",recipient,host);
	  
	  /* search for file in outgoing spool */
	  for (hlp=hls; hlp; hlp=hlp->next) {
	    for (oflp=hlp->flist; oflp; oflp=oflp->next) {
	   
	      /* matching file name? */
	      if (simplematch(oflp->fname,utf_name,0)) {
		
		/* matching recipient? */
		snprintf(MAXS(rto),"%s@%s",oflp->to,hlp->host);
		if (simplematch(rto,to,0)) {
		  unlink(oflp->oshfn);
		  oflp->oshfn[strlen(oflp->oshfn)-1]='d';
		  unlink(oflp->oshfn);
		  if (del) {
		    del=2;
		    utf2iso(0,NULL,file,NULL,oflp->fname);
		    snprintf(MAXS(tmp),
			     "deleted from outgoing spool: '%s' for %s ",
			     file,rto);
		    if (quiet<2) message(prg,'I',tmp);
		  }
		}
		
	      }
	    }
	  }
	}

	if (del) continue;
	
	/* open outgoing spool header file */
	if ((oshf=outspool(pw_name,outgoing,oshfn))==NULL)
	  message(prg,'F',"cannot create outgoing spool file");

	fprintf(oshf,"FROM\t%s\n",user);
	fprintf(oshf,"TO\t%s@%s\n",recipient,host);
	fprintf(oshf,"FILE\t%s\n",utf_name);
	/*if (del || overwrite) fprintf(oshf,"DEL\n");*/
	
      } else /* send header lines */ {
       
	/* send file name */
	snprintf(MAXS(tmp),"FILE %s",utf_name);
	sendcommand(sockfd,tmp,reply);
	if (!test && !str_beq(reply,"200 ") && quiet<2) 
	  message(prg,'W',"remote site does not support file names");

	/* delete file? */
	if (overwrite) sendcommand(sockfd,"DEL",NULL);
	if (del) {
	  if (sendheader(sockfd,"DEL")) {
	    if (quiet<2) message(prg,'W',"remote site cannot delete files");
	  } else {
	    snprintf(MAXS(tmp),"'%s' deleted",iso_name);
	    if (quiet<2) message(prg,'I',tmp);
	  }
	  continue;
	}

      }

      /* is the file readable? */
      if (stat(file,&finfo)<0) {
        snprintf(MAXS(tmp),"cannot access '%s'",file);
	message(prg,'E',tmp);
	if (spool) {
	  fclose(oshf);
	  unlink(oshfn);
	}
	continue;
      }

      /* is it a regular file? */
      if (!S_ISREG(finfo.st_mode)) {
	errno=0;
        snprintf(MAXS(tmp),"%s is not a regular file, skipping",file);
	message(prg,'E',tmp);
	if (spool) {
	  fclose(oshf);
	  unlink(oshfn);
	}
	continue;
      }

      /* is it a executable? */
      if (finfo.st_mode&S_IXUSR) exe=1; else exe=0;
      
      /* no file mode set? guess the file type */
      mode=guess_ftype(file,ftype);
      if (!guess) mode=0;

      /* get the original file size */
      strftime(date,20,"%Y-%m-%d %H:%M:%S",gmtime(&finfo.st_mtime));
#ifdef HPUX
      /* dirty hack around HPUX strftime bug */
      date[17]=0;
      strcat(date,"00");
#endif

      /* text or source mode? */
      if (text || source || mode=='t' || mode=='s') {

	/* open and test file to send and open tmp-file */
	inf=rfopen(file,"r");
	outf=rfopen(texttmp,"w");
	if (!inf) {
	  snprintf(MAXS(tmp),"cannot open '%s'",file);
	  message(prg,'E',tmp);
	  if (spool) {
	    fclose(oshf);
	    unlink(oshfn);
	  }
	  continue;
	}
	if (!outf) message(prg,'F',"cannot open tmp-file");

	/* convert file to NVT format */
	do {
	  ch=fgetc(inf);
	  if (ch!=EOF) {
	    if (ch=='\n')
	      fprintf(outf,"\r\n");
	    else
	      fputc(ch,outf);
	  }
	} while (!feof(inf));

	fclose(inf);
	fclose(outf);
	strcpy(file,texttmp);
      }

      /* get the original file size */
      stat(file,&finfo);
      orgsize=finfo.st_size;

      /* compression mode wanted? */
      if (*compress) do_compress=1;

      /* check if file is compressible */
      if (do_compress && !*force_compress) {
	
	/* do not compress too small files */
	if (finfo.st_size < 1024) do_compress=0;
	
	/* determine file type */
	/* loop over all non-compressible file type strings */
	for (n=0;do_compress && *cft[n];n++) {

	  /* is this file a not compressible one? */
	  snprintf(MAXS(tmp),"*%s*",cft[n]);
	  if (simplematch(ftype,tmp,1)) do_compress=0;

	}
      }

      /* compressed mode? */
      if (do_compress) {
       
	/* compress tmp-file */
	if (!quiet) printf("compressing...       \r");
	fflush(stdout);
	snprintf(MAXS(cmd),
		 "trap \"rm -f %s 2>/dev/null\" 1 2 3;%s < '%s' > %s",
		 ziptmp,zprg,file,ziptmp);
	if (verbose) {
	  snprintf(MAXS(tmp),"shell-call: %s",strchr(cmd,';')+1);
	  message(prg,'I',tmp);
	}
	if (system(cmd)) {
	  snprintf(MAXS(tmp),"cannot compress %s",file);
	  message(prg,'E',tmp);
	  if (spool) {
	    fclose(oshf);
	    unlink(oshfn);
	  }
	  continue;
	}
	strcpy(file,ziptmp);

      } else
	size=orgsize;

      /* pgp encryption? */
      if (pgpcrypt) pgp_encrypt(pgpcrypt,pgprid,file);

      /* pgp signature to add? */
      if (*pgpsign) pgp_sign(pgpsign,file,sockfd);

      /* get the file size */
      if (stat(file,&finfo)<0) message(prg,'F',"cannot access tmp file");
      size=finfo.st_size;
      snprintf(MAXS(sizes),"%ld %ld",size,orgsize);

      /* determine the file type */
      type="BINARY";
      if (guess) {
	if (mode=='s') type="SOURCE";
	if (mode=='t') type="TEXT="CHARSET;
      }
      else if (mime)   type="MIME";
      else if (source) type="SOURCE";
      else if (text)   type="TEXT="CHARSET;

      /* write to outgoing spool? */
      if (spool) {
       
	if (do_compress) {
	  if (str_eq(compress,S_GZIP))
	    fprintf(oshf,"TYPE\tBINARY COMPRESSED\n");
	  else
	    fprintf(oshf,"TYPE\tBINARY COMPRESSED=%s\n",compress);
	} else if (pgpcrypt)
	  fprintf(oshf,"TYPE\t%s CRYPTED\n",type);
	else
	  fprintf(oshf,"TYPE\t%s\n",type);
	fprintf(oshf,"SIZE\t%s\n",sizes);
	fprintf(oshf,"DATE\t%s\n",date);
	if (exe)
	  fprintf(oshf,"ATTR\tEXE\n");
	else
	  fprintf(oshf,"ATTR\tNONE\n");
      } else /* send the header information */ {
       
	if (do_compress) {
	  if (str_eq(compress,S_GZIP))
	    snprintf(MAXS(line),"TYPE %s COMPRESSED",type);
	  else
	    snprintf(MAXS(line),"TYPE %s COMPRESSED=%s",type,compress);
	} else if (pgpcrypt)
	  snprintf(MAXS(line),"TYPE %s CRYPTED",type);
	else
	  snprintf(MAXS(line),"TYPE %s",type);
	sendcommand(sockfd,line,reply);
	if (!test && !str_beq(reply,"200 ") && quiet<2) {
	  errno=0;
	  snprintf(MAXS(tmp),"remote site does not support file of %s",line);
	  message(prg,'F',tmp);
	}
	snprintf(MAXS(tmp),"SIZE %s",sizes);
	sendheader(sockfd,tmp);
	snprintf(MAXS(tmp),"DATE %s",date);
	if (sendheader(sockfd,tmp) && quiet<2)
	  message(prg,'W',"remote site does not support dates");
	if (exe)
	  sendcommand(sockfd,"ATTR EXE",NULL);
	else
	  sendcommand(sockfd,"ATTR NONE",NULL);
      }

      /* comment to send? */
      if (*comment || *redirect) {
        *line=0;
	if (*comment) strcat(line,comment);
	if (*redirect) {
	  if (*line) {
	    snprintf(MAXS(tmp),"%s\r\n%s",line,redirect);
	    strcpy(line,tmp);
	  } else
	    strcpy(line,redirect);
	}
	iso2utf(tmp,line);
	if (spool)
	  fprintf(oshf,"COMMENT\t%s\n",tmp);
	else {
	  snprintf(MAXS(line),"COMMENT %s",tmp);
	  sendcommand(sockfd,line,NULL);
	}
      }

      /* send the file data */
      if (spool) {
        fclose(oshf);
	strcpy(osdfn,oshfn);
	osdfn[strlen(osdfn)-1]='d';
	if (fcopy(file,osdfn,S_IRUSR|S_IWUSR)<0) {
	  unlink(oshfn);
	  unlink(osdfn);
	} else {
	  strcpy(tmp,oshfn);
	  oshfn[strlen(oshfn)-1]='h';
	  rename(tmp,oshfn);
	  spooled_info(iso_name,osdfn,do_compress);
	}
      } else {
	strcpy(tmp,type);
	if ((cp=strchr(tmp,'='))) *cp=0;
	str_tolower(tmp);
	if (do_compress) strcat(tmp," compressed");
	if (pgpcrypt)    strcat(tmp," crypted");
        if (send_data(sockfd,size,file,tinfo,iso_name,tmp,mtp,&attime)==0) {
	
	  /* summarize transfer statistics */
	  if (attime) {
	    tfn++;
	    tsize+=size;
	    tttime+=attime;
	  }
	
	  /* do some logging */
	  outlog(recipient,host,utf_name,sizes);
	  notespeed(host,size,attime);
	  
	}
      }
    }
  }

  /* start spool daemon or close the connection */
  if (spool) {
    if (del) {
      if (del<2 && quiet<2) 
	message(prg,'W',"no matching files found in outgoing spool");
    } else {
      if (spooling==2) start_spooldaemon(localhost);
    }
  } else {
    sendcommand(sockfd,"QUIT",NULL);
    close(sockfd);
  }
  
  /* print total transfer statistics */
  if (tsize && info && tfn>1 && quiet<2) {
    thruput=tsize*1000/tttime;
   if (tsize/1024>9999)
      snprintf(MAXS(tmp),"%d files sent with %.1f MB",tfn,tsize/1024/1024);
    else if (tsize>9999)
      snprintf(MAXS(tmp),"%d files sent with %.1f kB",tfn,tsize/1024);
    else
      snprintf(MAXS(tmp),"%d files sent with %d byte",tfn,(int)tsize);
    if (thruput>9999)
      snprintf(MAXS(line),"%s at %.1f kB/s",tmp,thruput/1024);
    else
      snprintf(MAXS(line),"%s at %d byte/s",tmp,(int)thruput);
    message("",'I',line);
  }

  
  /* delete tmp-files */
  cleanup();
  exit(0);
}


/*
 * cleanexit  - clean termination routine
 *
 * A very simple interrupt handler
 */
void cleanexit() {
  
  printf("\r\n");
  cleanup();
  exit(0);

}


/*
 * cleanup  - delete tmp files and send LOG command to local server
 */
void cleanup() {
  int 
    sockfd;     	/* socket file descriptor */
  char  
    line[MAXLEN], 	/* one line of text */
    reply[MAXLEN],	/* reply string from server */
    server[FLEN]; 	/* saft server */

  /* ignore all relevant signals */
  signal(SIGTERM,SIG_IGN);
  signal(SIGABRT,SIG_IGN);
  signal(SIGQUIT,SIG_IGN);
  signal(SIGHUP,SIG_IGN);
  signal(SIGINT,SIG_IGN);

  if (verbose<2) verbose=0;
  *reply=0;
  
#ifndef DEBUG
  rmtmpdir(tmpdir);
  
  if (outlogging) {
    
    /* inform local saft server about outgoing log file */
    if (access(outlogtmp,R_OK)==0 && !test &&
	(sockfd=open_connection("127.0.0.1",SAFT))>=0) {
    
      /* check local saft server */
      sock_getline(sockfd,reply);
      if (str_beq(reply,"220 ") && strstr(reply,"SAFT")) {
	
	/* send LOG command */
	snprintf(MAXS(line),"LOG %s %s",pw_name,outlogtmp);
	sock_putline(sockfd,line);
	sock_getline(sockfd,reply);
	str_trim(reply);
	
	/* emergency exit */
	if (*reply=='4') {
	  unlink(outlogtmp);
	  exit(0);
	}
	
	/* forward address to real saft server? */
	if (str_beq(reply,"510 ")) {
	  strcpy(server,strrchr(reply,' ')+1);
	  
	  /* close current connection */
	  sendcommand(sockfd,"QUIT",NULL);
	  shutdown(sockfd,2);
	  
	  /* connect real saft server */
	  if (!test && (sockfd=open_connection(server,SAFT))>=0) {
	    sock_getline(sockfd,reply);
	    if (str_beq(reply,"220 ") && strstr(reply,"SAFT")) {
	      
	      /* send LOG command */
	      snprintf(MAXS(line),"LOG %s %s",pw_name,outlogtmp);
	      sock_putline(sockfd,line);
	      sock_getline(sockfd,reply);
	      str_trim(reply);
	
	      /* emergency exit */
	      if (*reply=='4') {
		unlink(outlogtmp);
		exit(0);
	      }
	
	    }
	  }
	}
      }
      
      /* close the connection */
      sendcommand(sockfd,"QUIT",NULL);
      shutdown(sockfd,2);
      
    }
  }
  unlink(outlogtmp);

#endif
  
}


/*
 * pgp_encrypt  - encrypt a file with pgp
 *
 * INPUT:  pgpcrypt	- public key or IDEA encryption
 *	   pgprid	- pgp recipient id
 *         file		- input file name
 *
 * OUTPUT: file		- output file name
 */
void pgp_encrypt(int pgpcrypt, char *pgprid, char *file) {
  char
    *cp,		/* simple character pointer */
    cmd[OVERSIZE],	/* the command for system() */
    tmp[MAXLEN],	/* temporary string */
    line[MAXLEN],	/* one text line */
    rmcmd[MAXLEN];	/* the rm command */
  struct stat finfo;	/* information about a file */
  FILE *inf;		/* input file */

  inf=NULL;
  
  /* determine which tmp-files to delete in system-trap */
  if (str_eq(file,tartmp) || str_eq(file,texttmp))
    snprintf(MAXS(rmcmd),
	     "trap \"rm -f %s %s 2>/dev/null\" 1 2 3 15",file,pgptmp);
  else
    snprintf(MAXS(rmcmd),"trap \"rm -f %s 2>/dev/null\" 1 2 3 15",pgptmp);

  if (!quiet) message(prg,'I',"call to pgp...");

  /* look for matching pgp-IDs */
  if (strlen(pgprid)>1) {
    snprintf(MAXS(cmd),"%s -kvf %s > %s 2>/dev/null",pgp_bin,pgprid,pgptmp);
    if (verbose) {
      snprintf(MAXS(tmp),"shell-call: %s",cmd);
      message(prg,'I',tmp);
    }
    system(cmd);
    if (stat(pgptmp,&finfo)<0 || finfo.st_size==0 || !(inf=rfopen(pgptmp,"r"))) {
      errno=0;
      message(prg,'F',"call to pgp failed");
    }
    while (fgetl(line,inf) && !strstr(line,"matching key"));
    fclose(inf);
    if ((cp=strchr(line,'.'))) *cp=0;
    if (!str_eq(line,"1 matching key found")) {
      if (!quiet) {
	snprintf(MAXS(line),"ambigous pgp-ID '%s'",pgprid);
	message(prg,'W',line);
	inf=rfopen(pgptmp,"r");
	while (fgetl(line,inf)) printf("%s",line);
	fclose(inf);
      }
      *pgprid=0;
    }
  } else
    *pgprid=0;

  /* pgp needs user input? */
  if (pgpcrypt=='c' || !*pgprid) {
    snprintf(MAXS(cmd),"%s;%s +armor=off -f%c < %s > %s",
	     rmcmd,pgp_bin,pgpcrypt,file,pgptmp);
    if (verbose) {
      snprintf(MAXS(tmp),"shell-call: %s",strchr(cmd,';')+1);
      message(prg,'I',tmp);
    }
    if (system(cmd) || stat(pgptmp,&finfo)<0 || finfo.st_size==0) {
      errno=0;
      message(prg,'F',"call to pgp failed");
    }
    printf("\n");

  /* pgp needs no user input */ 
  } else {
    snprintf(MAXS(cmd),"%s;%s +armor=off -fe %s < %s > %s 2>/dev/null",
	     rmcmd,pgp_bin,pgprid,file,pgptmp);
    if (verbose) {
      snprintf(MAXS(tmp),"shell-call: %s",strchr(cmd,';')+1);
      message(prg,'I',tmp);
    }
    if (system(cmd) || stat(pgptmp,&finfo)<0 || finfo.st_size==0) {
      errno=0;
      message(prg,'F',"call to pgp failed (wrong pgp user id?)");
    }
  }

  strcpy(file,pgptmp);
}


/*
 * pgp_sign  - create detached pgp signature file and send SIGN header line
 *
 * INPUT:  pgpsign	- pgp user id
 *         infile	- input file name
 *         sockfd	- socket descriptor
 */
void pgp_sign(const char *pgpsign, const char *infile, int sockfd) {
  int
    check;		/* check if sig is ok */
  char
    *cp,		/* simple string pointer */
    tmp[MAXLEN],	/* temporary string */
    sign[MAXLEN], 	/* signature */
    line[MAXLEN], 	/* one line of text */
    cmd[2*MAXLEN];	/* the command for popen() */
  FILE *pipe;		/* input file descriptor */

  check=0;
  *sign=0;

  if (!quiet && !pgppass) message(prg,'I',"call to pgp...");

  snprintf(MAXS(cmd),"%s %s -fsba %s < %s",pgp_bin,pgpvm,pgpsign,infile);
  if (verbose) {
    snprintf(MAXS(tmp),"shell-call: %s",cmd);
    message(prg,'I',tmp);
  }
  if (!(pipe=popen(cmd,"r"))) message(prg,'F',"call to pgp (signature) failed");

  /* read signature file in NVT format */
  while (fgetl(line,pipe)) {
    if ((cp=strchr(line,'\n'))) *cp=0;
    if (str_eq(line,"-----BEGIN PGP MESSAGE-----")) check+=1;
    if (str_eq(line,"-----END PGP MESSAGE-----")) check+=2;
    strcat(sign,line);
    strcat(sign,"\r\n");
  }
  pclose(pipe);

  if (!pgppass) printf("\n");
  if (check!=3) message(prg,'F',"call to pgp (signature) failed");

  iso2utf(tmp,sign);
  snprintf(MAXS(sign),"SIGN %s",tmp);
  if (sockfd) sendcommand(sockfd,sign,NULL);
}


/*
 * outspool  - create and open outgoing spool header file
 *
 * INPUT:  user		- own user name
 * 	   outgoing	- outgoing spool directory
 *
 * OUTPUT: oshf		- outgoing spool header file name
 *
 * RETURN: FILE pointer if ok, NULL if failed
 *
 * This function does not use a locking or a atomar test&create function
 * because this would not work with NFS. To avoid race conditions (remember:
 * a spool daemon is normally running, too!) a tmp-header file is created.
 */
FILE *outspool(const char *user, const char *outgoing, char *oshf) {
  struct stat finfo;	/* information about a file */
  struct timeval tv;
#if !defined(SOLARIS2) && !defined(IRIX)
  struct timezone tz;
#endif

  /* get time structure */
#if defined(SOLARIS2) || defined(IRIX)
#   ifdef _SVID_GETTOD
     gettimeofday(&tv);
#   else
     gettimeofday(&tv,NULL);
#   endif
#else
  gettimeofday(&tv,&tz);
#endif

  /* build (tmp) header file name */
  snprintf(oshf,MAXLEN-1,"%s/%s_%d.t",outgoing,user,(int)tv.tv_usec);

  /* does the file already exist? */
  if (stat(oshf,&finfo)==0) return(NULL);

  return(rfopen(oshf,"w"));
}


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


/*
 * start_spooldaemon  - start local spool daemon for outgoing files
 * 
 * INPUT: localhost  - name of the local host
 */
void start_spooldaemon(char *localhost) {
  int
    sockfd;     	/* socket file descriptor */
  char
    *host,	 	/* name of local SAFT server */
    reply[MAXLEN],	/* reply string from remote server */
    tmp[MAXLEN],	/* temporary string */
    line[MAXLEN]; 	/* one line of text */
    
  /* open connection to the own server */
  sockfd=open_connection(localhost,SAFT);
  if (sockfd==-1) snprintf(MAXS(tmp),"cannot create a network socket "
			      "- cannot start local spool daemon");
  if (sockfd==-2) snprintf(MAXS(tmp),"cannot open connection to %s "
			      "- cannot start local spool daemon",localhost);
  if (sockfd==-3) snprintf(MAXS(tmp),"%s is unknown (name server down?) "
			      "- cannot start local spool daemon",localhost);
  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 "
	        "- cannot start local spool daemon",SAFT,localhost);
    message(prg,'F',tmp);
  }

  sendcommand(sockfd,"START SPOOLDAEMON",reply);
  str_trim(reply);

  /* forward to local SAFT-server set? */
  if (str_beq(reply,"510 ") && (host=strrchr(reply,' '))) {
    host++;

    /* close current connection */
    sendcommand(sockfd,"QUIT",NULL);
    shutdown(sockfd,2);
      
    /* open connection to the own SAFT server */
    sockfd=open_connection(host,SAFT);
    if (sockfd==-1) snprintf(MAXS(tmp),"cannot create a network socket "
			        "- cannot start local spool daemon");
    if (sockfd==-2) snprintf(MAXS(tmp),"cannot open connection to %s "
			        "- cannot start local spool daemon",host);
    if (sockfd==-3) snprintf(MAXS(tmp),"%s is unknown (name server down?) "
			        "- cannot start local spool daemon",host);
    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 "
	          "- cannot start local spool daemon",SAFT,host);
      message(prg,'F',tmp);
    }

    sendheader(sockfd,"START SPOOLDAEMON");
    sendheader(sockfd,"QUIT");
    
  }
}


/* 
 * outlog  - create temporary user outgoing log file
 * 
 * INPUT: from	- own user name
 *        to	- recipient user name
 * 	  host	- recipient host name
 *        file	- file name
 *        sizes	- original and compressed file size
 */
void outlog(char *to, char *host, char *file, char *sizes) {
  char currentdate[FLEN];	/* current date */
  FILE *outf;			/* output file */
  time_t timetick;		/* unix time (in seconds) */

  if (outlogging && strcmp(host,localhost) && strcmp(host,"localhost") &&
      (outf=rfopen(outlogtmp,"a"))) {
    
    /* get current date */
    timetick=time(0);
    strftime(currentdate,20,"%Y-%m-%d %H:%M:%S",localtime(&timetick));
  
    /* write to log file */
    fprintf(outf,"FROM\t%s\nTO\t%s@%s\nDATE\t%s\nFILE\t%s\nSIZES\t%s\n\n",
	    pw_name,to,host,currentdate,file,sizes);
  
    fclose(outf);

  }
  return;
    
}


/*
 * forward - forward a file with complete header from stdin
 * 
 * INPUT:  host - host to send to
 * 	   mtp	- maximum thruput
 */
void forward(char *host, float mtp) {
  int
    sockfd;     	/* socket file descriptor */
  unsigned long
    size;		/* size of the file */
  float
    ttime;
  char
    *arg,		/* SAFT command argument */
    from[FLEN], 	/* sender user name */
    recipient[FLEN], 	/* recipient at serverhost */
    line[MAXLEN], 	/* one line of text */
    tmp[MAXLEN];	/* temporary string */

  size=0;
  *line=*recipient=*from=0;

  get_header("FROM",from);
  get_header("TO",recipient);
  
  /* look for correct SAFT server and open connection */
  sockfd=saft_connect("file",recipient,from,host,tmp);

  /* loop over the other header lines */
  while (fgetl(line,stdin)) {

    str_trim(line);
    if (str_neq_nocase("DATA",line,strlen(line))) break;
    if (str_neq_nocase("RESEND",line,strlen(line))) {
      errno=0;
      message(prg,'F',"the RESEND command is not supported with the -X option");
    }
    
    /* look for SIZE command */
    if ((arg=strchr(line,' '))) {
      if (str_neq_nocase("SIZE",line,4)) sscanf(arg+1,"%ld",&size);
    }

    /* send this SAFT command */
    sendheader(sockfd,line);
    
  }
  
  if (!size) {
    errno=0;
    message(prg,'F',"SIZE command is missing");
  }
    
  send_data(sockfd,size,"","","STDIN","",mtp,&ttime);
}


/*
 * get_header -  read a header line from stdin
 * 
 * INPUT:  cmd - command type
 * 
 * OUTPUT: arg - the command argument
 */
void get_header(const char *cmd, char *arg){
  char 
    *cp,		/* a simple character pointer */
    line[MAXLEN],	/* one line of text */
    tmp[MAXLEN];	/* temporary string */
  
  /* read one header line */
  fgetl(line,stdin);
  str_trim(line);
  
  /* split command and argument */
  if (strlen(line)>3 && (cp=strchr(line,' '))) {
    *cp=0;
    strcpy(arg,cp+1);
    str_toupper(line);
  }
  
  if (!str_eq(cmd,line)) {
    errno=0;
    line[MAXLEN-80]=0;
    snprintf(MAXS(tmp),
	     "illegal SAFT command \"%s\", \"%s\" was expected",line,cmd);
    message(prg,'F',tmp);
  }

}


/* 
 * guess_ftype - guess file type
 * 
 * INPUT:  file - file name with path
 * 
 * OUTPUT: type - file type as long string
 * 
 * RETURN: b for binary, s for source, t for text
 * 
 * Remark: you MUST call stat(file) and check the return value before 
 *         calling guess_ftype(), because of symlink loop detection.
 */
char guess_ftype(const char *file, char *type) {
  int i;
  char
    *cp,
    **cpp,
    tmp[MAXLEN],		/* temporary string */
    link[MAXLEN],		/* symlink file name */
    cmd[MAXLEN];		/* the command for popen() */
  FILE *pipe;			/* input file descriptor */
  static const char *set[]=	/* source extension type array */
  { "Makefile","Makefile.",
    ".c",".f",".f77",".for",".f90",".p",".pas",".java",".h",".ada",".pl",".sl",
    ".cc",".tcl",".tk",".ps","" };
  static char *tet[]=		/* text extension type array */
  { "README",".txt","" };
  static char *sft[]=		/* source file type array */
  { "source","shell","program","command","script",
    "perl","pascal"," c ","c++","java","fortran","" };
  static const char *tft[]=	/* text file type array */
  { "text","ASCII","english","" };
  static const char *bft[]=	/* binary file type array */
  { "data","archive","zip","stripped","" };
  static char *cet[]=		/* compressed extension type array */
  { ".zip",".z",".zoo",".gz",".bz",".bz2",".tgz",".rpm",
    ".mp3",".gif",".jpg",".tif",".tiff",".png",".avi",".mpeg","" };

  /* first look for known file names and extensions */
  
  /* source types */
  for (i=0;*set[i];i++) {
    if (*set[i]=='.') {
      if ((cp=strrchr(file,'.'))) if str_eq(cp,set[i]) return('s');
    } else {
      if str_eq(file,set[i]) return('s');
    }
  }
  
  /* text types */
  for (i=0;*tet[i];i++) {
    if (*tet[i]=='.') {
      if ((cp=strrchr(file,'.'))) if str_eq(cp,tet[i]) return('t');
    } else {
      if str_eq(file,tet[i]) return('t');
    }
  }
  
  /* compressed binary types */
  if (*dontcompress[0]) 
    cpp=dontcompress; /* list from sendfile.cf */
  else
    cpp=cet;
  for (i=0;*cpp[i];i++) {
    /*printf(">%s<\n",cpp[i]);*/
    if ((cp=strrchr(file,'.')) && str_eq(cp,cpp[i])) {
      strcpy(type,"already compressed");
      return('b');
    }
  }

  /* next, try with file command */
  
  /* read output from file command */
  snprintf(MAXS(cmd),"file '%s'",file);
  if ((pipe=popen(cmd,"r")) && fgetl(tmp,pipe)) {
    pclose(pipe);
    
    /* get type string */
    if (str_beq(tmp,file))
      strcpy(type,tmp+strlen(file));
    else
      strcpy(type,tmp);
    if ((cp=strchr(type,'\n'))) *cp=0;
    
    /* follow symbolic link */
    if (strstr(type,"symbolic link")) {
      strcpy(link,strrchr(type,' ')+1);
      return guess_ftype(link,type);
    }
    
    if (verbose) {
      snprintf(MAXS(tmp),"%s is of type %s",file,type);
      message(prg,'I',tmp);
    }
    
    /* look for characteristic substrings */
    str_tolower(type);
    for (i=0;*bft[i];i++) if (strstr(type,bft[i])) return('b');
    for (i=0;*sft[i];i++) if (strstr(type,sft[i])) return('s');
    for (i=0;*tft[i];i++) if (strstr(type,tft[i])) return('t');
  }
  
  /* default type is binary */
  return('b');

}


/* 
 * linkspeed - check if link to host is fast enough
 * 
 * INPUT:  host      - remote host to send to
 *         lanspeed  - min. speed for LAN in kB/s
 *         compress  - the compression method
 * 
 * OUTPUT: compress  - empty compression method if fast link found
 * 
 * RETURN: 1 for slow, 0 for faster than lanspeed
 */
int linkspeed(const char *host, int lanspeed, char **compress) {
  int speed;		/* found speed */
  char 
    msg[MAXLEN],	/* message output string */
    speeddir[MAXLEN],	/* directory with speed info files */
    hostfile[MAXLEN];	/* file with speed for remote host */
  FILE *inf;		/* input file */
  struct stat finfo;	/* information about a file */
  
  speed=0;
  
  /* local host IS fast! :-) */
  if (str_eq(host,localhost) || str_eq(host,"localhost")) {
    if (verbose) message(prg,'I',"disabling compressing for localhost");
    *compress="";
    return(0);
  }
  
  /* on too low lanspeed value we suppose a slow link */
  if (lanspeed<1) return(1);
  
  /* create speeds dir if necessary */
  snprintf(MAXS(speeddir),"%s/speeds",userspool);
  if (stat(speeddir,&finfo)<0 || !S_ISDIR(finfo.st_mode)) {
    unlink(speeddir);
    if (mkdir(speeddir,S_IRUSR|S_IWUSR|S_IXUSR)<0) return(1);
    chmod(speeddir,S_IRUSR|S_IWUSR|S_IXUSR);
  }
  
  snprintf(MAXS(hostfile),"%s/%s",speeddir,host);

  /* if host file is missing return slow link */
  if (!(inf=rfopen(hostfile,"r"))) return(1);
  fscanf(inf,"%d",&speed);
  fclose(inf);
  
  if (speed<lanspeed) return(1);

  if (verbose) {
    snprintf(MAXS(msg),
	     "disabling compressing because last link speed to %s was %d kB/s",
	     host,speed);
    message(prg,'I',msg);
    snprintf(MAXS(msg),"LAN speed is defined as min %d kB/s",lanspeed);
    message(prg,'I',msg);
  }

  if (compress) *compress="";
  
  return(0);
}


/*
 * notespeed - note the link speed in a log file for later connections
 *
 * INPUT:  host   - actual connected host
 * 	   size   - file size in byte
 *         ttime  - transfer time in ms
 */
void notespeed(const char *host, unsigned long size, float ttime) {
  char 
    speeddir[MAXLEN],	/* directory with speed info files */
    hostfile[MAXLEN];	/* file with speed for remote host */
  FILE *outf;		/* output file */
  struct stat finfo;	/* information about a file */
  
  /* don't log local host */
  if (str_eq(host,localhost) || str_eq(host,"localhost")) return;
  
  /* don't log too small files */
  if (ttime<1 || size <10240) return;
    
  /* create speeds dir if necessary */
  snprintf(MAXS(speeddir),"%s/speeds",userspool);
  if (stat(speeddir,&finfo)<0 || !S_ISDIR(finfo.st_mode)) {
    unlink(speeddir);
    if (mkdir(speeddir,S_IRUSR|S_IWUSR|S_IXUSR)<0) return;
    chmod(speeddir,S_IRUSR|S_IWUSR|S_IXUSR);
  }
  
  snprintf(MAXS(hostfile),"%s/%s",speeddir,host);

  if ((outf=rfopen(hostfile,"w"))) {
    fprintf(outf,"%d\n",(int)(size/ttime/1.024)); /* kB/s */
    fclose(outf);
  }

}


/*
 * list_spool  - list files in outgoing spool
 * 
 * RETURN:  0 if found, 1 if no files
 */
int list_spool() {
  char
    file[FLEN];		/* name of file to send */
  struct hostlist
    *hls, 		/* host list start */
    *hlp; 		/* host list pointer */
  struct outfilelist
    *oflp;		/* outgoing file list pointer */

  hls=scanoutspool(pw_name);
  if (!hls) {
    if (quiet<2) message(prg,'W',"no files found in outgoing spool");
    return(1);
  }
  
  printf("Files in outgoing spool:\n");
  
  /* browse thru outgoing spool files lists */
  for (hlp=hls; hlp; hlp=hlp->next) {
    printf("\n");
    for (oflp=hlp->flist; oflp; oflp=oflp->next) {
      utf2iso(0,NULL,file,NULL,oflp->fname);
      printf("%s@%s : %s (%ld KB)\n",
	     oflp->to,hlp->host,file,(oflp->size+512)/1024);
    }
  }
/*
  printf("\n(To delete these files use: %s -Sd <filename> <recipient>)\n",prg);
*/  
  return(0);
}



/*
 * spooled_info  - print information about spooled file
 * 
 * INPUT:  file		- original file name
 *         sdf		- spool data file name
 *         compressed	- compressed flag
 */
void spooled_info(const char *file, const char *sdf, int compressed) {
  int size;
  char tmp[MAXLEN];	/* temporary string */
  struct stat finfo;	/* information about a file */
  
  if (quiet>1) return;
  
  if (stat(sdf,&finfo)<0) {
    snprintf(MAXS(tmp),"cannot access spool file %s",sdf);
    message(prg,'E',tmp);
    return;
  }
  
  size=(finfo.st_size+512)/1024; 
  
  if (compressed)
    snprintf(MAXS(tmp),"'%s' spooled (%d KB [compressed])",file,size);
  else
    snprintf(MAXS(tmp),"'%s' spooled (%d KB)",file,size);
  message(prg,'I',tmp);
}


/*
 * usage - print short help usage text
 */
int usage() {
  /* HAVE_GETOPTLONG_H is dead code for sendfile! */
#if defined(HAVE_GETOPTLONG_H)
  fprintf(stderr,"usage: %s [OPTIONS] file [...] user[@host]\n",prg);
  fprintf(stderr,"   or: %s [OPTIONS] -a archivename file-or-directory [...] user[@host]\n",prg);
  fprintf(stderr,"options:\n");
  fprintf(stderr,"   -a, --archive            sends file-or-director(s) as\n                              archivename to user[@host]\n");
  fprintf(stderr,"   -s, --source             send file(s) in source mode\n");
  fprintf(stderr,"   -t, --text               send file(s) in text mode\n");
  fprintf(stderr,"   -d, --delete             delete previous sent file(s)\n");
  fprintf(stderr,"   -c, --comment            comment a single file\n");
  fprintf(stderr,"   -o, --overwrite          overwrite already sent file(s) with same name\n");
  fprintf(stderr,"   -b, --bounce             resend file(s) to user[@host]\n");
  fprintf(stderr,"   -u, --uncompressed       send file(s) uncompressed\n");
  fprintf(stderr,"   -v, --verbose            verbose mode\n");
  fprintf(stderr,"   -q, --quiet              quiet mode 1: no transfer messages or questions\n");
  fprintf(stderr,"   -Q, --real-quiet         quiet mode 2: no transfer\n                             information and warning messages\n");
  fprintf(stderr,"   -P, --stdin              read data stream from stdin\n                             normaly this is a pipe\n");
  /*  fprintf(stderr,"   -X, --extended           this uses the extended\n                             header feature, this implies -P\n"); */
  fprintf(stderr,"   -V, --version            show version\n");
  fprintf(stderr,"   -h, --help               print this help\n");
  fprintf(stderr,"   -ps, --pgp-sign          pgp-sign the file\n");
  fprintf(stderr,"   -pe=ID, --pgp-encrypt    pgp-encrypt the file\n\n");
  fprintf(stderr,"Default mode: send file(s) in binary mode and compressed.\n");
  fprintf(stderr,"For a full description of all options, see 'man %s'.\n\n",prg);
  fprintf(stderr,"Example: %s rabbit.gif beate@juhu.lake.de\n\n",prg);
  fprintf(stderr,"Report bug to: Ulli Horlacher <framstag@belwue.de>\n");
#endif
  fprintf(stderr,"\n");
  fprintf(stderr,"usage: %s [OPTIONS] file [...] user[@host]\n",prg);
  fprintf(stderr,"   or: %s [OPTIONS] -a=archive-name file-or-directory [...] user[@host]\n",prg);
  fprintf(stderr,"options: -s   send file(s) in source mode\n");
  fprintf(stderr,"         -t   send file(s) in text mode\n");
  fprintf(stderr,"         -g   send file(s) in guessed mode (does not work in every case!)\n");
  fprintf(stderr,"         -d   delete previously sent file(s)\n");
  fprintf(stderr,"         -o   overwrite file(s) with the same name\n");
  fprintf(stderr,"         -u   send file(s) uncompressed\n");
  fprintf(stderr,"         -v   verbose mode\n");
  fprintf(stderr,"         -q   quiet mode 1: no transfer messages or questions\n");
  fprintf(stderr,"         -Q   quiet mode 2: no transfer, information or warning messages\n");
  fprintf(stderr,"         -P   read file from stdin (this is usually a pipe)\n");
  fprintf(stderr,"         -S   spool file(s) for later processing\n");
  fprintf(stderr,"         -l   list file(s) in outgoing spool\n");
  fprintf(stderr,"         -V   show version\n");
  fprintf(stderr,"         -m LIMIT      maximum thruput at LIMIT KB/s\n");
  fprintf(stderr,"         -a=name       send all files in one archive\n");
  fprintf(stderr,"         -c='comment'  add a one line text comment to a single file\n");
  fprintf(stderr,"         -ps           pgp-sign\n");
  fprintf(stderr,"         -pe[=ID]      pgp-encrypt [for ID]\n");
  fprintf(stderr,"example: %s rabbit.gif beate@juhu.lake.de\n",prg);
  fprintf(stderr,"see also: sfconf\n");
/*  
  fprintf(stderr,"Default mode: send file(s) in binary mode and compressed.\n");
  fprintf(stderr,"For a full description of all options, see 'man %s'.\n",prg);
*/
  return(2);
}


syntax highlighted by Code2HTML, v. 0.9.1