/*
 * File:	sendfiled.c
 *
 * Author:	Ulli Horlacher (framstag@rus.uni-stuttgart.de)
 *
 * Contrib.:	Thomas Tissen (tici@uni-paderborn.de)
 *		Olaf Erb (erb@insu1.etec.uni-karlsruhe.de)
 *		Rainer Bawidamann (widi@sol.wohnheim.uni-ulm.de)
 *		Andreas "Ako" Koppenhöfer (koppenhoefer@belwue.de)
 * 		Chris Foote (chris@senet.com.au)
 * 		Daniel Kobras <kobras@lists.tat.physik.uni-tuebingen.de>
 *		Colin Phipps <cph@cph.demon.co.uk>
 *		Stefan `Sec` Zehl <sec@42.org>
 *
 * History:	
 *
 *   1995-08-11	Framstag	initial version
 *   1995-09-10	Framstag	added delete and resend function
 *   1995-11-01	Framstag	eliminated some security flaws
 * 				added pgp signature entry
 *   1995-11-05	Framstag	added NeXT support
 *   1995-11-14	Framstag	added user config files
 *   1995-11-21	Framstag	added chat client support
 *   1996-01-04	Framstag	added allow-only flag to NOSENDFILE
 *   1996-01-11	Framstag	added global and user config files
 *   1996-01-27	Framstag	added maxspool and minfree config option
 *   1996-01-31	Framstag	bug fixes for mail2user
 *   1996-02-04	Olaf Erb	added notification=both
 *   1996-02-05	Framstag	some code cleanup
 *   1996-02-06	Framstag	added ATTR EXE
 *     				bug fixes for attribute handling
 *   1996-02-19	Framstag	fixed problems with NFS and MSG
 *     				fixed statfs-problem with Solaris-2 (?)
 *   				enhanced msg2tty with non-blocking write
 *   1996-02-20	Framstag	changed msg-tty and msg-fifo files to support 
 *	  			NFS (msg2tty)
 *   1996-02-21	widi		better Solaris-2 support
 *   1996-02-21	Framstag	bug fix with maxfiles option
 *   				better notification check
 *   1996-02-22	Framstag	replaced string "localhost" with the
 *	  			real name of the local host
 *   1996-02-23	Framstag	mail-notification now contains output
 *	  			of "receive -l", too
 *   1996-03-17	Framstag	security bug (?) fixed:
 *				no more chown on symlinks
 *   1996-03-27	Framstag	security bug fixed V2.0:
 *				no more chown on any links
 *   1996-04-01	Framstag	corrected logfile bug
 *	  			swapped O_SYNC and O_NONBLOCK
 *   1996-04-02	Framstag	fixed FROM line handling
 *   				added forwarding by user
 *   1996-04-04	Framstag	allowed COMPRESSED=GZIP for TYPE option
 *   1996-04-08	Framstag 	changed signature command
 *   				better checking for same files
 *   1996-04-12	Framstag	added pgp support
 *   				added own SIGN attribute
 *   1996-05-10	Framstag	added global alias file support
 *   1996-05-12	Framstag	added checking of SPOOL/.nosendfile
 *   1996-05-22	Framstag	added -c configfile runtime option
 *   1996-06-21	Framstag	added global log files
 *   1996-06-22	Framstag	better default date setting
 *   1996-06-23	Framstag	nicer log file formats
 *   1996-06-13	Framstag	differ between SYSV and BSD du
 *   1996-09-24	Framstag	protocol-change:
 *   				CHARSET charset --> TYPE TEXT=charset
 *   1996-10-23	Framstag	define O_SYNC for BSD
 *   1996-12-29	Framstag	added domain option in sendfile.cf
 *   1997-01-01	Framstag	fixed mkdir bug
 *   1997-01-07	Framstag	better spool file creation handling
 *   				added debug facility
 *   1997-01-19	Framstag	fixed resend bug
 *   1997-01-20	Framstag	fixed bug with TEXT=charset attribute
 *   1997-01-22	Framstag	added file post-processing feature
 *   1997-01-25	Framstag	fixed TYPE default declaration (binary)
 *   1997-01-30	Framstag	better SIZE parsing
 *   1997-02-03	Framstag	sprintf() -> snprintf()
 *   1997-02-09	Framstag	new msg2tty interface (msgh,msg)
 *   1997-02-10	Framstag	added setegid-call (security!)
 *   1997-02-12	Framstag	better IRIX and HP-UX support
 *   1997-02-13	Framstag	better OSF/1 support
 *   1997-02-23	Framstag	modified str_* function names
 *     				extended with TYPE=MIME
 *   1997-03-27	Framstag	added DEBUG command
 *   				fixed minfree checking bug
 *   1997-05-05	Framstag	better IRIX support (blksize)
 *   1997-06-10	Ako		more reliable retransmit operation
 *   1997-06-16	Framstag	added pseudo-user /dev/null for 
 *	  			network speed testing
 *   1997-06-17	Framstag	added packet_size config option
 *   1997-06-19	Framstag	changed ruid and rgid to global variables
 *   				added message logging option
 *   1997-06-23	Framstag	new own user log file for messages: msglog
 *   1997-07-07	Framstag	activated outgoing spooling
 *   1997-07-09	Framstag	added parallel option (sendfile.cf)
 *   1997-07-10	Framstag	fixed date bug with messages
 *   1997-08-21	Framstag	added config flag for allowing DEL command
 *   1997-09-12	Framstag	allow empty date field on resend command
 *   1997-09-14	Framstag	moved spoolid() from sendfiled.c to spool.c
 *   1997-09-30	Framstag	added -f free_space option
 *   1997-11-22	Framstag	better SAFT URL checking
 *   1997-11-23	Framstag	moved NOSENDFILE to ALLOW and DENY files
 *   1997-12-07	Framstag	better locking checking
 *   1997-12-10	Framstag	allowed COMPRESSED=BZIP2 for TYPE option
 *   				added PATH variable in sendfiled.cf
 *   1997-12-12	Framstag	fixed PATH bug
 *				no more testing for gzip binary
 *   1997-12-19	Framstag	added forcepgp option
 *   1998-01-04	Framstag	added forward dection for outgoing spooling
 *	  			added usage()
 *   1998-01-05	Framstag	reactivated outgoing logging
 *   1998-01-15	Framstag	save time stamp when bouncing file from outgoing spool 
 *	  			back to the user
 *   1998-01-16	Framstag	added -Q option
 *   1998-01-20	Framstag	fixed socket closing and delivery bugs in sfsd
 *   1998-01-21	Framstag	fixed closedir bug in check_outspool()
 *   1998-01-25	Framstag	fixed forwarding bug in sfsd()
 *   1998-03-01	Framstag	changed /dev/null testing recipient to :NULL:
 *   1998-03-11	Framstag	userconfig directory may now in $HOME
 *   1998-03-16	Framstag	fixed sfsd-fork bug (too many sfsd running)
 *   1998-05-19	Framstag	fixed maxfiles counting bug in spoolid()
 *   1998-07-04	Ako		set tcp timeout option
 *   1998-07-10	Framstag	pid is now in OUTGOING/.lock
 *   1998-07-11	Framstag	notification my be USERSPOOL/config/notify
 *   1998-07-12	Framstag	log last msg-sender in 
 *	  			USERSPOOL/config/msg@localhost
 *   1998-08-20	Framstag	added maxthruput option
 *   1998-08-21	Chris Foote	fixed free_space-bug with BIG partitions
 *   1998-08-21	Framstag	fixed sfsd mismatch bug
 *   1998-10-29 Framstag	fixed iso2utf space encoding bug (O-SAFT)
 *   1999-03-21 Framstag	added +batchmode to pgp-call
 *   2000-06-22 Framstag	fixed Linux glibc 2.1 incompatibilities
 *   2000-12-10 Daniel Kobras	fixed notify security bug
 *   2000-12-26 Framstag	fixed several security bugs as reported in 
 *				Bug#76048 of Debian Bug Tracking System
 *   2000-12-27 Framstag	fixed bug: spaces in file names
 *   2000-12-28 Framstag	fixed :NULL: recipient bug
 *   2001-01-10	Framstag	fopen() ==> rfopen()
 *   2001-01-17 cph		fixed auth/log security bug
 *   2001-01-17 Framstag	mail2user() now runs in a subprocess
 *   2001-02-02 Framstag	fixed openlog() bug
 *   2001-02-06 Framstag	added timeout on waiting response from client
 *   2001-08-26 sec		workaround for sete[ug]id on FreeBSD
 *
 *
 * The sendfile-daemon of the sendfile package.
 * sendfiled receives files for a specified recipient, stores them in the
 * sendfile spool directory and informs the recipient.
 * It also receives messages for a specified recipient and display them
 * on the recipients terminal.
 * sendfiled is spawned via the inetd superserver. See the README file
 * for installing hints.
 *
 * Copyright © 1995-2001 Ulli Horlacher
 * This file is covered by the GNU General Public License
 */

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

#include <time.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <utime.h>
#include <stdio.h>
#include <ctype.h>
#include <netdb.h>
#include <fcntl.h>
#include <utmp.h>
#include <grp.h>
#include <dirent.h>
#include <pwd.h>
#include <signal.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "reply.h"	/* the 3 digit reply codes with text messages */
#include "peername.h"	/* get the name of the calling host */
#include "string.h"	/* extended string functions */
#include "message.h"	/* information, warning and error messages */
#include "spool.h"	/* operations on files in the sendfile spool */
#include "net.h"	/* network stuff */
#include "io.h"		/* socket read/write */
#include "utf7.h"       /* UTF-7 coding */
#include "address.h"	/* address routines */
#include "lock.h"       /* file locking */

#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 putenv(const char *);
  int gethostname(char *, size_t);
  int symlink(const char *, const char *);
#endif
*/

#if defined(IRIX) || defined(IRIX64)
  #include <sys/statfs.h>
#elif defined(AIX3)
  #include <sys/statfs.h>
  int statfs(char *, struct statfs *);
#elif defined(OSF1)
  #include <sys/mount.h>
  #include <ustat.h>
  int statfs(char *p, struct statfs *, int);
#elif defined(HAVE_SYS_STATVFS_H)
  #include <sys/statvfs.h>
  #define statfs statvfs
  #define FSBS 1024  /* dirty hack against broken statvfs block information */
#elif defined(BSD) || defined(ULTRIX)
  #include <sys/param.h>
  #include <sys/mount.h>
#else
  #include <sys/vfs.h>
#endif
#ifdef SYSV
  #define DU "du -ks "
#else
  #define DU "du -s "
#endif

/* stupid AIX comes with no include files for networking and other stuff */
#if defined(AIX3) || defined(ULTRIX)
  #include "bsd.h"
#endif

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

#ifdef ULTRIX
  int statfs(char *, struct fs_data *);
#endif

#ifdef NEXT
  int statfs(char *, struct statfs *);
#endif

#ifdef BSD
  #ifndef O_SYNC
    #define O_SYNC O_FSYNC
  #endif
#endif

#ifdef HPUX
  #define seteuid(a) setuid(a)
  #define setegid(a) setgid(a)
#endif

#ifndef _PATH_UTMP
  #define _PATH_UTMP "/etc/utmp"
#endif

#define DEBUG(a) if (dbf) fprintf(dbf,"%s\n",a)

#define timeout 300

/* get a command line */
int getaline(char *);

/* get next SAFT command from client */
int getcmd(char *, char *);

/* write one line to header spool file */
void writeheader(int, const char *, const char *);

/* notify recipient and send reply to client */
void notify_reply(int *, char *, const char *, const char *, const char *,
		  int, int);

/* write a message to all ttys of the user */
int msg2tty(const char *, const char *, char *, int);

/* send a mail to the recipient */
void mail2user(const char *, const char *, const char *);

/* check killfile */
int restricted(const char *, const char *, char);

/* missed in <unistd.h> in some systems */
int seteuid(uid_t);
int setegid(gid_t);

/* sendfile spool daemon for outgoing files */
int sfsd(int, int, int, int, float);

/* send a file from the outgoing spool */
int send_spooldata(int, char *, char *, char *, char *, char *, float, int);

/* send a status report via mail to the local user */
int mail_report(const char *);

/* look for correct SAFT server and open connection */
int saftserver_connect(char *, char *);

/* check outgoing spool if there are expired files */
void check_outspool(int);

/* check if user is allowed to use sendfile 
   and create the user spool directories */
int check_userspool(char *, int);

/* bounce back a file from outgoing spool to the sender's incoming spool */
int bounce_file(char *, char *);

/* copy a file to a pipe descriptor */
int copy2pipe(const char *, int);

/* get the compressed and original file size */
int get_sizes(char *, unsigned long *, unsigned long *);

/* write debug output */
void dbgout(const char *);

/* simple interrupt handler for SIGCHLD */
void sigchld();

/* free disk space on spool device */
long free_space();

/* send a file to stdout */
int send_file(int);

/* open user log file */  
int openlog(const char *);

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

/* timeout termination routine */
void timeoutexit();

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

/* send a message to a remote SAFT server */
int send_msg(const char *, const char *, const char *);

/* set effective uid and gid for recipient */
void setreugid();

/* emulate su(1) */
int sudo(const char *, const char *);

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


/* global variables */
char
  *prg,				/* name of the game */
  *config,			/* config file name */
  domain[FLEN], 		/* domain name for get_domainname() */
  localhost[FLEN], 		/* name of the local host */
  path[MAXLEN],			/* $PATH for environment */
  chfile[MAXLEN],		/* challenge file name */
  csfile[MAXLEN],		/* challenge sign file name */
  userspool[MAXLEN],		/* user spool directory */
  userconfig[MAXLEN];		/* user config directory */
int
  test=0,			/* flag for test mode: receiving to /dev/null */
  auth=0,			/* flag for correct authentification */
  quiet=3,			/* no user messages to stdout */
  client=0,			/* flag to determine client or server */
  verbose=0,			/* flag for verbose mode */
  packet_size=0;		/* packet size */
uid_t ruid;			/* recipient's user id */
gid_t rgid;			/* recipient's group id */
FILE *dbf;			/* debug file */


int main(int argc, char *argv[]) {
  int
    status,			/* return status */
    opt,  			/* arg-option to test for */
    bytes,			/* number of bytes to be received */
    infd,			/* input file descriptor */
    outfd,			/* input file descriptor */
    shfd,			/* spool header file descriptor */
    sdfd,			/* spool data file descriptor */
    lfd,			/* log file descriptor */
    nblocks,			/* number of packet length blocks */
    bn,				/* block number to read */
    maxfiles,			/* maximal number of allowed files per user */
    minfree,			/* minimum free spool disk space in MB */
    maxspool,			/* maximum spool disk space quota, total in MB */
    bounce,			/* days after files are bounced to sender */
    parallel,			/* flag for multple spool daemons */
    retry,			/* minutes to wait for retrying to deliver */
    spoolsize,			/* current spool size in KB */
    wconf,			/* flag for writing config file (0=reading) */
    notify,			/* flag for sending a message */
    bell,			/* flag for tty bell */
    sys_bell,			/* system flag for tty bell */
    fetchfile,			/* flag for allowing SAFT fetchfile commands */
    forwarding,			/* flag for user file forwarding */
    msglog,			/* flag for logging incoming messages */
    sys_msglog,			/* system flag for logging incoming messages */
    queue,			/* flag for processing outgoing spool queue */
    piping,			/* flag for user file post-processing */
    spooling,			/* flag for outgoing spooling */
    deleting,			/* flag for allowing delete command */
    userconfighome,		/* flag for linking the user config to HOME */
    sys_deleting,		/* system flag for allowing delete command */
    del_success,		/* flag for successfull deleting a file */
    flags,			/* source, text, compress, tar and exe flag */
    challenge,			/* challenge for fetchfile authentification */
    id,				/* spool file id */
    n;				/* simple loop variable */
  unsigned long
    transmitted,		/* bytes already transmitted */
    size,			/* size of the file */
    osize;			/* original size of the file (uncompressed) */
  float mtp;			/* maximum thruput for outgoing files */
  char
    *cp,			/* simple char pointer */
    *argp,			/* argument string pointer */
    *peer,			/* sender host name */
    *realr,			/* real recipient name */
    *aliasr,			/* alias recipient name */
    log,			/* type of global logging */
    acceptonly,			/* flag for accepting only files or messages */
    sys_notification,		/* system flag for notification */
    notification[MAXLEN],	/* notification type (message, mail or action) */
    line[MAXLEN],		/* incoming command line */
    rpipe[MAXLEN],		/* receiving pipe */
    arg[MAXLEN],		/* the argument(s) of the command line */
    cmd[MAXLEN],		/* the command itself */
    type[MAXLEN],		/* file type: binary, source or text */
    otype[MAXLEN],		/* original file type with charset info */
    subtype[MAXLEN],		/* COMPRESSED or CRYPTED subtype */
    compress[FLEN],		/* compression type */
    sizes[FLEN],		/* original and compressed file size */
    charset[MAXLEN],		/* name of the character set */
    attribute[MAXLEN],		/* tar or exe attribute */
    sign[MAXLEN],		/* pgp signature */
    comment[MAXLEN],		/* file comment in UTF-7 */
    pgp_bin[MAXLEN],		/* the pgp binary */
    forcepgp[FLEN],		/* force pgp option */
    sys_forcepgp[FLEN],		/* force pgp option default */
    date[MAXLEN],		/* date string of file */
    currentdate[FLEN],		/* current date */
    shfile[MAXLEN],		/* spool header file name */
    sdfile[MAXLEN],		/* spool data file name */
    tmp[3*MAXLEN], 		/* temporary string */
    real[MAXLEN],		/* sender real name in UTF-7 */
    forward[MAXLEN],		/* user forward address */
    sender[MAXLEN],		/* user@senderhost (real name) */
    utfsender[MAXLEN],		/* sender in UTF-7 */
    logsender[MAXLEN],		/* sender for log file */
    filename[MAXLEN],		/* file name in UTF-7 */
    recipient[MAXLEN],		/* local user */
    mailto[MAXLEN],		/* notification mail recipient */
    saftserver[MAXLEN],		/* real saft server name */
    packet[OVERSIZE],		/* data packet to read */
    msgh[MAXLEN],		/* message header to user-tty */
    msg[3*MAXLEN],		/* message to user-tty */
    logdata[OVERSIZE];		/* log file data */
  unsigned char *ucp;		/* simple unsigend char pointer */
  struct stat finfo;		/* information about a file */
  struct filelist *flp;		/* file list pointer */
  struct senderlist 
    *sls,			/* sender list start */
    *slp;			/* sender list pointer */
  time_t timetick,tt1,tt2;	/* unix time (in seconds) */
  FILE 
    *inf,			/* for various files */
    *outf,			/* output file */
    *pp;			/* pipe stream */


  /* set default values */
  id=0;
  lfd=0;
  mtp=0;
  bell=1;
  ruid=0;
  rgid=0;
  sdfd=0;
  shfd=0;
  auth=0;
  infd=0;
  flags=0;
  queue=0;
  retry=0;
  bounce=0;
  piping=1;
  notify=0;
  msglog=0;
  minfree=0;
  fetchfile=0;
  deleting=1;
  parallel=1;
  maxspool=0;
  spooling=2;
  sys_bell=1;
  maxfiles=200;
  forwarding=1;
  sys_msglog=0;
  acceptonly=0;
  transmitted=0;
  sys_deleting=1;
  userconfighome=0;
  log='b';
  sys_notification='t';
  *sign=0;
  *date=0;
  *chfile=0;
  *csfile=0;
  *mailto=0;
  *sender=0;
  *domain=0;
  *comment=0;
  *filename=0;
  *forcepgp=0;
  *utfsender=0;
  *recipient=0;
  *attribute=0;
  *saftserver=0;
  *sys_forcepgp=0;
  config=CONFIG;
  prg=argv[0];
  strcpy(notification,"t");
  strcpy(charset,CHARSET);
  strcpy(type,"BINARY");
  strcpy(otype,"BINARY");
  dbf=NULL;
  sls=NULL;
  flp=NULL;
  inf=NULL;
  outf=NULL;
  realr=NULL;

  /* determine pgp program */
  strcpy(pgp_bin,PGP);
  if ((cp=getenv("SF_PGP"))) strcpy(pgp_bin,cp);
  
  /* switch off buffering for STDIN */
  setvbuf(stdout,NULL,_IONBF,0);
  /* setbuf(stdout,NULL); */

  /* set tcp timeout option */
#ifdef SO_KEEPALIVE
  { 
    int flag=1;
    setsockopt(fileno(stdin),SOL_SOCKET,SO_KEEPALIVE,
	       (void *)&flag,sizeof(flag));
  }
#endif

  /* scan the command line */
  while ((opt=getopt(argc, argv, "Vh?fvqQdc:")) > 0) {
    switch (opt) { 
      case ':':
      case 'h':
      case '?': exit(usage());
      case 'c': config=optarg; break;
      case 'd': dbf=rfopen(DBF,"a"); break;
      case 'v': verbose=1; quiet=0; break;
      case 'q': queue=1; break;
      case 'Q': queue=2; break;
      case 'f': printf("%ld\n",free_space()); exit(0);
      case 'V': message(prg,'I',"version "VERSION" revision "REVISION); exit(0);
    }
  }
  
#ifdef DBG
  if (!dbf) dbf=rfopen(DBF,"a");
#endif
  if (dbf) {
    timetick=time(0);
    strftime(tmp,20,"%Y-%m-%d %H:%M:%S",localtime(&timetick));
    dbgout(tmp);
  }

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

  /* 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,"bell")) {
	  if (str_eq(argp,"off")) sys_bell=0;
	  continue;
	}
	if (str_eq(line,"userconfig")) {
	  if (str_eq(argp,"home")) userconfighome=1;
	  continue;
	}
	if (str_eq(line,"forwarding")) {
	  if (str_eq(argp,"off")) forwarding=0;
	  continue;
	}
	if (str_eq(line,"piping")) {
	  if (str_eq(argp,"off")) piping=0;
	  continue;
	}
	if (str_eq(line,"deleting")) {
	  if (str_eq(argp,"off")) sys_deleting=0;
	  continue;
	}
	if (str_eq(line,"fetchfile")) {
	  if (str_eq(argp,"on")) fetchfile=1;
	  continue;
	}
	if (str_eq(line,"path")) {
	  if (*argp == '/') { 
	    snprintf(MAXS(path),"PATH=%s",argp);
	    putenv(path);
	  }
	  continue;
	}
	if (str_eq(line,"pgp")) {
	  if (*argp == '/') strcpy(pgp_bin,argp);
	  continue;
	}
	if (str_eq(line,"forcepgp")) {
	  snprintf(MAXS(sys_forcepgp),"%s",argp);
	  continue;
	}
	if (str_eq(line,"spooling")) {
	  if (str_eq(argp,"off"))     spooling=0;
	  if (str_eq(argp,"nostart")) spooling=1;
	  if (str_eq(argp,"on"))      spooling=2;
	  continue;
	}
	if (str_eq(line,"parallel")) {
	  if (str_eq(argp,"off")) parallel=0;
	  continue;
	}
	if (str_eq(line,"maxthruput")) {
	  mtp=atof(argp);
	  if (mtp<0) mtp=0;
	  continue;
	}
	if (str_eq(line,"msglog")) {
	  if (str_eq(argp,"on")) sys_msglog=1;
	  continue;
	}
	if (str_eq(line,"maxfiles")) {
	  maxfiles=atoi(argp);
	  continue;
	}
	if (str_eq(line,"maxspool")) {
	  maxspool=atoi(argp);
	  continue;
	}
	if (str_eq(line,"minfree")) {
	  minfree=atoi(argp);
	  continue;
	}
	if (str_eq(line,"packet")) {
	  packet_size=atoi(argp);
	  continue;
	}
	if (str_eq(line,"bounce")) {
	  bounce=atoi(argp);
	  continue;
	}
	if (str_eq(line,"retry")) {
	  retry=atoi(argp);
	  continue;
	}
	if (str_eq(line,"acceptonly")) {
	  acceptonly=*argp;
	  continue;
	}
	if (str_eq(line,"saftserver")) {
	  strcpy(saftserver,argp);
	  continue;
	}
	if (str_eq(line,"domain")) {
	  strcpy(domain,argp);
	  continue;
	}
	if (str_eq(line,"notification")) {
	  if (str_eq(argp,"mail")) sys_notification='m';
	  if (str_eq(argp,"both")) sys_notification='b';
	  if (str_eq(argp,"none")) sys_notification=0;
	  continue;
	}
	if (str_eq(line,"log")) {
	  if (str_eq(argp,"in"))   log='i';
	  if (str_eq(argp,"out"))  log='o';
	  if (str_eq(argp,"both")) log='b';
	  if (str_eq(argp,"none")) log=0;
	  continue;
	}
      }

    }
    fclose(inf);
  }

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

  /* process outgoing spool queue? */
  if (queue) {
    if (getuid()) {
      errno=0;
      message(prg,'F',"only root is allowed to process the outgoing spool");
    }
    switch (sfsd(queue,parallel,bounce,retry,mtp)) {
      case -1: message(prg,'F',"cannot start spool daemon");
      case  0: message(prg,'I',"spool daemon started"); break;
      case  1: message(prg,'W',"a sendfile spool daemon is already running");
    }
    exit(0);  
  }

  /* send the server ready message */
  reply(220);

  /* main loop for command line parsing and getting data */
  for (;;) {

    /* establish timeout signal handler */
    signal(SIGALRM,timeoutexit);
    alarm(timeout);
    
    /* get next SAFT command and argument */
    status=getcmd(cmd,arg);
    		 
    if (status<0) {
      notify_reply(&notify,notification,sender,recipient,mailto,bell,221);
      exit(0);
    }
    
    /* HELP command? */
    if (str_eq(cmd,"HELP") || str_eq(cmd,"?")) {
      reply(214);
      continue;
    }

    /* TO command? */
    if (str_eq(cmd,"TO")) {
      
      /* is there an argument? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      /* is there a pending notification request? */
      if (notify) notify_reply(&notify,notification,sender,recipient,
			       mailto,bell,0);

      /* convert recipient name from UTF-7 to ISO Latin 1 */
      utf2iso(0,recipient,NULL,NULL,arg);

      realr=aliasr=NULL;
      *rpipe=0;

      /* is there a global alias file? */
      if ((inf=rfopen(ALIASES,"r"))) {
	while (fgetl(line,inf)) {
	  
	  /* prepare line to be parsed */
	  if ((cp=strchr(line,'#'))) *cp=0;
	  str_trim(line);

	  /* check alias and real recipient user name */
	  if ((realr=strchr(line,' ')))  { 
	    *realr=0; 
	    realr++;
	    
	    aliasr=line;
	    
	    /* does the recipient name match the alias name? */
	    if (str_eq(aliasr,recipient)) {
	      
	      /* is it a forward to a pipe? */
	      if ((cp=strchr(realr,'|'))) {
		*cp=0;
		
		/* piping to a remote address is not allowed 
		   recipient must be a local user! */
		if (strchr(realr,'@') || str_beq_nocase(realr,"saft://")) 
		  continue;

		/* save the pipe command */
		snprintf(rpipe,MAXLEN,"%s",cp+1);
		
		/* an empty pipe is not allowed */
		if (! *rpipe) continue;
		
		/* strip off trailing space */
		str_trim(realr);
	      }
	    
	      /* alias found, ok */
	      break;
	      
	    } else {
	      realr = NULL;
	    }

	  }

	}
	fclose(inf);

	/* alias found? */
	if (aliasr && str_eq(aliasr,recipient)) {

	  /* convert SAFT to mail address */
	  if (str_beq_nocase(realr,"saft://")) saft2rfc822(realr);
	    
	  if (strchr(realr,'@'))  
	  { printf("510 Admin has set a forward to: %s\r\n",realr);
	    fflush(stdout);
	    *recipient=0;
	    continue;
	  }
	  else {
	    strcpy(recipient,realr);
	  }
	}

      }
      			
      /* for transfer benchmarks don't write to spool */
      if (str_eq(recipient,"/dev/null") ||
	  str_eq(recipient,":NULL:")) {
	test=1;
	reply(200);
	continue;
      } else {
	test=0;
      }

      /* check the user's spool and get his uid/gid */
      if (check_userspool(recipient,userconfighome)<0) continue;

      /* set global configs */
      *forward=0;
      bell=sys_bell;
      msglog=sys_msglog;
      deleting=sys_deleting;
      sprintf(notification,"%c",sys_notification);
      strcpy(mailto,recipient);
      strcpy(forcepgp,sys_forcepgp);

      /* parse the user config-file */
      snprintf(MAXS(tmp),"%s/config",userconfig);
      setreugid();
      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=' '; else continue;
	  str_trim(line);
	  if (!*line) continue;
	  str_tolower(line);
	  
	  /* is there an option and an argument? */
	  if ((argp=strchr(line,' ')) && strlen(argp)) { 
	    *argp=0; argp++;

	    /* bell on or off? */
	    if (str_eq(line,"bell")) { 
	      if (str_eq(argp,"off")) bell=0;
	      if (str_eq(argp,"on"))  bell=1;
	      continue;
	    }

	    /* allow sender to delete his files afterwards */
	    if (str_eq(line,"deleting")) { 
	      if (str_eq(argp,"off")) deleting=0;
	      if (str_eq(argp,"on"))  deleting=1;
	      continue;
	    }

	    /* log messages? */
	    if (str_eq(line,"msglog")) { 
	      if (str_eq(argp,"off")) msglog=0;
	      if (str_eq(argp,"on"))  msglog=1;
	      continue;
	    }

	    /* pgp force option */
	    if (str_eq(line,"forcepgp")) {
	      snprintf(MAXS(forcepgp),"%s",argp);
	      continue;
	    }

	    /* determine notification type */
	    if (str_eq(line,"notification")) {
	      if (str_eq(argp,"none"))     *notification=0;
	      if (str_beq(argp,"message")) strcpy(notification,"t");
	      if (str_beq(argp,"mail"))	   strcpy(notification,"m");
	      if (str_eq(argp,"both"))	   strcpy(notification,"b");
	      if (str_eq(argp,"program")) 
		snprintf(MAXS(notification),"%s/notify ",userconfig);
	      else {

		/* mail address specified to send notifications to? */
		if ((argp=strchr(argp,' '))) {
		  argp++;
		  
		  /* convert SAFT to mail address */
		  if (str_beq_nocase(argp,"saft://")) saft2rfc822(argp);
		  
		  strcpy(mailto,argp);
		}
	      }

	      continue;
	    }
	    
	    /* forwarding or post-processing? */
	    if (str_eq(line,"forward") && *argp) {
	      
	      /* convert SAFT to mail address */
	      if (str_beq_nocase(argp,"saft://")) saft2rfc822(argp);

	      /* forwarding AND post-processing is not allowed */
	      if ((cp=strchr(argp,'@')) && strchr(cp,'|')) continue;  
		
	      /* post-processing by a pipe allowed and specified? */
	      if (piping && (cp=strchr(argp,'|'))) {
		snprintf(rpipe,MAXLEN,"%s",cp+1);
		*notification=0;
		continue;
	      }

	      strcpy(forward,argp);
	      continue;
	    }
	  }

	}
	fclose(inf);
      }
      
      seteuid(0);
      setegid(0);

      /* user forward? */
      if (forwarding && *forward && *filename && !*rpipe) {
	printf("510 User has set a forward to %s\r\n",forward);
	fflush(stdout);
	continue;
      }

      reply(200);
      continue;
    }

    /* FROM command? */
    if (str_eq(cmd,"FROM")) {
      
      /* is there an argument? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      *real=0;

      /* is there a real name? */
      if ((cp=strchr(arg,' '))) {
	strcpy(real,cp+1);
	*cp=0;
      }

      /* save sender@host and real name */
      peer=peername(0);
      if (str_eq(peer,"localhost")) peer=localhost;
      if (strlen(arg)+strlen(peer)+strlen(real)+4<MAXLEN) {
	snprintf(MAXS(utfsender),"%s@%s %s",arg,peer,real);
	snprintf(MAXS(tmp),"%s@%s (%s)",arg,peer,real);
	utf2iso(0,sender,NULL,NULL,tmp);
	if ((cp=strchr(utfsender,' '))) {
	  *cp=0;
	  snprintf(MAXS(logsender),"%s (%s)",utfsender,cp+1);
	  *cp=' ';
	} else
	  strcpy(logsender,utfsender);
      } else {
	snprintf(MAXS(utfsender),"???@%s",peer);
	snprintf(MAXS(sender),"???@%s",peer);
      }

      reply(200);
      continue;
    }

    /* CHARSET command? (only for compatibilty reason) */
    if (str_eq(cmd,"CHARSET")) {
      
      /* is there an argument? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      /* save the charset name */
      strcpy(charset,arg);
      reply(200);
      continue;
    }

    /* DATE command? */
    if (str_eq(cmd,"DATE")) {
      
      /* is there an argument? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      /* save the date */
      utf2iso(0,NULL,date,NULL,arg);

      /* parse ISO-8601 date & time string */
      if ((cp=strchr(date,'T'))) *cp=' ';
      if (!strchr(date,'-')) {
	strcpy(tmp,date);
	snprintf(MAXS(date),"%c%c%c%c-%c%c-%c%c %c%c:%c%c:%c%c",
		tmp[0],tmp[1],tmp[2],tmp[3],
		tmp[4],tmp[5],
		tmp[6],tmp[7],
		tmp[9],tmp[10],
		tmp[11],tmp[12],
		tmp[13],tmp[14]);
      }

      reply(200);
      continue;
    }

    /* SIGN command? */
    if (str_eq(cmd,"SIGN")) {
      
      /* is there an argument? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      strcpy(sign,arg);

      reply(200);
      continue;
    }

    /* FILE command? */
    if (str_eq(cmd,"FILE")) {
      
      /* forward address set? */
      if (*saftserver) {
	
	/* is this a msg-only server? */
	if (acceptonly=='m') printf("510-This SAFT-server can only receive "
			 	    "messages, no files.\r\n");

	if (*recipient)
	  printf("510 For sending files use: %s@%s\r\n",recipient,saftserver);
	else
	  printf("510 For sending files use: user@%s\r\n",saftserver);

	fflush(stdout);
	continue;
      }

      /* is this a msg-only server? */
      if (acceptonly=='m') {
	reply(512);
	continue;
      }

      /* user forward? */
      if (forwarding && *forward) {
	printf("510 User has set a forward to %s\r\n",forward);
	fflush(stdout);
	continue;
      }

      /* receiving files currently disabled? */
      if (stat(SPOOL"/.nosendfile",&finfo)==0) reply(421);

      /* is there an argument? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      auth=0;
      flp=NULL;

      /* save the filename (still in UTF-7) */
      strcpy(filename,arg);
      reply(200);
      continue;
    }

    /* ATTR command? */
    if (str_eq(cmd,"ATTR")) {
      
      /* is there an argument? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      str_toupper(arg);

      /* exe attribute? */
      if (str_eq(arg,"EXE")) {
	strcpy(attribute,arg);
	flags=(flags|F_EXE)&~F_TAR;
        reply(200);
        continue;
      }

      /* tar attribute? */
      if (str_eq(arg,"TAR")) {
	strcpy(attribute,arg);
	flags=(flags|F_TAR)&~F_EXE;
        reply(200);
        continue;
      }

      /* reset attribute? */
      if (str_eq(arg,"NONE")) {
	*attribute=0;
	flags=flags&~F_TAR&~F_EXE;
        reply(200);
        continue;
      }

      reply(504);
      continue;
    }

    /* TYPE command? */
    if (str_eq(cmd,"TYPE")) {
      
      /* parse the type command and check if it is valid */
      str_toupper(arg);
      *compress=0;

      /* default compression method is gzip */
      if (strstr(arg," COMPRESSED")) strcpy(compress,"gzip");

      /* other compression method specified? */
      if (strstr(arg," COMPRESSED=")) {
	*compress=0;
	cp=strchr(arg,'=');
	*cp=0;
	cp++;
	if (str_eq(cp,"GZIP"))  strcpy(compress,"gzip");
	if (str_eq(cp,"BZIP2")) strcpy(compress,"bzip2");
	if (!*compress) {
	  reply(504);
	  continue;
	}
      }

      /* test if compression programm is available */
      if (str_eq(compress,"bzip2") && !whereis(BZIP2)) {
	reply(504);
	continue;
      }
      /* we cannot test gzip, because IRIX and others have non-standard
       * places for it! STUPID!! 
       */
      
      /* default encryption method is pgp */
      if (strstr(arg," CRYPTED=PGP")) {
	cp=strchr(arg,'=');
	*cp=0;
      }
      
      /* save the type */
      strcpy(type,arg);
      strcpy(otype,arg);
      if (str_eq(compress,"gzip"))  strcat(otype,"=GZIP");
      if (str_eq(compress,"bzip2")) strcat(otype,"=BZIP2");
      
      /* charset specified? */
      if (strncmp(type,"TEXT=",5)==0) {
	
	/* save subtype (CRYPTED or COMPRESSED) */
	*subtype=0;
	if ((cp=strrchr(type,' '))) {
	  strcpy(subtype,cp);
	  *cp=0;
	}
	
	/* extract CHARSET from TYPE string */
	cp=strchr(type,'=');
	*cp=0;
	strcpy(charset,cp+1);
	strcat(type,subtype);
      }

      /* check type format syntax */
      if (!(str_eq(type,"TEXT")              ||
	    str_eq(type,"TEXT CRYPTED")      ||
	    str_eq(type,"TEXT COMPRESSED")   ||
            str_eq(type,"MIME")              ||
	    str_eq(type,"MIME CRYPTED")      ||
	    str_eq(type,"MIME COMPRESSED")   ||
            str_eq(type,"SOURCE")            ||
	    str_eq(type,"SOURCE CRYPTED")    ||
	    str_eq(type,"SOURCE COMPRESSED") ||
	    str_eq(type,"BINARY")	     ||
	    str_eq(type,"BINARY CRYPTED")    ||
	    str_eq(type,"BINARY COMPRESSED"))) {
	reply(501);
	continue;
      }

      /* save the flags */
      flags=flags&~F_SOURCE&~F_TEXT&~F_MIME&~F_COMPRESS&~F_CRYPT;
      if (strstr(type,"TEXT"))       flags=flags|F_TEXT;
      if (strstr(type,"MIME"))       flags=flags|F_MIME;
      if (strstr(type,"SOURCE"))     flags=flags|F_SOURCE;
      if (strstr(type,"CRYPTED"))    flags=flags|F_CRYPT;
      if (strstr(type,"COMPRESSED")) flags=flags|F_COMPRESS;

      reply(200);
      continue;
    }

    /* SIZE command? */
    if (str_eq(cmd,"SIZE")) {
      
      /* is there an argument? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      /* save the size(s) */
      status=get_sizes(arg,&size,&osize);
      if (status) { 
	reply(status); 
	continue;
      }

      strcpy(sizes,arg);
      transmitted=0;

      /* sending not to /dev/null */
      if (!test) {

	/* quota exceeded for sendfile? */
	if (free_space() <= minfree+size/1048576) 
	  notify_reply(&notify,notification,sender,recipient,
		       mailto,bell,452);
	
	/* spool quota limit set? */
	if (maxspool) {
	  pp=popen(DU SPOOL,"r");
	  if (pp && fgetl(tmp,pp)) {
	  
	    sscanf(tmp,"%d",&spoolsize);

	    /* too much spool usage? */
	    if (spoolsize+size/1024>maxspool*1024)
	      notify_reply(&notify,notification,sender,recipient,mailto,bell,
			   452);
	  }
	  pclose(pp);
	}
      
      }

      reply(200);
      continue;
    }

    /* MSG command? */
    if (str_eq(cmd,"MSG")) {
      
      /* is this a file-only server? */
      if (acceptonly=='f') {
	reply(511);
	continue;
      }

      /* is there an argument? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      /* sender and recipient already specified? */
      if (*sender==0 || *recipient==0) {
	reply(503);
	continue;
      }

      setreugid();
      
      /* check killfile */
      if (restricted(sender,recipient,'m')) reply(430);

      /* convert message from UTF-7 to ISO Latin 1 and kill control codes */
      utf2iso(0,msg,NULL,NULL,arg);
      for (ucp=(unsigned char *)msg,n=0; *ucp; ucp++)
	if (*ucp==9 || *ucp==10 || (*ucp>31 && *ucp<127) || *ucp>159)
          tmp[n++]=*ucp;
      tmp[n]=0;
      strcpy(msg,tmp);
      msg[MAXLEN-3]=0;

/*      
      fprintf(dbf,"msglog: %d\n",msglog);
      fprintf(dbf,"check_userspool: %d\n",check_userspool(recipient,
							  userconfighome));
      fprintf(dbf,"chdir: %d\n",chdir(userspool));
      fprintf(dbf,"ruid: %d (%d)\n",ruid,geteuid());
      fprintf(dbf,"rgid: %d (%d)\n",rgid,getegid());
      fprintf(dbf,"setegid: %d\n",setegid(rgid));
      fprintf(dbf,"seteuid: %d\n",seteuid(ruid));
*/
      /* convert back to UTF-7 and write to log file */
      if (msglog && check_userspool(recipient,userconfighome)==0 && 
	  chdir(userspool)==0) {
	if ((lfd=openlog("msglog"))) {
	  iso2utf(tmp,msg);
	  timetick=time(0);
	  strftime(currentdate,20,"%Y-%m-%d %H:%M:%S",localtime(&timetick));
	  writeheader(lfd,"FROM",logsender);
	  writeheader(lfd,"MSG",tmp);
	  writeheader(lfd,"DATE",currentdate);
	  write(lfd,"\n",1);
	  close(lfd);
	}
      }
      seteuid(0);
      setegid(0);

      timetick=time(0);
      if (bell) strcat(msg,"\007");
      strftime(currentdate,9,"%H:%M",localtime(&timetick));
      snprintf(MAXS(msgh),"Message from %s at %s :",sender,currentdate);

      /* try to send to recipient ttys */
      if (msg2tty(recipient,msgh,msg,O_SYNC)<0)
        reply(522);
      else {
	
	/* log sender address */
	if (rgid) setegid(rgid);
	if (ruid) seteuid(ruid);
	snprintf(MAXS(tmp),"%s/msg@%s",userconfig,localhost);
	if ((outf=rfopen(tmp,"w"))) {
	  strcpy(tmp,sender);
	  if ((cp=strchr(tmp,' '))) *cp=0;
	  fprintf(outf,"%s\n",tmp);
	}
	fclose(outf);
	seteuid(0);
	setegid(0);
	
        reply(200);
      }
      continue;
    }

    /* DELETE command? */
    if (str_eq(cmd,"DEL")) {
      
      /* DEL command not allowed? */
      if (!deleting) {
	reply(502);
	continue;
      }

      del_success=-1;
      
      /* within authentication (O-SAFT) mode? */
      if (auth) {
	
	/* need argument */
	if (*arg==0) {
	  reply(505);
	  continue;
	}
	id=atoi(arg);
	if (id<1) {
	  reply(507);
	  continue;
	}
	  
	if ((sls=scanspool(""))) {

	  /* loop over sender list */
	  for (slp=sls; slp!=NULL && del_success<0; slp=slp->next) {
   
	    /* loop over files list */
	    for (flp=slp->flist; flp!=NULL && del_success<0; flp=flp->next)
	      if (id==flp->id) del_success=delete_sf(flp,0);
	    
	  }
	}
	
      } else { /* normal mode */
	
	/* sender, recipient and filename already specified? */
	if (*sender==0 || *recipient==0 || *filename==0) {
	  reply(503);
	  continue;
	}
	
	transmitted=0;

	/* are there any files in the spool directory from this user? */
	if ((sls=scanspool(sender))) {
	
	  /* loop over files list */
	  for (flp=sls->flist; flp!=NULL; flp=flp->next) {
	  
	    /* if file found try to delete spool file */
	    if (str_eq(filename,flp->fname)) del_success*=delete_sf(flp,0);

	  }
	}
      }

      if (del_success!=0)
      	reply(550);
      else
      	reply(200);

      continue;
    }

    /* DATA command? */
    if (str_eq(cmd,"DATA")) {
      
      /* sender, recipient, sizes and filename already specified? */
      if (*sender==0 || *recipient==0 || *filename==0 ||
	  (*sizes==0 && transmitted==0)) {
	reply(503);
	continue;
      }
      
      if (!test) {

	/* pgp signing enforced? */
	if ((str_eq(forcepgp,"sign") || str_eq(forcepgp,"both")) && !*sign) {
	  reply(540);
	  continue;
	}
	    
	/* pgp encryption enforced? */
	if ((str_eq(forcepgp,"encrypt") || str_eq(forcepgp,"both")) && 
	    !strstr(type,"CRYPTED")) {
	  reply(541);
	  continue;
	}
	    
	/* does the top level spool directory exist? */
	if (stat(SPOOL,&finfo)<0 || (finfo.st_mode&S_IFMT)!=S_IFDIR)
	  notify_reply(&notify,notification,sender,recipient,mailto,bell,410);

	setreugid();

	/* go to the user spool directory */
	if (chdir(userspool)<0)
	  notify_reply(&notify,notification,sender,recipient,mailto,bell,410);
	
	/* check killfile */
	if (restricted(sender,recipient,'f')) reply(430);
      
	/* resend option? */
	if (transmitted && flp) {
       
	  /* file already completly transferd? */
	  if (transmitted==flp->csize) {
	    transmitted=0;
	    reply(531);
	    continue;
	  }

	  /* open spool data file for appending */
	  id=flp->id;
	  snprintf(MAXS(sdfile),"%d.d",id);
	  sdfd=open(sdfile,O_WRONLY|O_APPEND,S_IRUSR|S_IWUSR);
	  if (sdfd<0) notify_reply(&notify,notification,sender,recipient,
				   mailto,bell,412);
	  if (wlock_file(sdfd)<0) {
	    reply(532);
	    continue;
	  }

	} else /* new file */ {

	  /* check if the file has been already sent */
	  if ((sls=scanspool(sender))) {
	  
	    /* loop over files list */
	    for (flp=sls->flist; flp!=NULL; flp=flp->next) {
	      
	      /* is it the same file and complete? */
	      if (str_eq(filename,flp->fname) &&
		  flp->csize==flp->tsize &&
		  osize==flp->osize &&
		  (str_eq(date,flp->date) || !*date) &&
		  (flags|F_COMPRESS|F_CRYPT)==(flp->flags|F_COMPRESS|F_CRYPT)) {
		reply(531);
		*filename=0;
		break;
	      }
	      
	    }

	    /* return to main loop if file is already there */
	    if (*filename==0) continue;
	  }
	  
	  /* get next spool id */
	  id=spoolid(maxfiles);
	  if (id==0) 
	    notify_reply(&notify,notification,sender,recipient,mailto,bell,412);
	  if (id<0 || id>maxfiles)
	    notify_reply(&notify,notification,sender,recipient,mailto,bell,413);
	  
	  /* 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) notify_reply(&notify,notification,sender,
					     recipient,mailto,bell,412);

	  /* lock the data file */
	  if (wlock_file(sdfd)<0) {
	    reply(532);
	    continue;
	  }

	  /* get the actual UTC time if date is not specified */
	  /* disabled to allow resending files without DATE field *//* 
	  if (!*date) {
	    timetick=time(0);
	    strftime(date,20,"%Y-%m-%d %H:%M:%S",gmtime(&timetick));
	  }*/
	  
	  /* write the header lines */
	  writeheader(shfd,"FROM",utfsender);
	  writeheader(shfd,"FILE",filename);
	  writeheader(shfd,"TYPE",otype);
	  writeheader(shfd,"SIZE",sizes);
	  if (*date)	  writeheader(shfd,"DATE",date);
	  if (*attribute) writeheader(shfd,"ATTR",attribute);
	  if (*sign)      writeheader(shfd,"SIGN",sign);
	  if (*comment)   writeheader(shfd,"COMMENT",comment);
	  if (*rpipe)     writeheader(shfd,"DATA","");
	  close(shfd);

	}
      }
      
      /* tell the client to send data */
      reply(302);

      /* read the file data in packet_size blocks */
      /* and write it to the spool file */
      if (transmitted)
	if (lseek(sdfd,transmitted,SEEK_SET)!=transmitted) reply(490);
      bytes=size-transmitted;
      nblocks=bytes/packet_size;
      transmitted=0;
      tt1=time(0);
      for (bn=1; bn<=nblocks; bn++) {
	
	alarm(timeout);
	
	/* check every 3 seconds if receiving files is currently disabled? */
	tt2=time(0);
	if (tt2-tt1>2) {
	  tt1=tt2;
	  if (stat(SPOOL"/.nosendfile",&finfo)==0) reply(421);
	}

	if (readn(0,packet,packet_size)<packet_size) {
	  close(sdfd);
	  /*
          unlink(sdfile);
          unlink(shfile);
	  */
	  notify_reply(&notify,notification,sender,recipient,mailto,bell,415);
        }
        if (!test && writen(sdfd,packet,packet_size)<packet_size) {
	  close(sdfd);
	  /*
          unlink(sdfile);
          unlink(shfile);
	  */
	  notify_reply(&notify,notification,sender,recipient,mailto,bell,452);
        }

      }

      /* copy the last bytes to the spool file */
      if ((n=bytes-nblocks*packet_size)) {
	if (readn(0,packet,n)<n) {
	  close(sdfd);
	  /*
          unlink(sdfile);
          unlink(shfile);
	  */
	  notify_reply(&notify,notification,sender,recipient,mailto,bell,415);
        }
        if (!test && writen(sdfd,packet,n)<n) {
	  close(sdfd);
	  /*
          unlink(sdfile);
          unlink(shfile);
	  */
	  notify_reply(&notify,notification,sender,recipient,mailto,bell,452);
        }
      }
      
      if (!test) close(sdfd);

      /* get the receive date */
      timetick=time(0);
      strftime(currentdate,20,"%Y-%m-%d %H:%M:%S",localtime(&timetick));

      /* reformat file name for log file */
      if (str_eq(attribute,"TAR")) {
	filename[sizeof(filename)-12]=0;
	strcat(filename," (archive)");
      }
	
      if (!test) {
      
	/* write to the log file */
	if ((lfd=openlog("log"))) {
	  writeheader(lfd,"FROM",logsender);
	  writeheader(lfd,"FILE",filename);
	  writeheader(lfd,"DATE",currentdate);
	  if (*comment) writeheader(lfd,"COMMENT",comment);
	  if (*sign)	writeheader(lfd,"SIGN",sign);
	  write(lfd,"\n",1);
	  close(lfd);
	}

	/* receiving to a pipe? */
	if (*rpipe) {

	  /* open post-processing pipe */
	  snprintf(MAXS(tmp),"cat %s/%s %s/%s | %s && rm -f %s/%s %s/%s",
		   userspool,shfile,
		   userspool,sdfile,
		   rpipe,			     
		   userspool,shfile,
		   userspool,sdfile);
	  /*
	   sprintf(tmp,"sudo return for %s: %d",recipient,sudo(recipient,tmp));
	   dbgout(tmp);
	  */
	  *notification=0;
	
	}
      }
      
      /* add sender and spool number to notification list */
      if (*notification == '/') {
	/* first argument shall be sender address */
	if (strchr(notification,' ') == strrchr(notification,' ')) {
	  strcpy(tmp,utfsender);
	  if ((cp=strchr(tmp,' '))) *(cp+1)=0;
	  if (strlen(notification)+strlen(tmp)+1<sizeof(notification))
	    strcat(notification,tmp);
	}
	sprintf(tmp,"%d ",id);
	if (strlen(notification)+15<sizeof(notification))
	  strcat(notification,tmp);
      }
      
      /* all ok */
      reply(201);

      /* reset uid and gid to root */
      seteuid(0);
      setegid(0);

      /* add entry to global logfile if required */
      /* ### todo: this part needs locking! ### */
      if ((log=='b' || log=='i') && (outf=rfopen(INLOG,"a"))) {
	stat(INLOG,&finfo);
	if (finfo.st_size==0) 
	  fprintf(outf,"# use utf7decode to view this file\n\n");
	fprintf(outf,"FROM\t%s\nTO\t%s\nDATE\t%s\nFILE\t%s\nSIZES\t%s\n\n",
		logsender,recipient,currentdate,filename,sizes);
	fclose(outf);
      }

      /* reset attributes */
      *filename = *sign = *comment = *attribute = *sizes = *date = 0;
      flags=transmitted=0;
      flp=NULL;
      strcpy(charset,CHARSET);
      strcpy(type,"BINARY");
      
      /* recipient has to be notified at the end */
      notify=1;

      continue;
    }

    /* RESEND command? */
    if (str_eq(cmd,"RESEND")) {
      
      /* sender, recipient and filename already specified? */
      if (*sender==0 || *recipient==0 || *filename==0 || *sizes==0) {
	reply(503);
        continue;
      }
      
      /* test mode? */
      if (test)
	transmitted=0;
      else {

   	/* check if this file has been already sent */
	if ((sls=scanspool(sender))) {
	
	  /* loop over files list */
	  for (flp=sls->flist; flp!=NULL; flp=flp->next) {
	  
	    /* is it the same file? */
	    if (str_eq(filename,flp->fname) &&
		(str_eq(date,flp->date) || !*date) &&
		flags==flp->flags) {
	      /* with same sizes? */
	      snprintf(MAXS(tmp),"%ld %ld",flp->csize,flp->osize);
	      if (str_eq(tmp,sizes)) {

		/* number of bytes already transmitted */
		transmitted=flp->tsize;
		break;

	      }
	    }
	  }
	}
      }

      /* emulate reply(230) */
      printf("230 %ld bytes have already been transmitted.\r\n",transmitted);
      fflush(stdout);
      continue;
    }

    /* VERSION command? */
    if (str_eq(cmd,"VERSION")) {
      reply(215);
      continue;
    }

    /* COMMENT command? */
    if (str_eq(cmd,"COMMENT")) {
      
      /* is there an argument for "COMMENT" command? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      strcpy(comment,arg);
      reply(200);
      continue;
    }

    /* insider joke :-) */
    if (str_eq(cmd,"HOPPEL")) {
      reply(203);
      continue;
    }

    /* AUTH before ID? */
    if (str_eq(cmd,"AUTH")) {
      reply(503);
      continue;
    }

    /* ID command? */
    if (str_eq(cmd,"ID")) {
      
      auth=0;
      
      if (!fetchfile) {
	reply(502);
	continue;
      }
      
      /* is there an argument? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      /* too many arguments? */
      if (strchr(arg,' ')) {
	reply(504);
	continue;
      }

      /* is pgp available? */
      if (!whereis(pgp_bin)) reply(421);
      
      /* check the user's spool and get his uid/gid */
      if (check_userspool(arg,userconfighome)<0 || chdir(userspool)<0) 
	reply(410);
      
      srand(time(0));
      challenge=rand();
      printf("331 challenge: %d\r\n",challenge);
      fflush(stdout);
      
      /* get the next command line from the client, must be "auth" */
      status=getcmd(cmd,arg);
      
      if (status<0) {
	notify_reply(&notify,notification,sender,recipient,mailto,bell,221);
	exit(0);
      }

      if (!str_eq(cmd,"AUTH")) {
	reply(503);
	continue;
      }

      if (*arg==0) {
	reply(505);
	continue;
      }

      /* enable simple interrupt handler */
      signal(SIGTERM,cleanexit);
      signal(SIGABRT,cleanexit);
      signal(SIGQUIT,cleanexit);
      signal(SIGHUP,cleanexit);
      signal(SIGINT,cleanexit);
      
      /* change effective uid and gid to recipient */
      setreugid();

      /* write challenge file */
      snprintf(MAXS(chfile),"tmp_challenge_%d",(int)getpid());
      outf=rfopen(chfile,"w");
      if (!outf) reply(410);
      fprintf(outf,"%d",challenge);
      fclose(outf);
      
      /* write challenge signature file */
      snprintf(MAXS(csfile),"tmp_challenge_%d.asc",(int)getpid());
      unlink(csfile);
      outf=rfopen(csfile,"w");
      if (!outf) {
	unlink(chfile);
	reply(410);
      }
      utf2iso(0,tmp,NULL,NULL,arg);
      fprintf(outf,"%s",tmp);
      fclose(outf);

/* 
 * pgp -kg +pubring=public.pgp +secring=private.pgp 512 
 * pgp -sbaf +clearsig=on +secring=private.pgp +pubring=private.pgp
 */
      
      snprintf(MAXS(tmp),"%s +pubring=config/public.pgp %s %s 2>/dev/null",
	       pgp_bin,csfile,chfile);
      pp=popen(tmp,"r");
      if (!pp) {
	unlink(chfile);
	unlink(csfile);
	reply(421);
      }
      while (fgetl(line,pp)) if (str_beq(line,"Good signature")) auth=1;

      pclose(pp);
      unlink(chfile);
      unlink(csfile);
      
      if (auth)
	reply(200);
      else
	reply(530);
      
      seteuid(0);
      setegid(0);
	     
      continue;
    }

    /* CONF command? */
    if (str_eq(cmd,"CONF")) {
      
      if (!fetchfile) {
	reply(502);
	continue;
      }
      
      if (!auth) {
	reply(503);
	continue;
      }
      
      if (ruid==0) reply(430);
      
      /* correct arguments? */
      if (!(cp=strchr(arg,' '))) {
	reply(505);
	continue;
      }
      *cp=0;
      str_toupper(arg);
      if (str_eq(arg,"READ")) wconf=0; else wconf=1;
      strcpy(filename,cp+1);
      if (!(str_eq(filename,"config") || str_eq(filename,"restrictions"))) {
	reply(507);
	continue;
      }
      
      /* change to user config directory */
      snprintf(MAXS(tmp),"%s/config",userspool);
      if (chdir(tmp)<0) reply(410);

      /* change effective uid and gid to recipient */
      setreugid();

      /* write config file */
      if (wconf) {
	
	reply(302);
	
	/* open config file for writimg */
	if ((outf=rfopen(filename,"w"))) {
	
	  /* read config data until EOT */
	  while (fgetl(line,stdin)) {
	    if ((cp=strchr(line,'\n'))) *cp=0;
	    if ((cp=strchr(line,'\r'))) *cp=0;
	    if (str_eq(line,"\004")) break;
	    fprintf(outf,"%s\n",line);
	  }
	
	  fclose(outf);
	} else 
	  reply(412);

	reply(201);
	
      } else { /* read config file */
	
	/* open config file for reading */
	if ((inf=rfopen(filename,"r"))) {
	
	  /* read config data */
	  while (fgetl(line,inf)) {
	    if ((cp=strchr(line,'\n'))) *cp=0;
	    if ((cp=strchr(line,'\r'))) *cp=0;
	    iso2utf(tmp,line);
	    printf("250-%s\r\n",tmp);
	  }
	  reply(250);
	  fclose(outf);
	  
	} else 
	  reply(550);
	
      }

      seteuid(0);
      setegid(0);
	     
      continue;
    }
    
    /* LIST command? */
    if (str_eq(cmd,"LIST")) {
      
      if (!fetchfile) {
	reply(502);
	continue;
      }
      
      if (!auth) {
	reply(503);
	continue;
      }
      
      sls=scanspool("");
      
      /* loop over sender list */
      for (slp=sls; slp!=NULL; slp=slp->next) {
   
	/* loop over files list */
	for (flp=slp->flist; flp!=NULL; flp=flp->next) {
       
	  /* not complete? */
	  if (flp->csize!=flp->tsize) continue;
	  
	  iso2utf7(tmp,slp->from,0);
	  printf("250-%d %s %s %ld ",flp->id,flp->fname,tmp,flp->csize);
	  strftime(tmp,FLEN,"%Y-%m-%d+ACA-%H:%M:%S",localtime(&flp->rtime));
	  printf("%s\r\n",tmp);
	  	  
	}
      }
      
      reply(250);
      continue;
    }

    /* GET command? */
    if (str_eq(cmd,"GET")) {
      
      if (!fetchfile) {
	reply(502);
	continue;
      }
      
      if (!auth) {
	reply(503);
	continue;
      }
      
      str_toupper(arg);

      /* too few arguments? */
      if (!(cp=strchr(arg,' '))) {
	reply(505);
	continue;
      }
      
      /* split arguments */
      transmitted=0;
      *cp=0;
      cp++;
      id=atoi(cp);
      if ((cp=strchr(cp,' '))) {
	*cp=0;
	cp++;
	transmitted=atol(cp); 
      }

      /* header requested? */
      if (str_eq(arg,"HEADER")) {
	
	/* change effective uid and gid to recipient */
	setreugid();

	/* open header file */
	snprintf(MAXS(shfile),"%d.h",id);
	if (!(inf=rfopen(shfile,"r"))) reply(550);

	/* read and transfer header file */
	while (fgetl(line,inf)) {
	  if ((cp=strchr(line,'\n'))) *cp=0;
	  /* if ((cp=strchr(line,'\t'))) *cp=' '; */
	  printf("250-%s\r\n",line);
	}
	
	fclose(inf);
	reply(250);
	
	seteuid(0);
	setegid(0);
	
	continue;
      }

      if (str_eq(arg,"FILE")) {
	
	/* change effective uid and gid to recipient */
	setreugid();

	/* open spool data file */
	snprintf(MAXS(sdfile),"%d.d",id);
	if (stat(sdfile,&finfo)<0 || (infd=open(sdfile,O_RDONLY))<0) reply(550);
	if (transmitted>finfo.st_size) {
	  reply(507);
	  continue;
	}
	
	size=finfo.st_size-transmitted;
	
	printf("231 %ld bytes will follow.\r\n",(unsigned long)size);
	fflush(stdout);
	
	if (size) {
	  if (transmitted && lseek(infd,transmitted,SEEK_SET)<0) reply(451);
	  if (send_file(infd)!=finfo.st_size) reply(415);
	}
	fflush(stdout);
	
	close(infd);
	
	seteuid(0);
	setegid(0);
	
	continue;
      }
      
      reply(501);
      continue;
    }

    /* start outgoing spool daemon */
    if (str_eq(cmd,"START")) {

      /* is there an argument? */
      if (*arg==0) {
	reply(505);
	continue;
      }

      str_toupper(arg);

      /* wrong argument? */
      if (!str_eq(arg,"SPOOLDAEMON")) {
	reply(501);
	continue;
      }

      /* start outgoing spool daemon if allowed */
      if (spooling==2 && *saftserver==0) {
	if (queue>=0)
	  if (sfsd(queue,parallel,bounce,retry,mtp)<0) reply(453);
	queue=-1;
	reply(200);
      } else if (*saftserver) {
	printf("510 Your SAFT-server is: %s\r\n",saftserver);
	fflush(stdout);
      } else
	reply(502);

      continue;
    }

    /* log command for outgoing files */
    if (str_eq(cmd,"LOG")) {
      
      /* arguments correct? */
      if (*arg && (cp=strchr(arg,' '))) {
	*cp=0;
	utf2iso(0,sender,NULL,NULL,arg);
	strcpy(filename,cp+1);
	
	/* throw away untrusty path in filename */
	if ((cp=strrchr(filename,'/'))) {
	  strcpy(tmp,cp+1);
	  strcpy(filename,tmp);
	}
      } else {
	reply(505);
	continue;
      }
      
      /* saftserver defined? */
      if (*saftserver) {
	printf("510 Your SAFT-server is: %s\r\n",saftserver);
	fflush(stdout);
	continue;
      } 
      
      /* check sender spool and log filename */
      if (check_userspool(sender,userconfighome)<0) continue;
      if (ruid==0) {
	reply(504);
	continue;
      }
      if (chdir(userspool)<0 || access(filename,R_OK)<0) {
	reply(550);
	continue;
      }

      /* open user tmp log and global log */
      infd=open(filename,O_RDONLY);
      outfd=open(OUTLOG,O_WRONLY|O_CREAT|O_APPEND,S_IRUSR|S_IWUSR);
      if (infd<0 || outfd<0) reply(421);
      
      /* try to the lock the logfile */
      if (wlock_file(sdfd)<0) {
	sleep(1);
	if (wlock_file(sdfd)<0) {
	  sleep(1);
	  if (wlock_file(sdfd)<0) reply(421);
	}
      }
      
      /* add header to empty log file */
      stat(OUTLOG,&finfo);
      if (finfo.st_size==0) { 
	strcpy(tmp,"# use utf7decode to view this file\n\n");
	write(lfd,tmp,strlen(tmp));
      }

      /* append user tmp log file to global log */
      if ((bytes=read(infd,logdata,OVERSIZE))<0) reply(421);
      if (write(outfd,logdata,bytes)!=bytes) reply(421);
      
      close(infd);
      close(outfd);
      
      reply(200);
      continue;
    }
    
    /* for debugging purposes? */
    if (str_eq(cmd,"DEBUG")) {

      str_toupper(arg);
      if (str_eq(arg,"FREE")) {
	long f = free_space();
	if (f<0)
	  printf("260 %s\r\n",strerror(errno));
	else
	  printf("260 %ld MB free\r\n",f);
	continue;
      }

      reply(501);
      continue;
    }
      
    /* QUIT command? */
    if (str_eq(cmd,"QUIT")) {
      
      /* is there a pending notification request? */
      notify_reply(&notify,notification,sender,recipient,mailto,bell,221);
      exit(0);
    }

    /* unknown command or syntax error */
    reply(500);

  }
}


/*
 * getaline - get a command line until LF
 *
 * INPUT:  ptr  - empty string
 *
 * OUTPUT: ptr  - string containing the command line
 *
 * RETURN: number of read bytes, -1 on error
 */
int getaline(char *ptr) {
  int c,        /* one character */
      n,        /* number of read bytes */
      ctrl=0;	/* flag for non-ASCII character */

  ptr[0]=0;

  /* get max MAXLEN characters */
  for (n=0; n<MAXLEN-1; n++) {
    
    /* next char from socket */
    c=getchar();

    /* quit if there is no more a connection to the client */
    if (c==EOF) return(-1);

    /* surpress non-ASCII chars */
    if (c<9 || c==11 || c==12 || (c>13 && c<32) || c>126) {
      ctrl=1;
      n--;
    } else {
      
      /* copy the char into the command line string */
      ptr[n]=c;

      /* check for EOL */
      if (c=='\n') break;
      if (c=='\r') n--;

    }
  }

  /* input line overrun? */
  if (n==MAXLEN-1 && ptr[n]!='\n') {
    
    /* read to next lf */
    while (c!='\n') c=getchar();
    n=0;
    reply(506);
  } else
    if (ctrl) reply(205);

  ptr[n]=0;

  /* trim all white spaces */
  if (n) str_trim(ptr);

  return(strlen(ptr));
}


/*
 * getcmd - get next SAFT command from client
 *
 * OUTPUT: cmd  - SAFT command
 *         arg  - argument of SAFT command
 * 
 * RETURN: status from getaline()
 */
int getcmd(char *cmd, char *arg) {
  int status;		/* return value */
  char
    *cp,		/* simple char pointer */
    line[MAXLEN];	/* incoming command line */
  
  *cmd=0;
  *arg=0;
  status=0;
  
  while (status==0) status=getaline(line);

  if (status>0) {

    /* extract the command name and the argument */
    strcpy(cmd,line);
    cp=strchr(cmd,' ');
    if (cp) {
      *cp=0;
      strcpy(arg,cp+1);
    }
    str_toupper(cmd);
    
  }
  
  return(status);
}


/*
 * writeheader - write one line of header information to log file
 *
 * INPUT: fd		- file descriptor
 *        attribute	- name of the header attribute
 *        value		- contents of the header attribute
 */
void writeheader(int fd, const char *attribute, const char *value) {
  int hsize;			/* header string size */
  char header[2*MAXLEN];	/* header string */

  snprintf(MAXS(header),"%s\t%s\n",attribute,value);
  hsize=strlen(header);
  if (write(fd,header,hsize)<hsize) reply(412);
}


/*
 * notify_reply  - notify user and sent reply code if given; a fatal error
 *		   (reply code 4xx) will terminate sendfiled
 *
 * INPUT:  notify	- notify flag
 *         notification	- kind of notification
 *	   sender	- sender name
 *         recipient	- local recipient
 *         mailto	- address to send mail to
 *         bell		- flag for adding bell
 * 	   replycode	- server reply code to send
 */
void notify_reply(int *notify, char *notification,
		  const char *sender, const char *recipient, const char *mailto,
		  int bell, int replycode) {
  char msgh[MAXLEN];	/* message to user-tty */

  if (!test) {
      
    if (*notify) {

      /* set time out */
      signal(SIGALRM,cleanexit);
      alarm(60);
      
#ifdef HAEGAR
      snprintf(msgh,MAXLEN-3
	       "\n>>> INFO <<< SAFT-Daemon has a new file for you!"
	       "\n\nFrom: %s ( saft://%s/%s )\n\nType \"receive\".\n",
	       sender,localhost,recipient);
#else
      snprintf(msgh,MAXLEN-3,
	       "%s has sent a file to you (%s@%s). Type \"receive\".",
	       sender,recipient,localhost);
#endif
      switch (*notification) {
	  case 'b': mail2user(mailto,sender,msgh);
	  case 't': if (strchr(mailto,'@')) {
                      snprintf(msgh,MAXLEN-1,"new file from %s",sender);
	              send_msg(msgh,mailto,recipient);
                    } else {
                      if (bell) strcat(msgh,"\007");
	              msg2tty(recipient,msgh,"",O_NONBLOCK);
		    }
	            break;
	  case 'm': mail2user(mailto,sender,msgh); break;
	  case '/': strcat(notification,">/dev/null 2>&1 &"); 
		    sudo(recipient,notification);
      }
      
      /* reset time out */
      signal(SIGALRM,SIG_IGN);
    }
  }

  *notify=0;

  /* send reply if given */
  if (replycode) reply(replycode);
}


/*
 * msg2tty - send a one line message to the recipient (tty(s) or FIFO)
 *
 * INPUT:  recipient	- recipient of the message
 *         msgh		- the message header
 *         msg		- the message
 *         mode		- non-blocking or synchronous mode
 *
 * RETURN: 0 if successfull, -1 if failed
*/
int msg2tty(const char *recipient, const char *msgh, char *msg, int mode) {
  char
    *cp,		/* simple character pointer */
    tty[FLEN],		/* name of tty */
    user[9],		/* username */
    output[MAXLEN],	/* msgh+msg */
    msgcf[MAXLEN];	/* message control file */
  int
    wall,		/* force writing to all ttys */
    utmpfd, 		/* file descriptor for utmp */
    mfd, 		/* message file descriptor */
    success;		/* return code */
  struct utmp uinfo;	/* information about a user */
  struct stat finfo;	/* information about a file */
  FILE *inf;		/* input file */

  wall=0;
  success=0;
  user[8]=0;

  if (test) return(0);

  /* change effective uid to recipient for security reasons */
  setreugid();

  snprintf(MAXS(msgcf),"%s/config/tty@%s",userspool,localhost);

  /* force writing to all ttys which are open? */
  if (*msg && str_beq(msg,"wall!")) {
    wall=1;
    msg=strchr(msg,'!')+1;
  }

  if (*msg)
    snprintf(MAXS(output),"\r\n%s\n\r%s\r\n",msgh,msg);
  else
    snprintf(MAXS(output),"\r\n%s\n\r",msgh);
    
  /* is there a message control file? */
  if (!wall && success<=0 && (inf=rfopen(msgcf,"r"))) {
    
    /* read the tty name */
    fgetl(tty,inf);
    if ((cp=strchr(tty,'\n'))) *cp=0;
    fclose(inf);

    /* belongs the tty to the recipient and is it writable? */
    if (stat(tty,&finfo)==0 && finfo.st_uid==ruid &&
	((finfo.st_mode&S_IWOTH) || (finfo.st_mode&S_IWGRP))) {
      
      /* write to dedicated tty */
      mfd=open(tty,O_WRONLY|mode);
      success=write(mfd,output,strlen(output));
      close(mfd);
    }
  }

  /* no write success so far? */
  if (success<=0) {
    success=0;

    /* search the utmp file (not standarisized, grrrr) */
    utmpfd=open(_PATH_UTMP,O_RDONLY);
    if (utmpfd<0) utmpfd=open("/var/adm/utmp",O_RDONLY);
    if (utmpfd<0) utmpfd=open("/var/run/utmp",O_RDONLY);
    if (utmpfd<0) {
      seteuid(0);
      setegid(0);
      return(-1);
    }

    /* scan through utmp (currently logged in users) */
    while (read(utmpfd,(char *)&uinfo,sizeof(uinfo))>0) {
      
#if defined(NEXT) || defined(BSD) || defined(ULTRIX) || defined(SOLARIS1)
      strncpy(user,uinfo.ut_name,8);
      if (str_eq(recipient,user)) {
	
#ifdef JEDPARSE
}
#endif
#else
      strncpy(user,uinfo.ut_user,8);
      if (uinfo.ut_type==USER_PROCESS && str_eq(recipient,user)) {
#endif
	/* get the tty */
	snprintf(MAXS(tty),"/dev/%s",uinfo.ut_line);

	/* is the tty writeable? */
	if (stat(tty,&finfo)==0 &&
	    ((finfo.st_mode&S_IWOTH) || (finfo.st_mode&S_IWGRP))) {
	  mfd=open(tty,O_WRONLY|mode);

	  /* write message to tty */
	  success=success | write(mfd,output,strlen(output));
	  close(mfd);

	}
      }
    }
    close(utmpfd);
  }

  /* reset uid to root */
  seteuid(0);
  setegid(0);

  if (success>0) return(0); else return(-1);
}


/*
 * mail2user - send the recipient a mail in a subprocess
 *
 * INPUT:  recipient	- recipient of the mail
 *         sender   	- sender of the file
 *         msg		- the message
*/
void mail2user(const char *recipient, const char *sender, const char *msg) {
  char *cp,		/* simple character pointer */
       line[MAXLEN],	/* one text line */
       cmd[MAXLEN];	/* command for pipe */
  FILE *pin,		/* input pipe */
       *pout;		/* output pipe */
  struct passwd *pwe;	/* password entry struct */
  pid_t pid;

  /* security check: disallow superuser/group */
  pwe=getpwuid(ruid);
  if (pwe==NULL || ruid==0 || rgid==0) return;
  
  /* spawn subprocess */
  signal(SIGCHLD,SIG_IGN);
  pid=fork();
  if (pid) return;

  /* change user */
  /* note: setuid does not work if euid>0 ! STUPID! */
  chdir(pwe->pw_dir);
  seteuid(0);
  setegid(0);
  if (setgid(rgid)<0) exit(1);
  initgroups(pwe->pw_name,pwe->pw_gid);
  if (setuid(ruid)<0) exit(1);

  /* strip off bell */
  if ((cp=strchr(msg,7))) *cp=0;

  /* delete ' in sendername */
  while ((cp=strchr(sender,'\''))) *cp=' ';

  /* open pipe to sendmail */
  snprintf(MAXS(cmd),SENDMAIL" %s",recipient);
  pout=popen(cmd,"w");

  /* fill out mail message */
  if (pout) {
    /* fprintf(pout,"From: %s\n",recipient); */
    fprintf(pout,"To: %s\n",recipient);
    fprintf(pout,"Subject: new file from %s\n\n",sender);

    /* try to open receive pipe */
    pin=popen(RECEIVE" -l","r");
    if (fgetl(line,pin)==NULL) {
      pclose(pin);
      pin=popen("receive -l","r");
      if (fgetl(line,pin)==NULL) {
	pclose(pin);
	pin=popen("/client/bin/receive -l","r");
	if (fgetl(line,pin)==NULL) {
	  pclose(pin);
	  pin=NULL;
	}
      }
    }

    /* add output from receive command, too */
    if (pin) {
      fprintf(pout,"\n");
      while (fgetl(line,pin)) fprintf(pout," %s",line);
      pclose(pin);
    }
/*    
    fprintf(pout,"\n\n");
    fprintf(pout,"To get your file(s), do: rlogin saft.uni-hildesheim.de\n");
    fprintf(pout,"and then type: receive\n");
*/
    fprintf(pout,".\n");
    pclose(pout);
  }

  exit(0);
}


/*
 * restricted  - check killfile
 *
 * INPUT:  sender     - sender name
 *         recipient  - local recipient
 *         type       - type of restriction: m(essage) or f(ile)
 *
 * RETURN: 1 if not allowed to send, 0 if allowed
 */
int restricted(const char *sender, const char *recipient, char type) {
  int m;
  char *cp,
       killfile[MAXLEN],
       kfu[MAXLEN],
       kfm[MAXLEN],
       line[MAXLEN],
       from[MAXLEN];
  FILE *inf;

  if (test) return(0);

  setreugid();
  
  snprintf(MAXS(killfile),"%s/config/restrictions",userspool);
  *kfm=*kfu=0;

  /* open and check killfile */
  inf=rfopen(killfile,"r");
  if (inf==NULL) return(0);

  strcpy(from,sender);
  if ((cp=strchr(from,' '))) *cp=0;

  while (fgetl(line,inf)) {
    line[MAXLEN-1]=0;
    sscanf(line,"%s%s",kfu,kfm);
    if (kfm[0]==0)
      m='b';
    else
      m=tolower(kfm[0]);
    kfm[1]=0;
    if (simplematch(from,kfu,1) && (m==type || m=='b')) {
      fclose(inf);
      return(1);
    }
  }

  fclose(inf);
  return(0);
}


/*
 * mkrdir - make recursive directory
 * 
 * INPUT:  pathname	- directory name
 * 	   mode		- permission mode bits
 * 
 * RETURN: 0 if ok, else errorcode from mkdir(2)
 */
int mkrdir(const char *pathname, mode_t mode) {
  return 0;
}


/* 
 * free_space - free disk space on spool device
 * 
 * RETURN: MBs of free disk space, -1 on error
 */
long free_space() {
#ifdef ULTRIX
  struct fs_data fsinfo;	/* information about the file system */
#else
  struct statfs fsinfo;		/* information about the file system */
#endif
  
#if defined(IRIX) || defined(IRIX64)
  if (statfs(SPOOL,&fsinfo,sizeof(struct statfs),0)==0)
    return ((long)(fsinfo.f_bsize/1048576.0L*fsinfo.f_bfree));
#elif defined(ULTRIX)
  if (statfs(SPOOL,&fsinfo)==0)
    return (((struct fs_data_req *)&fsinfo)->bfreen/1024);
#elif defined(OSF1)
  if (statfs(SPOOL,&fsinfo,sizeof(struct statfs))==0)
    return ((long)(fsinfo.f_bsize/1048576.0L*fsinfo.f_bavail));
#else
  if (statfs(SPOOL,&fsinfo)==0) { 
  #ifdef FSBS
    /* hack, hack :-) */
    fsinfo.f_bsize=FSBS;
  #endif
    return ((long)(fsinfo.f_bsize/1048576.0L*fsinfo.f_bavail));
  }
#endif
  return(-1);
}


/*
 * get_sizes - get the compressed and original file size
 * 
 * INPUT:  string  - the sizes in one string
 * 
 * OUTPUT: size    - the compressed size
 *         osize   - the original size
 * 
 * RETURN: 0 on success, reply-code on error
 */
int get_sizes(char *string, unsigned long *size, unsigned long *osize) {
  char 
    max[FLEN],
    s1[MAXLEN],
    *s2;
#ifdef RLIMIT_FSIZE
  struct rlimit rl;
#endif    

  strcpy(s1,string);

  /* get maximum file size for this process */
#ifdef RLIMIT_FSIZE
  getrlimit(RLIMIT_FSIZE,&rl);
  snprintf(MAXS(max),"%lu",(unsigned long)rl.rlim_cur);
#else
  /* this is 2**15 = max_signed_int */
  strcpy(max,"2147483647");
#endif    
  
  /* get compressed and original file size string */
  s2=strchr(s1,' ');
  if (!s2) return(501);
  *s2=0;
  s2++;
  
  /* more than maximum file size specified? */
  if (strlen(s1)>strlen(max) || 
      strlen(s2)>strlen(max) ||
      (strlen(s1)==strlen(max) && strcmp(s1,max)>0) || 
      (strlen(s2)==strlen(max) && strcmp(s2,max)>0)) return(453);
  
  /* return numeric compressed and original file size */
  sscanf(s1,"%lu",size);
  sscanf(s2,"%lu",osize);
  return(0);
    
}


/*
 * send_msg  - send a SAFT message to a remote server
 * 
 * INPUT:  msg    - the message
 *         to     - user@host recipient
 * 
 * RETURN: 0 on success, -1 on failure
 */
int send_msg(const char *msg, const char *to, const char *recipient) {
  int
    sockfd;     	/* socket file descriptor */
  char
    *cp,		/* simple char pointer */
    user[FLEN],		/* user name */
    host[MAXLEN],	/* host name */
    tmp[MAXLEN],	
    line[MAXLEN];	/* one line of text */
    
  cp=strchr(to,'@');
  if (cp) {
    *cp=0;
    snprintf(MAXS(user),"%s",to);
    snprintf(MAXS(host),"%s",cp+1);
  } else {
    snprintf(MAXS(user),"%s",to);
    strcpy(host,localhost);
  }

  sockfd=open_connection(host,SAFT);
  if (sockfd<0) return(-1);
  
  /* no remote server or protocol error? */
  sock_getline(sockfd,line);
  if (!str_beq(line,"220 ") || !strstr(line,"SAFT")) return(-1);
/*
  snprintf(MAXS(tmp),"connected to %s",host);
  dbgout(tmp);
*/  
  snprintf(MAXS(line),"FROM %s autogenerated+ACA-SAFT+ACA-message",recipient);
  snprintf(MAXS(line),"FROM %s",recipient);
  sock_putline(sockfd,line);
  if (!str_beq(getreply(sockfd),"200 ")) {
    close(sockfd);
    return(-1);  
  }
  snprintf(MAXS(line),"TO %s",user);
  sock_putline(sockfd,line);
  if (!str_beq(getreply(sockfd),"200 ")) {
    close(sockfd);
    return(-1);  
  }
  iso2utf(tmp,(char*)msg);
  snprintf(MAXS(line),"MSG %s",tmp);
  sock_putline(sockfd,line);
  if (!str_beq(getreply(sockfd),"200 ")) {
    close(sockfd);
    return(-1);  
  }
  sock_putline(sockfd,"QUIT");
  getreply(sockfd);
  close(sockfd);
  
  return(0);
}


/*
 * sfsd  - sendfile spool daemon
 * 
 * INPUT:  queue    - flag for processing the outgoing spool queue
 *         parallel - flag for multple spool daemons sending parallel 
 * 	   bounce   - days after files are bounced to sender
 *         retry    - minutes to wait for retrying to deliver
 *         mtp	    - maximum thruput
 * 
 * RETURN: 1 if spool daemon started, 0 if a spool daemon is already running
 */
int sfsd(int queue, int parallel, int bounce, int retry, float mtp) {
  int
    lockf,		/* lock file descriptor */
    error,		/* error flag */
    sockfd,     	/* socket file descriptor */
    sockfd2;     	/* 2nd socket file descriptor for forwards */
  char
    *cp,		/* simple char pointer */
    *rs,		/* reply string from the remote server */
    *lockfn,		/* lock file name */
    errmsg[MAXLEN],	/* error text from SAFT-connect */
    tmp[MAXLEN],	/* tmp string */
    ahost[MAXLEN],	/* alternate host to connect */
    line[MAXLEN],	/* incoming command line */
    arecipient[MAXLEN],	/* alternate recipient */
    lrecipient[MAXLEN];	/* last recipient */
  pid_t pid;		/* process id */
  struct hostlist
    *hls,		/* host list start */
    *hlp;		/* host list pointer */
  struct outfilelist
    *oflp;		/* outgoing file list pointer */
  FILE *inf;		/* input file */

  pid=-1;
  error=0;
  *errmsg=0;
  rs="";
  lockfn=OUTGOING"/.lock";
  if (retry<0) retry=10;
  if (bounce<=0) bounce=5;

  /* test outgoing spool */
  if (chdir(OUTGOING)<0) {
    if (queue==1) message(prg,'F',OUTGOING);
    snprintf(MAXS(tmp),OUTGOING" : %s",strerror(errno));
    dbgout(tmp);
    exit(1);
  }
  
  /* respect lock file when not in interactive mode */
  if (queue!=1) {
  
    /* try to create lock file */
    if ((lockf=open(lockfn,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))<0) reply(421);
  
    /* try to set a lock */
    if (wlock_file(lockf)<0) {
      /* lock failed -> sfsd is already running) */
      close(lockf);
      return(1);
    }

    /* ignore exit status from child process */
    signal(SIGCHLD,SIG_IGN);

    /* spawn subprocess */
    pid=fork();
  
    /* error? */
    if (pid<0) return(-1);
  
    /* child process has no lock any more - this is STUPID! */
    /* the parent process has to wait and check if the child relocks */
    if (pid) { 
      close(lockf);
      sleep(1); 
      if ((lockf=open(lockfn,O_WRONLY,S_IRUSR|S_IWUSR))<0 ||
	  tlock_file(lockf)!=1)
	return(-1);
      else
	return(0); 
    }

    /* this is the spool daemon child process */
  
    /* relock */
    unlink(lockfn);
    if ((lockf=open(lockfn,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))<0) {
      snprintf(MAXS(tmp),"cannot open %s : %s",lockfn,strerror(errno));
      dbgout(tmp);
      exit(1);
    }
    if (wlock_file(lockf)<0) {
      snprintf(MAXS(tmp),"cannot lock %s : %s",lockfn,strerror(errno));
      dbgout(tmp);
      exit(1);
    }
    snprintf(MAXS(tmp),"%d\n",getpid());
    write(lockf,tmp,strlen(tmp));

    /* disconnect from client */
    setsid();

  }
  
  /* set process name (argv[0]) */
/* does not work on all plattforms
  if ((int n=strlen(prg))>5) {
    for (int i=0;i<n;i++) prg[i]=0;
    strcpy(prg,"sfsd");
  }
*/
  
  /* process outgoing queue */
  for (;;) {
    
    dbgout("sfsd-loop");
    
    /* scan outgoing spool */
    hls=scanoutspool("");
    if (!hls) {
      if (queue==1) message(prg,'F',"outgoing spool is not accessible");
      dbgout("outgoing spool is not accessible");
      exit(1);
    }

    /* loop over all destination hosts */
    for (hlp=hls; hlp && hlp->flist; hlp=hlp->next) {
      
      /* ignore exit status from child process */
      signal(SIGCHLD,SIG_IGN);
      pid=-1;

      /* fork an extra spool daemon if parallel option is set */
      if (parallel) pid=fork();
      if (!parallel || pid==0) {
       
	/* initiate the connection to the server host */
	sockfd=saftserver_connect(hlp->host,errmsg);
	if (sockfd<0) {
	  if (*errmsg) dbgout(errmsg);
	  if (parallel) exit(0);
	  continue;
	}
	
	snprintf(MAXS(tmp),"connected to %s",hlp->host);
	if (queue==1) message(prg,'I',tmp);
	dbgout(tmp);
	
	*lrecipient=0;
	
	/* process all files for this host */
	for (oflp=hlp->flist; oflp; oflp=oflp->next) {
	  
	  error=0;
	  *arecipient=0;
	  
	  /* goto next spool file if this one is not accessible */
	  if (!(inf=rfopen(oflp->oshfn,"r"))) continue;

	  /* read the spool header file */
	  while (fgetl(line,inf)) {
	   
	    if ((cp=strchr(line,'\n'))) *cp=0;

	    /* check the TO line */
	    if (str_beq(line,"TO")) {
	      
	      if ((cp=strchr(line,'@'))) *cp=0;
	      
	      /* is this recipient the same as the last one
	         (without forwarding)? */
	      strcpy(ahost,hlp->host);
	      strcpy(arecipient,line+3);
	      if (str_eq(arecipient,lrecipient)) continue;
	      
	      /* is there a forward address for this recipient? */
	      sock_putline(sockfd,line);
	      error=check_forward(sockfd,arecipient,ahost,errmsg);
	      
	      /* recipient is not available */
	      if (error==-1) {
		fclose(inf);
		bounce_file(oflp->oshfn,errmsg);
		break;
	      }
	      
	      /* forward address found */
	      if (error==1) {

		/* same host? */
		if (str_eq(hlp->host,ahost)) {
		  snprintf(MAXS(line),"TO %s",arecipient);
		  sock_putline(sockfd,line);
		  rs=getreply(sockfd);
		  if (!str_beq(rs,"200")) {
		    fclose(inf);
		    break;
		  }
		  error=0;
		  continue;
		}
		
		error=2;
		fclose(inf);
		
		/* try to connect to forward address */
		if (parallel) if (fork()!=0) break;
		sockfd2=saft_connect("file",arecipient,oflp->from,ahost,errmsg);
		
		/* no connection? */
		if (sockfd2<0) {
		  if (parallel) exit(1);
		  break;
		}
		
		snprintf(MAXS(tmp),"connected to %s (forward redirection)",
			 hlp->host);
		if (queue==1) message(prg,'I',tmp);
		dbgout(tmp);
	
		/* reread spool header file and send it to the forward address */
		if (!(inf=rfopen(oflp->oshfn,"r"))) {
		  if (parallel) exit(1);
		  sendcommand(sockfd2,"QUIT",NULL);
		  shutdown(sockfd2,2);
		  break;
		}
		while (fgetl(line,inf)) {
		  if ((cp=strchr(line,'\n'))) *cp=0;
		  if (str_beq(line,"TO")) continue; /* TO line has been already sent */
		  sock_putline(sockfd2,line);
		  rs=getreply(sockfd2);
		  if (!str_beq(rs,"200") && !str_beq(rs,"202")) {
		    error=1;
		    break;
		  }
		}

		fclose(inf);
		
		/* on error bounce back file */
		if (error==1) {
		  sendcommand(sockfd2,"QUIT",NULL);
		  shutdown(sockfd2,2);
		  bounce_file(oflp->oshfn,rs+4);
		  if (parallel) exit(1);
		  break;
		}
		
		/* send the spool data file */
		error=send_spooldata(sockfd2,oflp->oshfn,oflp->from,
				     oflp->to,hlp->host,oflp->fname,
				     mtp,queue==1);
		
		sendcommand(sockfd2,"QUIT",NULL);
		shutdown(sockfd2,2);
		  
		/* send mail on successful delivery */
		if (!error) mail_report(ahost);
		
		/* return to normal spool processing */
		if (parallel) exit(error);
		if (!error) error=2; /* forward address found and delivered */
		break;
	      }
	      
	      /* last recipient with no forward address */
	      strcpy(lrecipient,arecipient);
	      
	    } else { 
	      
	      /* send header line (this is not the TO line!) */
	      sock_putline(sockfd,line);
	      rs=getreply(sockfd);
	      
	      if (!str_beq(rs,"200") && !str_beq(rs,"202")) {
		error=1;
		break;
	      }
	    
	    }
	  }

	  fclose(inf);
	  
	  /* on error bounce back file */
	  if (error==1) bounce_file(oflp->oshfn,rs+4);
	  
	  /* send the spool data file if there are no errors so far */
	  if (!error) send_spooldata(sockfd,oflp->oshfn,oflp->from,
				     oflp->to,hlp->host,oflp->fname,
				     mtp,queue==1);
					   
	} /* all files are now sent for this destination */

	sendcommand(sockfd,"QUIT",NULL);
	shutdown(sockfd,2);
	
	mail_report(hlp->host);
	if (pid==0) exit(0);
      }
    }
    
    /* check for expired files */ 
    check_outspool(bounce);
    
    /* started with option -q ? */
    if (queue==1) exit(0); 

    sleep(retry*60);

#ifdef deadcode    
    /* handle child process termination and wait for next queue run */
    t0=time(0);
    while ((ssec=t0-time(0)+retry*60)>0) {
      sigchld();
      signal(SIGCHLD,sigchld);
      snprintf(MAXS(tmp),"sleep %d s",ssec);
      dbgout(tmp);
      sleep(ssec);
    }
#endif
    
  }
}


/*
 * send_spooldata - send a file from the outgoing spool
 * 
 * INPUT:   sockfd	- socket file descriptor
 *          oshfn	- outgoing spool header file
 *          from	- sender name
 *          to		- recipient name
 * 	    host	- server host
 *          fname	- original file name
 * 	    mtp		- maximum thruput
 *          queue	- process interactivly
 * 
 * RETURN:  0 if ok, -1 on failure
 */
int send_spooldata(int sockfd, char *oshfn, char *from, char *to,
		   char *host, char *fname, float mtp, int queue) {
  int status;		/* return status from send_data */
  char
    osdfn[MAXLEN],	/* outgoing spool data file */
    isoname[MAXLEN],	/* filename in ISO Latin-1 */
    tmp[MAXLEN],	/* tmp string */
    line[MAXLEN];	/* one text line */
  struct stat finfo;	/* information about a file */
  FILE *mailf;		/* mail file */
  time_t timetick;	/* unix time (in seconds) */

  /* status report */
  if (queue) {
    utf2iso(0,isoname,NULL,NULL,fname);
    snprintf(MAXS(tmp),"sending %s to %s",isoname,to);
    message(prg,'I',tmp);
  }
		
  snprintf(MAXS(tmp),"sending %s to %s@%s",fname,to,host);
  dbgout(tmp);

  strcpy(osdfn,oshfn);
  osdfn[strlen(osdfn)-1]='d';
  if (stat(osdfn,&finfo)<0) {
    snprintf(MAXS(tmp),"cannot access %s",osdfn);
    dbgout(tmp);
    return(-1);
  }
  
  /* send file data and check return status */
  status=send_data(sockfd,finfo.st_size,osdfn,"",osdfn,"",mtp,NULL);
  
  /* error? */
  if (status<0) return(status);
  
  /* delete this file from outgoing spool */
  unlink(oshfn);
  unlink(osdfn);
  
  /* write status log */
  mkdir(SPOOL"/LOG",S_IRUSR|S_IWUSR|S_IXUSR);
  snprintf(MAXS(tmp),SPOOL"/LOG/%s:%s",from,host);
  if ((mailf=rfopen(tmp,"a"))) {
    chmod(tmp,S_IRUSR|S_IWUSR);
    snprintf(MAXS(tmp),"'%s' to %s",fname,to);
    utf2iso(1,line,NULL,NULL,tmp);
    timetick=time(0);
    strftime(tmp,21,DATEFORMAT,localtime(&timetick));
    fprintf(mailf,"sending %s@%s at %s : ",line,host,tmp);
    if (status)
      fprintf(mailf,"already transmitted\n");
    else
      fprintf(mailf,"ok\n");
    fclose(mailf);
  }
  
  return(0);
}


/*
 * saftserver_connect  - look for correct SAFT server and open connection
 *
 * INPUT:  host		- host to connect
 *
 * OUTPUT: host		- connected host (may be different because of forward)
 * 	   error	- error string
 * 
 * RETURN: socket file descriptor; -1 on error
 */
int saftserver_connect(char *host, char *error) {
  int
    port,		/* tcp port to connect to */
    status,		/* status code */
    sockfd,     	/* socket file descriptor */
    hopcount;		/* count for forwarding addresses */
  char
    *colon,
    tmp[MAXLEN],	/* temporary string */
    server[MAXLEN],	/* generic saft server */
    line[MAXLEN]; 	/* one line of text */
      

  port=SAFT;
  hopcount=0;
  *error=0;
  if ((colon=strrchr(host,':'))) {
    *colon=0;
    port=atoi(colon+1);
  }
  
  /* try to connect to the recipient's server */
  for (hopcount=1; hopcount<11; hopcount++) {
     
    /* tell where to send to */
    snprintf(MAXS(tmp),"opening connection to %s:%d",host,port);
    dbgout(tmp);

    /* initiate the connection to the server */
    sockfd=open_connection(host,port);
    if (sockfd==-3 && port==SAFT) {
      snprintf(MAXS(server),"saft.%s",host); 
      snprintf(MAXS(tmp),"opening connection to %s:%d",server,SAFT);
      dbgout(tmp);
      sockfd=open_connection(server,SAFT);
      switch (sockfd) {
	case -1: strcpy(error,"cannot create a network socket");
	case -2: snprintf(error,MAXLEN-1,"cannot open connection to %s",server);
	case -3: snprintf(error,MAXLEN-1,"%s has no internet-address",server);
      }
    } else {
      switch (sockfd) {
	case -1: strcpy(error,"cannot create a network socket");
	case -2: snprintf(error,MAXLEN-1,"cannot open connection to %s",host);
	case -3: snprintf(error,MAXLEN-1,"%s has no internet-address",host);
      }
    }
    if (sockfd<0) {
      dbgout(error);
      if (port!=SAFT) {
	if (colon) *colon=':';
	return(-1);
      }
    }

    /* no remote server or protocol error? */
    sock_getline(sockfd,line);
    if (!str_beq(line,"220 ") || !strstr(line,"SAFT")) {
      snprintf(error,MAXLEN-1,"No SAFT server on port %d at %s",port,host);
      dbgout(error);
      if (colon) *colon=':';
      return(-1);
    }

    /* is there a forward set? */
    sock_putline(sockfd,"FILE test");
    status=check_forward(sockfd,tmp,host,error);
    if (status==-1) return(-1);
    if (status==1) {
      snprintf(MAXS(tmp),"forward points to %s",host);
      dbgout(tmp);
      colon=NULL;
      port=487;
      continue;
    }

    /* connection is successfull */
    snprintf(MAXS(tmp),"connected to %s:%d",host,port);
    *error=0;
    dbgout(tmp);
    return(sockfd);
  }

  snprintf(error,MAXLEN-1,"maximum hopcount reached (forward loop?)");
  dbgout(error);
  return(-1);
}


/* 
 * mail_report  - send a status report via mail to the local user
 * 
 * INPUT:  host  - recipient host name
 * 
 * RETURN: 0 if ok; -1 in failure
 */
int mail_report(const char *host) {
  char
    *cp,
    mailfn[FLEN],	/* status log mail file name */
    line[MAXLEN],	/* incoming command line */
    sender[MAXLEN],	/* sender name */
    tmp[MAXLEN];	/* tmp string */
  FILE 
    *mailf,		/* mail file */
    *pp;		/* output pipe */
#ifdef NEXT
  FILE *dp;		/* directory pipe */
#else
  struct dirent *dire;	/* directory entry */
  DIR *dp;		/* directory pointer */
#endif
  
  /* look for status log files to send via mail */
  if (chdir(SPOOL"/LOG")<0) return(-1);

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

  /* open LOG dir */
  snprintf(MAXS(cmd),"ls *:%s 2>/dev/null",host);
  if ((dp=popen(cmd,"r")) == NULL) {
    exit(0);
    if (parallel) continue;
  }

  /* scan through LOG directory */
  while (fgetl(mailfn,dp)) {
    
    if ((cp=strrchr(mailfn,'\n'))) *cp=0;
#ifdef JEDPARSE
  }
#endif
#else
  /* open LOG dir */
  dp=opendir(".");

  /* scan through LOG directory */
  while ((dire=readdir(dp))) {
    
    strcpy(mailfn,dire->d_name);
    snprintf(MAXS(tmp),"*:%s",host);
    if (simplematch(mailfn,tmp,0)<1) continue;
#endif
    mailf=rfopen(mailfn,"r");
    if (!mailf) continue;
    
    strcpy(sender,mailfn);
    cp=strchr(sender,':');
    *cp=0;
    
    /* open pipe to sendmail */
    pp=popen(SENDMAIL" -t","w");
    if (!pp) continue;
    
    /* fill out mail message */
    fprintf(pp,"From: root\n");
    fprintf(pp,"To: %s\n",sender);
    fprintf(pp,"Subject: sendfile spool daemon status report for %s\n\n",
	    host);
    while (fgetl(line,mailf)) fprintf(pp,"%s",line);
    fprintf(pp,"\n.\n");
    pclose(pp);
    fclose(mailf);
    
    unlink(mailfn);
    
  }
  
#ifdef NEXT
  pclose(dp);
#else
  closedir(dp);
#endif
  
  return(0);
}


/*
 * sigchld - simple interrupt handler for SIGCHLD
 */
/* replaced by:   signal(SIGCHLD,SIG_IGN);
void sigchld() {
#ifdef NEXT
  int status;
  wait(&status);
#else
  waitpid(-1,NULL,WNOHANG);
#endif
  return;
}
*/


/*
 * check_outspool  - check outgoing spool if there are expired files and
 *                   bounce them to the sender's incoming spool
 *
 * INPUT: bounce  - number of days after file is expired
 */
void check_outspool(int bounce) {
  char
    tmp[MAXLEN],		/* temporary string */
    oshfn[FLEN];		/* outgoing spool header file name */
  time_t timetick;		/* unix time (in seconds) */
  struct stat finfo;		/* information about a file */
#ifdef NEXT
  FILE *pp;			/* pipe */
#else
  struct dirent *dire;		/* directory entry */
  DIR *dp;			/* directory pointer */
#endif

  timetick=time(0);
  if (chdir(OUTGOING)<0) return;

  /* mega stupid NeXT has broken readdir() */
#ifdef NEXT
  /* open spool dir */
  if ((pp=popen("ls *.h 2>/dev/null","r")) == NULL) return;

  /* scan through spool directory */
  while (fgetl(oshfn,pp)) {
   
    if ((cp=strrchr(oshfn,'\n'))) *cp=0;
#ifdef JEDPARSE
}
#endif
#else
  /* open spool dir */
  if (!(dp=opendir("."))) return;

  /* scan through outgoing spool directory */
  while ((dire=readdir(dp))) {
   
    /* ignore non-header files */
    snprintf(MAXS(oshfn),"%s",dire->d_name);
    if (!str_eq(&oshfn[strlen(oshfn)-2],".h")) continue;
#endif

    /* hier noch hinzufuegen: ungueltige Files nach outgoing/BAD schieben */
    
    if (stat(oshfn,&finfo)<0) continue;

    /* spool time expired? */
    if (timetick>finfo.st_mtime+bounce*DAYSEC) {
      snprintf(MAXS(tmp),"no connection within %d days",bounce);
      bounce_file(oshfn,tmp);
    }
  }
  
#ifdef NEXT
  pclose(pp);
#else
  closedir(dp);
#endif
}


/*
 * bounce_file  - bounce back a file from outgoing spool to the 
 *                sender's incoming spool
 *
 * INPUT: file	   - outgoing spool header file name
 *        comment  - comment to add
 * 
 * RETURN: 0 on success, -1 on failure
 */
int bounce_file(char *file, char *comment) {
  int
    id;				/* spool file id */
  char
    *cp,			/* simple character pointer */
    *arg,			/* the argument(s) of the header line */
    cwd[MAXLEN],		/* current working directory */
    hline[MAXLEN],		/* header line */
    tmp[MAXLEN],		/* temporary string */
    shfn[MAXLEN],		/* spool header file name */
    sdfn[MAXLEN],		/* spool data file name */
    oshfn[MAXLEN],		/* outgoing spool header file name */
    osdfn[MAXLEN];		/* outgoing spool data file name */
  FILE
    *inf,			/* input file */
    *outf;			/* output file */
  struct stat finfo;		/* information about a file */
  struct passwd *pwe;		/* password entry struct */
  struct utimbuf utb;		/* binary time */

  if ((cp=strrchr(file,'/')))
    cp++;
  else
    cp=file;
  snprintf(MAXS(oshfn),OUTGOING"/%s",cp);
  snprintf(MAXS(osdfn),OUTGOING"/%s",cp);
  osdfn[strlen(osdfn)-1]='d';
  
  if (stat(oshfn,&finfo)<0) return(-1);
  if (!getcwd(MAXS(cwd))) return(-1);

  /* get user name and spool directory */
  if (!(pwe=getpwuid(finfo.st_uid))) return(-1);
  snprintf(MAXS(userspool),SPOOL"/%s",pwe->pw_name);

  /* create user spool directory if necessary */
  if (mkdir(userspool,S_IRUSR|S_IWUSR|S_IXUSR)==0)
    chown(userspool,pwe->pw_uid,pwe->pw_gid);

  /* change uid and get user name */
  if (setegid(finfo.st_gid)<0) exit(1);
  if (seteuid(finfo.st_uid)<0) exit(1);

  if (!(inf=rfopen(oshfn,"r")) || chdir(userspool)<0) {
    fclose(inf);
    seteuid(0);
    setegid(0);
    chdir(cwd);
    return(-1);
  }


  /* get next spool id */
  id=spoolid(MAXLEN);
  if (id<=0 || id>MAXLEN) {
    fclose(inf);
    seteuid(0);
    setegid(0);
    chdir(cwd);
    return(-1);
  }

  /* set file names */
  snprintf(MAXS(shfn),"%d.h",id);
  snprintf(MAXS(sdfn),"%d.d",id);

  if (!(outf=rfopen(shfn,"w"))) {
    fclose(inf);
    seteuid(0);
    setegid(0);
    chdir(cwd);
    return(-1);
  }

  /* copy header file */
  while (fgetl(hline,inf)) {
    
    /* prepare the header line */
    if ((cp=strchr(hline,'\n'))) *cp=0;
    cp=strchr(hline,'\t');
    if (!cp) continue;
    arg=cp+1;
    *cp=0;
    
    /* ignore old COMMENT */
    if (str_eq(hline,"COMMENT")) continue;

    /* add new bounce COMMENT */
    if (str_eq(hline,"TO")) {
      snprintf(MAXS(tmp),"cannot sent to %s : %s",arg,comment);
      iso2utf(arg,tmp);
      fprintf(outf,"COMMENT\t%s\n",arg);
      continue;
    }

    /* add other header lines */
    fprintf(outf,"%s\t%s\n",hline,arg);
    
  }

  fclose(inf);
  fclose(outf);

  /* copy spool data file */
  if (fcopy(osdfn,sdfn,S_IRUSR|S_IWUSR)<0) {
        
    /* on failure delete new spool file */
    unlink(shfn);
    unlink(sdfn);
  } else {
        
    /* on success delete outgoing spool file */
    unlink(oshfn);
    unlink(osdfn);
  }
      
  /* set old time stamps */
  utb.actime=utb.modtime=finfo.st_mtime;
  utime(shfn,&utb);
  utime(sdfn,&utb);

  seteuid(0);
  setegid(0);
  chdir(cwd);
  return(0);
}


/*
 * check_userspool - check if user is allowed to use sendfile and create the
 *                   user spool directories
 *
 * INPUT:  user	- user login name
 * 
 * RETURN: 0 if ok, -1 if failed
 */
int check_userspool(char *user, int userconfighome) {
  int allowfound;	/* flag if the allow file is not empty */
  char 
    *cp,		/* simple character pointer */
    tmp[MAXLEN], 	/* temporary string */
    luser[MAXLEN];	/* check local user */
  FILE 
#ifdef NEXT
    *pp,		/* pipe stream */
#endif
    *inf;		/* for various files */
  struct passwd *pwe;	/* password entry struct */
  struct stat finfo;	/* information about a file */
  
  allowfound=0;
  
  if (test) return(-3);
  
  /* look in the sendfile allow file */
  if ((inf=rfopen(ALLOW,"r"))) {

    while (fgetl(luser,inf)) {
      if ((cp=strchr(luser,'#'))) *cp=0;
      str_trim(luser);
      if (*luser) {
	allowfound=1;
	if (str_eq(luser,user)) break;
      }
    }
    
    fclose(inf);

    /* is the user not in the allow list? */
    if (allowfound && !str_eq(luser,user)) {
      reply(521);
      *user=0;
      return(-1);
    }
  }
      
  /* look in the sendfile disallow file if the allow file is empty */
  if (!allowfound && (inf=rfopen(DENY,"r"))) {

    while (fgetl(luser,inf)) {
      if ((cp=strchr(luser,'#'))) *cp=0;
      str_trim(luser);

      /* is the user in the deny list? */
      if (str_eq(luser,user)) {
	reply(521);
	*user=0;
	return(-1);
      }
	
    }
    fclose(inf);
  }

  if (test) {
    ruid=rgid=auth=0;
    strcpy(userspool,".");
    *userconfig=0;
    return(0);
  }
    
  /* get user information */
  pwe=NULL;
#ifdef NEXT
  /* stupid NeXT has a broken getpwnam(); this is a dirty workaround */
  snprintf(MAXS(tmp),"( nidump passwd . ; nidump passwd / ) | "
              "awk -F: '$1==\"%s\"{print $3,$4;exit}'",user);
  pp=popen(tmp,"r");
  if (fgetl(tmp,pp) && *tmp!='\n' && *tmp!=0) {
    pwe=(struct passwd *) malloc(sizeof(struct passwd));
    sscanf(tmp,"%d %d",&ruid,&rgid);
    pwe->pw_uid=ruid;
    pwe->pw_gid=rgid;
  }
  pclose(pp);
#else
  pwe=getpwnam(user);
#endif

  /* no such user? */
  if (pwe==NULL) {
    reply(520);
    *user=0;
    return(-1);
  }

  /* going to change ruid/rgid, so reset auth flag */
  auth=0;
  ruid=pwe->pw_uid;
  rgid=pwe->pw_gid;

  /* build user spool string */
  user[32]=0;
  snprintf(MAXS(userspool),SPOOL"/%s",user);
  snprintf(MAXS(userconfig),"%s/config",userspool);

  /* create user spool directory for user */
  if (mkdir(userspool,S_IRUSR|S_IWUSR|S_IXUSR)==0) chown(userspool,ruid,rgid);
  
  setreugid();
  
  /* create user config directory in $HOME? */
  if (stat(userconfig,&finfo)<0) {
    if (userconfighome) {
      snprintf(tmp,MAXLEN-8,"%s/.sendfile",pwe->pw_dir);
      mkdir(tmp,S_IRUSR|S_IWUSR|S_IXUSR);
      symlink(tmp,userconfig);
      strcat(tmp,"/spool");
      symlink(userspool,tmp);
    } else
      mkdir(userconfig,S_IRUSR|S_IWUSR|S_IXUSR);
  }
  
  seteuid(0);
  setegid(0);

  return(0);
}


/* 
 * copy2pipe - copy a file to a pipe descriptor
 * 
 * INPUT:  from  - input file name
 * 
 * OUTPUT: pfd   - pipe descriptor
 * 
 * RETURN: 0 on success, -1 on failure
 */
int copy2pipe(const char *from, int pfd) {
  int fdin;		/* input file descriptor */
  int bytes;            /* read bytes */
  int status;		/* error status */
  unsigned long blksize;/* file system block size */
  char *buf;		/* copy buffer */
  struct stat finfo;    /* information about a file */

  status=0;
  
  /* open source file */
  fdin=open(from,O_RDONLY,0);
  if (fdin<0) return(-1);
  
  /* get file system block size for copy operation */
  stat(from,&finfo);
#ifdef HAVE_ST_BLKSIZE
  blksize=finfo.st_blksize;
#else
  blksize=1024;
#endif
  
  /* ANSI C can not dynamicly allocate with buf[blksize] */
  buf=(char *)malloc(blksize);
  if (!buf) return(-1);
  
  /* read until EOF */
  while ((bytes=read(fdin,buf,blksize)) > 0) {
    
    /* write to destination file */
    if (write(pfd,buf,bytes)<0) {
		
      /* write error */
      status=-1;
      break;

    }
  }
  
  close(fdin);
  free(buf);
  return(status);
}


/*
 * openlog  - open user log file
 * 
 * INPUT:  log - name of log file
 * 
 * RETURN: log file descriptor
 * 
 * Be sure to call this function with correct seteuid/setegid settings!
 */
int openlog(const char *log) {
  int 
    n,			/* simple loop counter */
    lfd;		/* log file descriptor */
  char tmp[MAXLEN]; 	/* temporary string */
  struct stat finfo;	/* information about a file */

  /* security check */
  if (rgid!=getegid()) { if (setegid(rgid)<0) return(0); }
  if (ruid!=geteuid()) { if (seteuid(ruid)<0) return(0); }
  
  /* create logfile if not there */
  close(open(log,O_CREAT|O_EXCL,S_IRUSR|S_IWUSR));

  /* try several times to lock-write the logfile */
  lfd=open(log,O_WRONLY|O_APPEND);
  for (n=1; n<5; n++) {
    if (wlock_file(lfd) >= 0) {
	  
      /* add header to empty log file */
      stat(log,&finfo);
      if (finfo.st_size==0) { 
	strcpy(tmp,"# use utf7decode to view this file\n\n");
	write(lfd,tmp,strlen(tmp));
      }
    }
    return(lfd);
  }
  
  return(0);
}


/*
 * send_file - send a file to stdout
 *
 * INPUT:  fd	- file descriptor
 *
 * RETURN: number of transfered bytes, -1 if transfer failed
 */
int send_file(int fd) {
  int bytes;			/* number of read bytes */
  unsigned long tbytes;		/* total number of bytes which has been sent */
  char packet[OVERSIZE];	/* data packet to send */
  
  /* fallback */
  if (packet_size<1) packet_size=512;

  /* send the file data in packets */
  bytes=0;
  tbytes=0;
  while ((bytes=read(fd,packet,packet_size))>0) {
    alarm(timeout);
    write(fileno(stdout),packet,bytes);
    tbytes+=bytes;
  }

  if (bytes<0)
    return(bytes);
  else
    return(tbytes);
}


/* 
 * dbgout  - write debug output
 * 
 * INPUT: line  - one line of text
 */
void dbgout(const char *line) {
  if (dbf) {
    fprintf(dbf,"(%d) >%s<\n",(int)getpid(),line);
    fflush(dbf);
  }
}


/*
 * 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);

  cleanup();
  exit(0);
}


/*
 * timeoutexit  - clean up after timeout
 */
void timeoutexit() {
  reply(429);
  cleanexit();
}


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

#ifndef DEBUG
  if (*chfile) unlink(chfile);
  if (*csfile) unlink(csfile);
#endif

}



/*
 * setreugid - set effective uid and gid for recipient
 * 
 * RETURN: nothing, but terminates program on error
 */
void setreugid() {
  if (rgid != getegid())
  if (rgid && setegid(rgid)<0) {
    printf("490 Internal error on setegid(%u): %s\r\n",
	   (unsigned int)rgid,strerror(errno));
    exit(1);
  }
  if (ruid != geteuid())
  if (ruid && seteuid(ruid)<0) {
    printf("490 Internal error on seteuid(%u): %s\r\n",
	   (unsigned int)ruid,strerror(errno));
    exit(1);
  }
}


/*
 * sudo - emulate the shell su command:
 *        execute a command in an async subprocess owned by another user
 * 
 * INPUT: user  - login name
 *        cmd   - shell command to execute 
 * 
 * RETURN: 0 on success, <0 on failure
 */
int sudo(const char *user, const char *cmd) {
  pid_t pid;
  char tmp[MAXLEN];		/* the command itself */
  struct passwd *pwe;		/* password entry struct */
  
  /* get user name */
  if (!(pwe=getpwnam(user))) return(-3);
  
  /* security check: disallow superuser/group */
  if (pwe->pw_uid==0 || pwe->pw_gid==0) return(-2);
  
  /* spawn subprocess */
  signal(SIGCHLD,SIG_IGN);
  pid=fork();
  if (pid<0) return(-1);
  if (pid)   {sleep(1); return(0);}

  /* change user */
  /* note: setuid does not work if euid>0 ! STUPID! */
  chdir(pwe->pw_dir);
  seteuid(0);
  setegid(0);
  if (setgid(pwe->pw_gid)<0) exit(1);
  initgroups(user,pwe->pw_gid);
  if (setuid(pwe->pw_uid)<0) exit(1);

  /* set some usefull environment variables */
  snprintf(MAXS(tmp),"HOME=%s",pwe->pw_dir);
  putenv(tmp);
  snprintf(MAXS(tmp),"SHELL=%s",pwe->pw_shell);
  putenv(tmp);
  snprintf(MAXS(tmp),"USER=%s",user);
  putenv(tmp);
  snprintf(MAXS(tmp),"LOGNAME=%s",user);
  putenv(tmp);
  putenv("TERM=");
  
  /* call external program */
  dbgout(cmd);
  system(cmd);
  
  exit(0);
  return(0);
}


/*
 * usage - print short help usage text
 */
int usage() {
  fprintf(stderr,"\n");
  fprintf(stderr,"usage: %s [OPTIONS]  (normally %s is called by inetd!)\n",prg,prg);
  fprintf(stderr,"options: -d        debug mode on, output goes to %s\n",DBF);
  fprintf(stderr,"         -f        print free space in MB on spool partition, then exit\n");
  fprintf(stderr,"         -q        process outgoing spool queue one time\n");
  fprintf(stderr,"         -Q        run as outgoing spool daemon\n");
  fprintf(stderr,"         -c file   alternate configuration file\n");
  fprintf(stderr,"see also: sfdconf\n");
  return(2);
}


syntax highlighted by Code2HTML, v. 0.9.1