/* * File: sendfile.c * * Author: Ulli Horlacher (framstag@rus.uni-stuttgart.de) * * Contrib.: Rainer Bawidamann (widi@sol.wohnheim.uni-ulm.de) * Martin Buck (Martin-2.Buck@student.uni-ulm.de) * Heiko Schlichting (heiko@fu-berlin.de) * Christoph 'GNUish' Goern (goern@janus.beuel.rhein.de) * Stefan Scholl (stesch@parsec.inka.de) * Michael Neumayer (eumel@42.org) * * History: * * 1995-08-11 Framstag initial version * 1995-08-12 Framstag elm alias support * 1995-09-10 Framstag added delete and resend function * 1996-02-06 Framstag added ATTR EXE * 1996-02-07 Framstag check for already compressed files * 1996-02-20 Framstag follow forward address if given * 1996-02-21 widi better Solaris-2 support * 1996-02-22 Framstag added bouncing (forwarding) of files * 1995-02-23 mbuck bug fixes for getting $HOME * 1995-02-27 Framstag added quiet options * 1995-03-08 Framstag catch up signals in cleanup() * 1995-03-17 Framstag set umask (for tmp-files) * 1995-03-23 Framstag added percentage output * 1995-03-24 Framstag $TMPDIR replaces $HOME for tmp-files * 1996-03-28 Framstag extended search for support programs * 1996-04-02 Framstag added forward address to COMMENT * 1996-04-06 Framstag changed transaction message format * added overwrite option * 1996-04-10 Framstag better usage text * 1996-04-12 Framstag added pgp support * 1996-04-16 Framstag better pgp signature creation * 1996-04-17 Framstag new error handling for open_connection * 1996-04-18 Framstag verbose mode displays system() commands * allowed multiple IDs for pgp encryption * 1996-04-20 Framstag added pgp IDEA encryption option * 1996-04-24 Framstag changed bouncing option syntax * 1996-05-08 Framstag fixed bug when bouncing * 1996-05-10 Framstag allowed multiple forwards * 1996-05-14 Framstag moved send_data() to net.c * 1996-05-21 Framstag fixed bug when sending archive * 1996-05-23 Framstag added check for writeable tmp-files * added -P option (read from stdin) * 1996-08-13 Framstag fixed wrong "(compressed)" output * 1996-09-04 Heiko some fixes for IRIX * 1996-09-11 Heiko fixed redirection comment bug * 1996-11-19 Framstag fix for broken AIX include-files * 1997-01-04 Framstag moved check_forward() to address.c * 1997-01-20 Framstag fixed bug with TEXT=charset attribute * 1997-01-20 GNUish modified to move to gnu-style * 1997-01-25 Framstag added -X option (extended headers) * better usage text * better pgp parsing * 1997-02-01 Framstag set default quiet mode 1 on dump ttys * 1997-02-14 GNUish added long options * 1997-02-23 Framstag modified str_* function names * extended with TYPE=MIME * 1997-02-24 Framstag sprintf() -> snprintf() * 1997-03-24 stesch fixed buffering bug with -X option * 1997-05-15 Framstag added -c option usage text * 1997-05-16 Framstag added file type guessing * 1997-05-17 Framstag better file type guessing * 1997-06-04 Framstag added SF_TMPDIR * 1997-06-15 Framstag added -T option for file reading test * added new line output in cleanexit() * 1997-06-17 Framstag added packet_size config option * 1997-06-30 Framstag better file type guessing * 1997-07-04 Framstag auto dection of pgp ID for encryption * 1997-08-20 Framstag new syntax with option -a=name-of-archive * 1997-08-21 Framstag better handling of reply code 202 * 1997-12-05 Eumel fixed file type handling bug * 1997-12-11 Framstag added bzip2 support * 1997-12-14 Framstag fixed bug when sending to /dev/null * 1997-12-15 Framstag added link speed test for auto-compression * 1997-12-19 Framstag added -W option * 1998-01-05 Framstag reactivated outgoing logging * 1998-01-17 Framstag check SAFT-URL for alternative tcp port * 1998-01-22 Framstag check compression method when bouncing * 1998-02-27 Framstag fixed small bug in guess_ftype * more verbose transfer statistics * 1998-03-01 Framstag changed /dev/null testing recipient to :NULL: * 1998-03-07 Framstag better detection of non-interactive mode * added -i option for more transfer information * 1998-03-08 Framstag fixed outlog deletion bug * 1998-03-16 Framstag fixed error loop bug in cleanup() * 1998-03-20 Framstag fixed archive+overwrite bug * 1998-08-21 Framstag added maximum thruput option * 1998-09-23 Framstag continue sending on tar errors * 1998-11-05 Framstag do not compress files less than 1 KB * 1998-11-12 Framstag print "spooled" information with size * fixed spool+archive+overwrite bug * 1999-08-03 Framstag gzip is default compression, back again * 2001-01-10 Framstag fopen() ==> rfopen() * 2001-02-04 Framstag added secure mktmpdir() * * The sendfile client of the sendfile package. * Sends one or more files to the sendfiled of the destination system. * * Copyright © 1995-2001 Ulli Horlacher * This file is covered by the GNU General Public License */ #include "config.h" /* autoconf header */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "string.h" /* extended string functions */ #include "net.h" /* the network routines */ #include "io.h" /* socket and file IO extensions */ #include "message.h" /* information, warning and error messages */ #include "spool.h" /* operations on files in the sendfile spool */ #include "utf7.h" /* UTF-7 coding */ #include "address.h" /* address checking */ #if defined(HAVE_GETOPT_H) #include #else int getopt(int, char * const *, const char *); extern int opterr; extern int optind; extern int optopt; extern char *optarg; #endif #if defined(SOLARIS2) int gethostname(char *, int); #endif #if defined(LINUX) int gethostname(char *, size_t); #endif #ifndef AIX3 #ifndef CONVEXOS FILE *popen(const char *, const char *); #endif int pclose(FILE *); #endif #ifdef NEXT int shutdown(int, int); #endif #if defined(AIX3) || defined(ULTRIX) # include "bsd.h" #endif /* print short help usage text */ int usage(); /* clean termination routine */ void cleanexit(); /* delete tmp-file and send LOG command to local server */ void cleanup(); /* encrypt a file with pgp */ void pgp_encrypt(int, char *, char *); /* create detached pgp signature file and send SIGN header command */ void pgp_sign(const char *, const char *, int); /* create and open outgoing spool header file */ FILE *outspool(const char *, const char *, char *); /* start local spool daemon for outgoing files */ void start_spooldaemon(char *); /* create temporary user outgoing log file */ void outlog(char *, char *, char *, char *); /* forward a file with complete header from stdin */ void forward(char *, float); /* read a header line from stdin */ void get_header(const char *, char *); /* guess file type */ char guess_ftype(const char *, char *); /* check if link to host is fast enough */ int linkspeed(const char *, int, char **); /* note the link speed for later processing */ void notespeed(const char *, unsigned long, float); /* list files in outgoing spool */ int list_spool(); /* print information about spooled file */ void spooled_info(const char *, const char *, int); /* global variables */ int pgppass=0, /* flag if the pgp password is set as an env variable */ verbose=0, /* flag for verbose mode */ client=1, /* flag to determine client or server */ quiet=0, /* quiet mode flag */ test=0, /* flag for file reading testing (send to /dev/null) */ outlogging=0, /* flag logging outgoing files */ packet_size=0; /* size of a packet in bytes */ char *prg, /* name of the game */ *pgpvm, /* pgp verbose mode string */ *tmpdir, /* directory for temporary files */ *dontcompress[99], /* list of file extensions which are not compressible */ pw_name[FLEN], /* own user name */ localhost[FLEN], /* name of the local host */ outlogtmp[MAXLEN], /* user temporary outgoing log file */ userspool[MAXLEN], /* user spool directory */ tar_bin[MAXLEN], /* the tar binary */ gzip_bin[MAXLEN], /* the gzip binary */ bzip2_bin[MAXLEN], /* the bzip2 binary */ zprg[MAXLEN], /* the compress programm (either gzip or bzip2) */ pgp_bin[MAXLEN], /* the pgp binary */ stdintmp[MAXLEN], /* name of stdin temporary file */ tartmp[MAXLEN], /* name of tar temporary file */ ziptmp[MAXLEN], /* name of gzipped temporary file */ texttmp[MAXLEN], /* name of text temporary file in NVT telnet format */ pgptmp[MAXLEN]; /* name of pgp temporary file */ int main(int argc, char *argv[]) { int i,n, /* simple loop count */ pid, /* current proccess id */ status, /* return codes */ sockfd, /* socket file descriptor */ opt, /* option to test for */ fn, /* file number in argv */ tfn, /* total file number sent */ stdinf, /* read file from stdin */ ch, /* character to read in from file */ lanspeed, /* speed in kB/s which defines what is in the LAN */ pgpcrypt, /* pgp public key or IDEA encryption */ del, /* flag for deleting previous sent files */ tar, /* flag for sending files as tar archive */ zip, /* flag for sending files as zip archive */ exe, /* flag for sending executable */ pgp, /* flag for pgp encoding */ guess, /* flag for file type guessing */ text, /* flag for sending text file */ mime, /* flag for sending mime file */ source, /* flag for sending source code file */ spool, /* flag for do outgoing spooling */ list, /* flag for listing files in outgoing spool */ info, /* flag for information mode */ bounce, /* flag for bouncing files */ extended, /* flag for extended command parsing */ spooling, /* flag for spooling allowed */ overwrite, /* flag for overwriting already sent files */ do_compress; /* flag for really do compressing */ unsigned long size, /* size of file to send */ orgsize; /* original file size uncompressed */ float mtp, /* maximum thruput limit */ tsize, /* total size of all sent files */ attime, /* actual transfer time */ tttime, /* total transfer time */ thruput; /* total net throughput */ char *cp, /* simple string pointer */ *argp, /* argument string pointer */ *pop, /* pgp option string pointer */ *fnp, /* file name pointer */ *type, /* type of file transfer */ *compress, /* the compression methode */ mode, /* guessed file mode */ to[2*FLEN], /* user@host from argv */ file[FLEN], /* name of file to send */ tinfo[FLEN], /* transfer information */ sdfn[FLEN], /* spool data file name */ shfn[FLEN], /* spool header file name */ rto[2*FLEN], /* user@host from spool header file */ ftype[FLEN], /* file type mode */ archive[FLEN], /* name of archive file */ where[FLEN], /* where is userspool or sendfile.cf */ aopt[FLEN], /* alias options */ recipient[FLEN], /* recipient at serverhost */ bouncearg[DLEN], /* bouncing files argument */ sizes[FLEN], /* original and compressed file size */ user[FLEN], /* local user name */ date[DLEN], /* date of file */ host[FLEN], /* name of serverhost */ pgpopt[FLEN], /* options for pgp */ pgprid[FLEN], /* pgp recipient id */ pgpsign[FLEN], /* pgp signature option */ redirect[MAXLEN], /* redirected comment */ cmd[MAXLEN], /* cmd string for system-call */ line[MAXLEN], /* one line of text */ reply[MAXLEN], /* reply from the server */ tmp[MAXLEN], /* temporary string */ comment[MAXLEN], /* file comment */ outgoing[MAXLEN], /* outgoing spool directory */ oshfn[MAXLEN], /* outgoing spool header file name */ osdfn[MAXLEN], /* outgoing spool data file name */ filelist[OVERSIZE], /* list of files for tar command */ force_compress[MAXLEN]; /* force compress with special programm */ const char *cft[]= /* file types which are not compressible (output from file(1) command */ { "compress","zip","zoo","frozen","gif","jpg","jpeg","mpg","mpeg","" }; FILE *shf, /* spool header file */ *oshf, /* outgoing spool header file */ *inf, /* input file */ *outf; /* output file */ struct passwd *pwe; /* password entry */ struct stat finfo; /* information about a file */ char utf_name[LEN_UNI], /* name in UTF-7 format */ iso_name[LEN_ISO]; /* name in ISO Latin-1 format */ struct hostlist *hls, /* host list start */ *hlp; /* host list pointer */ struct outfilelist *oflp; /* outgoing file list pointer */ /* HAVE_GETOPTLONG_H is dead code for sendfile! */ #if defined(HAVE_GETOPTLONG_H) static struct option long_options[] = { { "version", 0, 0, 'V' }, { "help", 0, 0, 'h' }, { "delete", 0, 0, 'd' }, { "text", 0, 0, 't' }, { "spool", 0, 0, 'S' }, { "quiet", 0, 0, 'q' }, { "real-quiet", 0, 0, 'Q' }, { "source", 0, 0, 's' }, { "mime", 0, 0, 'm' }, { "verbose", 0, 0, 'v' }, { "uncompressed", 0, 0, 'u' }, { "overwrite", 0, 0, 'o' }, { "comment", 1, 0, 'c' }, { "extended", 1, 0, 'X' }, { "archive", 1, 0, 'a' }, { "bounce", 1, 0, 'b' }, { "stdin", 0, 0, 'P' }, { "pgp-sign", 0, 0, 'ps' }, { "pgp-encrypt", 1, 0, 'p' } }; int long_index = 0; #endif mtp=0; tfn=0; del=0; tar=0; zip=0; exe=0; pgp=0; text=0; spool=0; quiet=0; mime=0; list=0; info=0; guess=0; tsize=0; source=0; bounce=0; sockfd=0; stdinf=0; tttime=0; thruput=0; verbose=0; extended=0; spooling=0; pgpcrypt=0; lanspeed=100; overwrite=0; do_compress=0; *aopt=0; *host=0; *date=0; *zprg=0; *where=0; *tinfo=0; *comment=0; *archive=0; *redirect=0; *filelist=0; *pgprid=0; *pgpopt=0; *pgpsign=0; *force_compress=0; dontcompress[0]=""; type="BINARY"; compress=S_GZIP; oshf=NULL; pid=(int)getpid(); prg=argv[0]; if ((cp=strrchr(prg,'/'))) prg=cp+1; /* switch off buffering for STDIN for -X option */ setvbuf(stdin, NULL, _IONBF, 0); if (getenv("PGPPASS")) { pgppass=1; pgpvm="+verbose=0"; } else { pgppass=0; pgpvm="+verbose=1"; } /* scann the command line on options */ #if defined(HAVE_GETOPTLONG_H) while ((opt=getopt_long(argc, argv, "Vh?dtmSqQsvuoc:X:a:b:p:", long_options, &long_index)) > 0) #else while ((opt=getopt(argc, argv, "ivVTgtsuqoMQPdSlh?a:c:p:b:m:X:C:W:")) > 0) #endif { switch (opt) { case ':': case 'h': case '?': exit(usage()); case 'd': del=1; break; case 'g': guess=1; break; case 't': text=1; break; case 'T': test=1; break; case 'S': spool=1; break; case 'l': list=1; break; case 'i': info++; break; case 'q': quiet++; break; case 'Q': quiet=2; break; case 'P': stdinf=1; break; case 's': source=1; break; case 'M': mime=1; break; case 'm': mtp=atof(optarg); if (mtp<0) mtp=0; break; case 'v': verbose++; break; case 'u': compress=""; break; case 'C': if (*optarg == '=') strcpy(force_compress,optarg+1); else strcpy(force_compress,optarg); break; case 'o': overwrite=1; break; case 'c': if (*optarg == '=') strcpy(comment,optarg+1); else strcpy(comment,optarg); break; case 'a': tar=1; strcpy(archive,optarg); break; case 'A': zip=1; strcpy(archive,optarg); break; case 'b': bounce=1; strcpy(bouncearg,optarg); break; case 'X': extended=1; strcpy(host,optarg); break; case 'W': if (*optarg == '=') strcpy(where,optarg+1); else strcpy(where,optarg); break; case 'p': pgp=1; snprintf(tmp,FLEN-1,"%s\n%s",pgpopt,optarg); strcpy(pgpopt,tmp); break; case 'V': message(prg,'I',"version "VERSION" revision "REVISION); exit(0); } } /* too few arguments? */ if (argc-optind<2 && !extended && !list && !*where) { if (argc-optind<1) exit(usage()); errno=0; message(prg,'F',"too few arguments: " "you must specify at least a file name and recipient"); } /* non-interactive usage without a tty? */ if (!quiet) { cp=getenv("TERM"); if (!cp || !*cp || strstr("tty|dumb",cp)) quiet=1; } /* incompatible options? */ if (test && spool) { errno=0; message(prg,'F',"you cannot specify :NULL: and -S option together"); } if (extended) { if (strchr(host,'@')) { errno=0; message(prg,'F',"you must specify only a host name with the -X option"); } if (del||text||spool||stdinf||source||mime||overwrite||tar||bounce||pgp|| *comment) { if (quiet<2) message(prg,'W',"you cannot use any other option with " "the extended header option - ignored"); del=text=spool=stdinf=source=mime=overwrite=tar=bounce=pgp=0; *comment=0; } } if (bounce) { if (!(str_eq(bouncearg,"k=y") || str_eq(bouncearg,"k=n"))) { errno=0; message(prg,'F',"wrong bouncing argument"); } if (source||mime||text||tar||del|stdinf) if (quiet<2) message(prg,'W',"you cannot use any other option " "when bouncing a file - ignored"); text=source=mime=tar=del=stdinf=0; compress=""; } if (del) { if (source||mime||text||tar||overwrite||stdinf||pgp||*comment) if (quiet<2) message(prg,'W',"you cannot use any other option " "when deleting a file - ignored"); text=source=mime=tar=stdinf=0; compress=""; } if (guess) { if (source||mime||text||tar) if (quiet<2) message(prg,'W',"you cannot use source, text, mime or " "archive option when guessing the file " "type - ignored"); text=source=mime=tar=0; } if (stdinf) { if (tar) if (quiet<2) message(prg,'W',"you cannot send stdin as an archive file; " "-a option will be ignored"); tar=0; } if (tar&&zip) { errno=0; message(prg,'F',"you cannot use the -a and -A archive options together"); } if (tar||zip) { if (source) if (quiet<2) message(prg,'W',"option SOURCE is not allowed when " "sending in archive format - ignored"); if (mime) if (quiet<2) message(prg,'W',"option MIME is not allowed when " "sending in archive format - ignored"); if (text) if (quiet<2) message(prg,'W',"option TEXT is not allowed when " "sending in archive format - ignored"); text=source=mime=0; } /* correct archive option? */ if (*archive) { if (*archive == '=') { strcpy(tmp,archive+1); strcpy(archive,tmp); } else { errno=0; message(prg,'F',"you have not specified an archive " "name with -a=name-of-archive"); } } /* correct comment? */ if (*comment && argc-optind>2 && !tar) { errno=0; message(prg,'F',"you can only comment a single file"); } /* protect all tmp-files */ umask(~(S_IRUSR|S_IWUSR)); /* support programs defaults */ strcpy(tar_bin,TAR); strcpy(pgp_bin,PGP); strcpy(gzip_bin,GZIP); strcpy(bzip2_bin,BZIP2); /* look for environment variables */ if ((cp=getenv("SF_TAR"))) strcpy(tar_bin,cp); if ((cp=getenv("SF_PGP"))) strcpy(pgp_bin,cp); if ((cp=getenv("SF_GZIP"))) strcpy(gzip_bin,cp); if ((cp=getenv("SF_BZIP2"))) strcpy(bzip2_bin,cp); /* do the support programs really exist? */ if (access(tar_bin,X_OK)<0) strcpy(tar_bin,"tar"); if (access(pgp_bin,X_OK)<0) strcpy(pgp_bin,"pgp"); if (access(gzip_bin,X_OK)<0) strcpy(gzip_bin,"gzip"); if (access(bzip2_bin,X_OK)<0) strcpy(bzip2_bin,"bzip2"); /* determine which compress programm to use */ if (*force_compress) { compress=""; if (strstr(force_compress,"gzip")) compress=S_GZIP; if (strstr(force_compress,"bzip2")) compress=S_BZIP2; if (!*compress) { snprintf(MAXS(tmp),"unsupported compression program %s",force_compress); errno=0; message(prg,'F',tmp); } strcpy(zprg,force_compress); } else if (*compress) { #if 0 snprintf(MAXS(tmp),"%s --help 2>&1",bzip2_bin); if ((pp=popen(tmp,"r"))) { while (fgetl(line,pp)) { if (strstr(line,"usage:")) { strcpy(zprg,bzip2_bin); break; } } pclose(pp); } /* is bzip2 available? */ if (!spool && whereis(bzip2_bin)) { strcpy(zprg,bzip2_bin); compress=S_BZIP2; } if (!*zprg) *bzip2_bin=0; snprintf(MAXS(tmp),"%s --help 2>&1",gzip_bin); if ((pp=popen(tmp,"r"))) { while (fgetl(line,pp)) if (strstr(line,"usage:")) break; pclose(pp); } if (strstr(line,"usage:")) { if (!*zprg) strcpy(zprg,gzip_bin); } else *gzip_bin=0; #endif /* is gzip available? */ if (whereis(gzip_bin) && !*zprg) { strcpy(zprg,gzip_bin); compress=S_GZIP; } if (!*zprg) { compress=""; if (quiet<2) message(prg,'W',"no compression program found - sending uncompressed"); } } /* get the local host name */ if (gethostname(localhost,FLEN-1)<0) strcpy(localhost,"localhost"); /* extended header feature = read everything from stdin? */ if (extended) { forward(host,mtp); exit(0); } /* get own user name, recipient name and host and alias options */ destination(argc,argv,user,recipient,host,aopt); /* printf("recipient: %s\nhost: %s\noptions: %s\n",recipient,host,aopt); */ /* name the local host */ if (str_eq(host,"127.0.0.1") || str_eq(host,"0")) strcpy(host,localhost); if (*aopt) { snprintf(MAXS(cmd),"%s %s ",argv[0],aopt); for(i=1;ipw_name); tmpdir=mktmpdir(verbose); /* check pgp options */ if (pgp) { pop=pgpopt; /* conventional IDEA encryption? */ if (str_eq(pop,"\nc")) { pgpcrypt='c'; compress=""; } else { /* check other pgp options */ while (*pop) { pop++; /* pgp encryption? */ if (*pop=='e') { pgpcrypt='e'; compress=""; pop++; snprintf(MAXS(pgprid),"%s@%s",recipient,host); /* is there a recipient id? */ if (*pop>'\n') { if (*pop=='=') strcpy(pgprid,++pop); /* cut off any more options */ if ((cp=strchr(pgprid,'\n'))) { *cp=0; pop=strchr(pop,'\n'); } else *pop=0; } /* if (!*pgprid) { errno=0; message(prg,'F',"you have to specify a pgp recipient-ID " "when encrypting"); } */ continue; } /* pgp signature? */ if (*pop=='s') { strcpy(pgpsign," "); pop++; /* is there a signature id? */ if (*pop>'\n') { if (*pop=='=') pop++; snprintf(MAXS(pgpsign),"-u '%s",pop); /* cut off any more options */ if ((cp=strchr(pgpsign,'\n'))) { *cp=0; pop=strchr(pop,'\n'); } else *pop=0; strcat(pgpsign,"'"); } continue; } /* wrong pgp options */ errno=0; snprintf(MAXS(tmp),"wrong pgp option, see 'man %s'",prg); message(prg,'F',tmp); } } } /* set various file names */ snprintf(MAXS(userspool),SPOOL"/%s",pw_name); snprintf(MAXS(outlogtmp),"%s/.sendfile_%d.log",userspool,pid); snprintf(MAXS(tartmp),"%s/sendfile.tar",tmpdir); snprintf(MAXS(ziptmp),"%s/sendfile.zip",tmpdir); snprintf(MAXS(pgptmp),"%s/sendfile.pgp",tmpdir); snprintf(MAXS(texttmp),"%s/sendfile.txt",tmpdir); snprintf(MAXS(stdintmp),"%s/sendfile.tmp",tmpdir); /* where are the files/directories ? */ if (*where) { if (str_eq(where,"config") || str_eq(where,"sendfile.cf")) { if (quiet) printf(CONFIG"\n"); else message(prg,'I',"the global configuration file is: "CONFIG); } else if (str_eq(where,"spool")) { if (quiet) printf(SPOOL"\n"); else message(prg,'I',"the spool directory is: "SPOOL); } else if (str_eq(where,"userspool")) { if (quiet) printf("%s\n",userspool); else { snprintf(MAXS(tmp),"the user spool directory is: %s",userspool); message(prg,'I',tmp); } } else { snprintf(MAXS(tmp),"%s is an unknown -W argument",where); errno=0; message(prg,'E',tmp); if (quiet<2) message(prg,'I',"you may specify -W=config, -W=spool, or " "-W=userspool"); } if (argc-optind<1) exit(0); } /* check tmp files */ unlink(tartmp); unlink(ziptmp); unlink(texttmp); unlink(pgptmp); unlink(stdintmp); if (stat(tartmp,&finfo)==0) { snprintf(MAXS(tmp), "tmp-file %s does already exist and cannot be deleted",tartmp); message(prg,'F',tmp); } if (stat(ziptmp,&finfo)==0) { snprintf(MAXS(tmp), "tmp-file %s does already exist and cannot be deleted",ziptmp); message(prg,'F',tmp); } if (stat(texttmp,&finfo)==0) { snprintf(MAXS(tmp), "tmp-file %s does already exist and cannot be deleted",texttmp); message(prg,'F',tmp); } if (stat(pgptmp,&finfo)==0) { snprintf(MAXS(tmp), "tmp-file %s does already exist and cannot be deleted",pgptmp); message(prg,'F',tmp); } if (stat(stdintmp,&finfo)==0) { snprintf(MAXS(tmp), "tmp-file %s does already exist and cannot be deleted",stdintmp); message(prg,'F',tmp); } /* parse the global config-file */ if ((inf=rfopen(CONFIG,"r"))) { while (fgetl(line,inf)) { /* prepare line to be parsed */ if ((cp=strchr(line,'#'))) *cp=0; if ((cp=strchr(line,'='))) *cp=' '; str_tolower(str_trim(line)); /* is there an option and an argument? */ if ((argp=strchr(line,' '))) { *argp=0; argp++; if (str_eq(line,"packet")) { packet_size=atoi(argp); continue; } if (str_eq(line,"lanspeed")) { lanspeed=atoi(argp); if (lanspeed<0) lanspeed=0; continue; } if (str_eq(line,"log")) { if (str_eq(argp,"out") || str_eq(argp,"both")) outlogging=1; continue; } if (str_eq(line,"spooling")) { if (str_eq(argp,"nostart")) spooling=1; if (str_eq(argp,"on")) spooling=2; continue; } if (str_eq(line,"dontcompress")) { if (*argp) { /* save the whole string in element #0 */ if (!(dontcompress[0]=strdup(argp))) message(prg,'F',"out of memory"); /* parse and split the list (tricky, eh? :-) ) */ for (n=1,cp=strtok(dontcompress[0],", \t"); cp && n<99; n++,cp=strtok(NULL,", \t")) { /*printf("sfconf: >%s<\n",cp); */ dontcompress[n]=cp; } dontcompress[n]=""; /* last element must terminate the list! */ } continue; } } } fclose(inf); } /* spooling allowed? */ if ((spool || list) && !spooling) { errno=0; message(prg,'F',"outgoing spooling of files is not allowed on this system"); } /* list files in outgoing spool */ if (list) exit(list_spool()); /* set tcp packet length */ if (packet_size<1) packet_size=PACKET; if (verbose && !spool && !del) { snprintf(MAXS(tmp),"packet size = %d bytes",packet_size); message(prg,'I',tmp); } /* enable simple interrupt handler */ signal(SIGTERM,cleanexit); signal(SIGABRT,cleanexit); signal(SIGQUIT,cleanexit); signal(SIGHUP,cleanexit); signal(SIGINT,cleanexit); /* file as stdin data stream? */ if (stdinf) { /* write stdin to tmp-file */ if (!(outf=rfopen(stdintmp,"w"))) { snprintf(MAXS(tmp),"cannot open tmp-file %s",stdintmp); message(prg,'F',tmp); } while ((ch=getchar())!=EOF) putc(ch,outf); fclose(outf); } /* determine the file type */ type="BINARY"; if (text) type="TEXT="CHARSET; if (mime) type="MIME"; if (source) type="SOURCE"; /* no testing mode */ if (!test) { /* prepare sending or spooling */ if (!spool) { /* look for correct SAFT server and open connection */ sockfd=saft_connect("file",recipient,user,host,redirect); /* compression mode wanted? */ if (*compress) do_compress=1; if (do_compress && !*force_compress) do_compress=linkspeed(host,lanspeed,&compress); /* test if server can handle bzip2 */ if (do_compress && *zprg && strstr(zprg,"bzip2")) { sendcommand(sockfd,"TYPE BINARY COMPRESSED=BZIP2",reply); if (!str_beq(reply,"200 ") && *gzip_bin && !*force_compress) { compress=S_GZIP; strcpy(zprg,gzip_bin); } else compress=S_BZIP2; } } else /* outgoing spooling */ { /* does the outgoing spool exist? */ strcpy(outgoing,SPOOL"/OUTGOING"); if (stat(outgoing,&finfo)<0 || !S_ISDIR(finfo.st_mode)) { snprintf(MAXS(tmp),"spool directory %s does not exist",outgoing); message(prg,'F',tmp); } /* and does it have the correct protection? */ if (!((finfo.st_mode&S_ISVTX) && (finfo.st_mode&S_IRWXO))) { snprintf(MAXS(tmp), "spool directory %s has wrong protection (must have 1777)", outgoing); message(prg,'F',tmp); } /* name the local host */ if (str_eq(host,"127.0.0.1") || str_eq(host,"0")) strcpy(host,localhost); } } /* bouncing files? */ if (bounce) { /* does the spool directory exist? */ if (chdir(userspool)<0) { snprintf(MAXS(tmp),"cannot access spool directory %s",userspool); message(prg,'F',tmp); } /* main loop over the spool file names */ for (fn=optind; fn%s",BZIP2,sdfn,GZIP,ziptmp); else snprintf(MAXS(cmd),"%s -d < %s > %s",BZIP2,sdfn,ziptmp); } else snprintf(MAXS(cmd),"%s -dc %s > %s",GZIP,sdfn,ziptmp); /* execute shell-command and close spool header file on error */ if (system(cmd)) { snprintf(MAXS(tmp),"cannot recompress spool file #%s",argv[fn]); message(prg,'E',tmp); fclose(inf); break; } } continue; /* read next line from spool header file */ } /* store size for later processing */ if (str_beq(line,"SIZE ")) { sscanf(line,"SIZE %ld %ld",&size,&orgsize); strcpy(sizes,line+5); } /* is there already a comment line? */ if (str_beq(line,"COMMENT")) { if (*redirect) snprintf(MAXS(line),"%s+AA0ACg-%s",comment,redirect); else { snprintf(MAXS(tmp), "%s+AA0ACg-forward+ACA-from+ACA-%s",line,comment); strcpy(line,tmp); } if (spool) fprintf(oshf,"%s\n",line); else sendcommand(sockfd,line,NULL); *comment=0; continue; } /* all other header lines */ if (spool) fprintf(oshf,"%s\n",line); else sendheader(sockfd,line); } /* if file has been already closed there is an error (see above) */ if (fclose(shf)) { if (spool) fclose(oshf); continue; } /* send comment if not already done */ if (*comment) { iso2utf(tmp,"forward from "); snprintf(MAXS(line),"COMMENT %s%s",tmp,comment); if (*redirect) { snprintf(MAXS(tmp),"\r\n%s",redirect); iso2utf(comment,tmp); strcat(line,comment); } if (spool) fprintf(oshf,"%s\n",line); else sendcommand(sockfd,line,NULL); } /* check the file size */ if (stat(ziptmp,&finfo)==0) { size=finfo.st_size; snprintf(MAXS(sizes),"%ld %ld",size,orgsize); snprintf(MAXS(line),"SIZE %s",sizes); sendcommand(sockfd,line,NULL); } else { if (stat(sdfn,&finfo)<0 || size!=finfo.st_size) { snprintf(MAXS(tmp), "spool file #%s has wrong size count - ignored",argv[fn]); errno=0; message(prg,'E',tmp); if (spool) fclose(oshf); continue; } } /* copy spool file to outgoing */ if (spool) { fclose(oshf); strcpy(osdfn,oshfn); osdfn[strlen(osdfn)-1]='d'; if (fcopy(sdfn,osdfn,S_IRUSR|S_IWUSR)<0) { unlink(oshfn); unlink(osdfn); } else { if (str_eq(bouncearg,"k=n")) { unlink(shfn); unlink(sdfn); } strcpy(tmp,oshfn); oshfn[strlen(oshfn)-1]='h'; rename(tmp,oshfn); spooled_info(iso_name,osdfn,do_compress); } } else /* forward the spool data file */ { if (stat(ziptmp,&finfo)==0) { status=send_data(sockfd,size,ziptmp,tinfo,file,"",mtp,&attime); unlink(ziptmp); } else status=send_data(sockfd,size,sdfn,tinfo,file,"",mtp,&attime); /* summarize transfer statistics */ if (attime) { tfn++; tsize+=size; tttime+=attime; } if (status==0) { /* log data */ outlog(recipient,host,file,sizes); notespeed(host,size,attime); /* if not keep file delete the spool files */ if (str_eq(bouncearg,"k=n")) { unlink(shfn); unlink(sdfn); } } } } } else if (tar||zip) { /* sending tar or zip archive */ /* translate the archive name to UTF-7 */ iso2utf7(utf_name,archive,0); /* collect all files */ for (n=optind; n/dev/null\" 1 2 3;%s cvf %s %s", tartmp,tar_bin,tartmp,filelist); else { if (!quiet) printf("making archive...\r"); fflush(stdout); snprintf(MAXS(cmd),"trap \"rm -f %s 2>/dev/null\" 1 2 3;%s cf %s %s", tartmp,tar_bin,tartmp,filelist); } if (verbose) printf("%s\n",strchr(cmd,';')+1); if (system(cmd)) { message(prg,'E',"no complete archive file"); if (!quiet) { printf("Continue? "); fgetl(tmp,stdin); if (toupper(tmp[0])!='Y') exit(1); } } /* get the file name and size */ strcpy(file,tartmp); if (stat(file,&finfo)<0) message(prg,'F',"cannot access tmp file"); orgsize=finfo.st_size; size=orgsize; /* compression mode wanted? */ if (*compress) do_compress=1; /* compressed mode? */ if (do_compress) { /* compress tar-archive */ if (!quiet) printf("compressing... \r"); fflush(stdout); snprintf(MAXS(cmd),"trap \"rm -f %s %s 2>/dev/null\" 1 2 3;%s < %s > %s", tartmp,ziptmp,zprg,tartmp,ziptmp); if (verbose) { snprintf(MAXS(tmp),"shell-call: %s",strchr(cmd,';')+1); message(prg,'I',tmp); } if (system(cmd)) message(prg,'F',"cannot compress archive file"); strcpy(file,ziptmp); } /* pgp encryption? */ if (pgpcrypt) pgp_encrypt(pgpcrypt,pgprid,file); /* pgp signature to add? */ if (*pgpsign) pgp_sign(pgpsign,file,sockfd); /* get the file size */ if (stat(file,&finfo)<0) message(prg,'F',"cannot access tmp file"); size=finfo.st_size; snprintf(MAXS(sizes),"%ld %ld",size,orgsize); /* write to outgoing spool? */ if (spool) { /* overwrite archive file? */ if (overwrite && (hls=scanoutspool(pw_name))) { /* create correct to-string */ if (strchr(argv[argc-1],'*')) snprintf(MAXS(to),"%s",argv[argc-1]); else snprintf(MAXS(to),"%s@%s",recipient,host); /* search for file in outgoing spool */ for (hlp=hls; hlp; hlp=hlp->next) { for (oflp=hlp->flist; oflp; oflp=oflp->next) { /* matching file name? */ if (simplematch(oflp->fname,utf_name,0)) { /* matching recipient? */ snprintf(MAXS(rto),"%s@%s",oflp->to,hlp->host); if (simplematch(rto,to,0)) { unlink(oflp->oshfn); oflp->oshfn[strlen(oflp->oshfn)-1]='d'; unlink(oflp->oshfn); } } } } } /* open outgoing spool header file */ if ((oshf=outspool(pw_name,outgoing,oshfn))==NULL) message(prg,'F',"cannot create outgoing spool file"); /* TAB as whitespace is needed here, because scanspool insists on it */ fprintf(oshf,"FROM\t%s\n",user); fprintf(oshf,"TO\t%s@%s\n",recipient,host); fprintf(oshf,"FILE\t%s\n",utf_name); if (*compress) { if (str_eq(compress,S_GZIP)) fprintf(oshf,"TYPE\tBINARY COMPRESSED\n"); else fprintf(oshf,"TYPE\tBINARY COMPRESSED=%s\n",compress); } else if (pgpcrypt) { fprintf(oshf,"TYPE\tBINARY CRYPTED\n"); } else fprintf(oshf,"TYPE\tBINARY\n"); fprintf(oshf,"SIZE\t%s\n",sizes); fprintf(oshf,"ATTR\tTAR\n"); } else { /* send header lines */ if (overwrite) { snprintf(MAXS(tmp),"FILE %s",utf_name); sendcommand(sockfd,tmp,reply); if (str_beq(reply,"200 ")) sendcommand(sockfd,"DEL",reply); } snprintf(MAXS(tmp),"FILE %s",utf_name); sendcommand(sockfd,tmp,reply); if (!test && !str_beq(reply,"200 ") && quiet<2) message(prg,'W',"remote site does not support file names"); if (*compress) { if (str_eq(compress,S_GZIP)) snprintf(MAXS(tmp),"TYPE BINARY COMPRESSED"); else snprintf(MAXS(tmp),"TYPE BINARY COMPRESSED=%s",compress); sendcommand(sockfd,tmp,reply); if (!test && !str_beq(reply,"200 ") && quiet<2) { errno=0; message(prg,'F',"remote site does not support compressed files"); } } else if (pgpcrypt) { sendcommand(sockfd,"TYPE BINARY CRYPTED",reply); if (!test && !str_beq(reply,"200 ") && quiet<2) { errno=0; message(prg,'F',"remote site does not support encrypted files"); } } else { sendcommand(sockfd,"TYPE BINARY",reply); if (!test && !str_beq(reply,"200 ") && quiet<2) message(prg,'W',"remote site does not support binary files"); } snprintf(MAXS(tmp),"SIZE %s",sizes); sendheader(sockfd,tmp); sendcommand(sockfd,"ATTR TAR",reply); if (!test && !str_beq(reply,"200 ") && quiet<2) { errno=0; message(prg,'F',"remote site does not support archive file type"); } } /* comment to send? */ if (*comment || *redirect) { *line=0; if (*comment) strcat(line,comment); if (*redirect) { if (*line) { snprintf(MAXS(tmp),"%s\r\n%s",line,redirect); strcpy(line,tmp); } else strcpy(line,redirect); } iso2utf(tmp,line); if (spool) fprintf(oshf,"COMMENT\t%s\n",tmp); else { snprintf(MAXS(line),"COMMENT %s",tmp); sendcommand(sockfd,line,NULL); } } /* send the file data */ if (spool) { fclose(oshf); strcpy(osdfn,oshfn); osdfn[strlen(osdfn)-1]='d'; if (fcopy(file,osdfn,S_IRUSR|S_IWUSR)<0) { unlink(oshfn); unlink(osdfn); } else { strcpy(tmp,oshfn); oshfn[strlen(oshfn)-1]='h'; rename(tmp,oshfn); spooled_info(archive,osdfn,do_compress); } } else { strcpy(tmp,"archive"); if (*compress) strcat(tmp," compressed"); if (pgpcrypt) strcat(tmp," crypted"); if (send_data(sockfd,size,file,"",archive,tmp,mtp,&attime)==0) { /* summarize transfer statistics */ if (attime) { tfn++; tsize+=size; tttime+=attime; } /* do some logging */ outlog(recipient,host,utf_name,sizes); notespeed(host,size,attime); } } } else /* sending or deleting single files */ { /* main loop over the file names */ for (fn=optind; fnnext) { for (oflp=hlp->flist; oflp; oflp=oflp->next) { /* matching file name? */ if (simplematch(oflp->fname,utf_name,0)) { /* matching recipient? */ snprintf(MAXS(rto),"%s@%s",oflp->to,hlp->host); if (simplematch(rto,to,0)) { unlink(oflp->oshfn); oflp->oshfn[strlen(oflp->oshfn)-1]='d'; unlink(oflp->oshfn); if (del) { del=2; utf2iso(0,NULL,file,NULL,oflp->fname); snprintf(MAXS(tmp), "deleted from outgoing spool: '%s' for %s ", file,rto); if (quiet<2) message(prg,'I',tmp); } } } } } } if (del) continue; /* open outgoing spool header file */ if ((oshf=outspool(pw_name,outgoing,oshfn))==NULL) message(prg,'F',"cannot create outgoing spool file"); fprintf(oshf,"FROM\t%s\n",user); fprintf(oshf,"TO\t%s@%s\n",recipient,host); fprintf(oshf,"FILE\t%s\n",utf_name); /*if (del || overwrite) fprintf(oshf,"DEL\n");*/ } else /* send header lines */ { /* send file name */ snprintf(MAXS(tmp),"FILE %s",utf_name); sendcommand(sockfd,tmp,reply); if (!test && !str_beq(reply,"200 ") && quiet<2) message(prg,'W',"remote site does not support file names"); /* delete file? */ if (overwrite) sendcommand(sockfd,"DEL",NULL); if (del) { if (sendheader(sockfd,"DEL")) { if (quiet<2) message(prg,'W',"remote site cannot delete files"); } else { snprintf(MAXS(tmp),"'%s' deleted",iso_name); if (quiet<2) message(prg,'I',tmp); } continue; } } /* is the file readable? */ if (stat(file,&finfo)<0) { snprintf(MAXS(tmp),"cannot access '%s'",file); message(prg,'E',tmp); if (spool) { fclose(oshf); unlink(oshfn); } continue; } /* is it a regular file? */ if (!S_ISREG(finfo.st_mode)) { errno=0; snprintf(MAXS(tmp),"%s is not a regular file, skipping",file); message(prg,'E',tmp); if (spool) { fclose(oshf); unlink(oshfn); } continue; } /* is it a executable? */ if (finfo.st_mode&S_IXUSR) exe=1; else exe=0; /* no file mode set? guess the file type */ mode=guess_ftype(file,ftype); if (!guess) mode=0; /* get the original file size */ strftime(date,20,"%Y-%m-%d %H:%M:%S",gmtime(&finfo.st_mtime)); #ifdef HPUX /* dirty hack around HPUX strftime bug */ date[17]=0; strcat(date,"00"); #endif /* text or source mode? */ if (text || source || mode=='t' || mode=='s') { /* open and test file to send and open tmp-file */ inf=rfopen(file,"r"); outf=rfopen(texttmp,"w"); if (!inf) { snprintf(MAXS(tmp),"cannot open '%s'",file); message(prg,'E',tmp); if (spool) { fclose(oshf); unlink(oshfn); } continue; } if (!outf) message(prg,'F',"cannot open tmp-file"); /* convert file to NVT format */ do { ch=fgetc(inf); if (ch!=EOF) { if (ch=='\n') fprintf(outf,"\r\n"); else fputc(ch,outf); } } while (!feof(inf)); fclose(inf); fclose(outf); strcpy(file,texttmp); } /* get the original file size */ stat(file,&finfo); orgsize=finfo.st_size; /* compression mode wanted? */ if (*compress) do_compress=1; /* check if file is compressible */ if (do_compress && !*force_compress) { /* do not compress too small files */ if (finfo.st_size < 1024) do_compress=0; /* determine file type */ /* loop over all non-compressible file type strings */ for (n=0;do_compress && *cft[n];n++) { /* is this file a not compressible one? */ snprintf(MAXS(tmp),"*%s*",cft[n]); if (simplematch(ftype,tmp,1)) do_compress=0; } } /* compressed mode? */ if (do_compress) { /* compress tmp-file */ if (!quiet) printf("compressing... \r"); fflush(stdout); snprintf(MAXS(cmd), "trap \"rm -f %s 2>/dev/null\" 1 2 3;%s < '%s' > %s", ziptmp,zprg,file,ziptmp); if (verbose) { snprintf(MAXS(tmp),"shell-call: %s",strchr(cmd,';')+1); message(prg,'I',tmp); } if (system(cmd)) { snprintf(MAXS(tmp),"cannot compress %s",file); message(prg,'E',tmp); if (spool) { fclose(oshf); unlink(oshfn); } continue; } strcpy(file,ziptmp); } else size=orgsize; /* pgp encryption? */ if (pgpcrypt) pgp_encrypt(pgpcrypt,pgprid,file); /* pgp signature to add? */ if (*pgpsign) pgp_sign(pgpsign,file,sockfd); /* get the file size */ if (stat(file,&finfo)<0) message(prg,'F',"cannot access tmp file"); size=finfo.st_size; snprintf(MAXS(sizes),"%ld %ld",size,orgsize); /* determine the file type */ type="BINARY"; if (guess) { if (mode=='s') type="SOURCE"; if (mode=='t') type="TEXT="CHARSET; } else if (mime) type="MIME"; else if (source) type="SOURCE"; else if (text) type="TEXT="CHARSET; /* write to outgoing spool? */ if (spool) { if (do_compress) { if (str_eq(compress,S_GZIP)) fprintf(oshf,"TYPE\tBINARY COMPRESSED\n"); else fprintf(oshf,"TYPE\tBINARY COMPRESSED=%s\n",compress); } else if (pgpcrypt) fprintf(oshf,"TYPE\t%s CRYPTED\n",type); else fprintf(oshf,"TYPE\t%s\n",type); fprintf(oshf,"SIZE\t%s\n",sizes); fprintf(oshf,"DATE\t%s\n",date); if (exe) fprintf(oshf,"ATTR\tEXE\n"); else fprintf(oshf,"ATTR\tNONE\n"); } else /* send the header information */ { if (do_compress) { if (str_eq(compress,S_GZIP)) snprintf(MAXS(line),"TYPE %s COMPRESSED",type); else snprintf(MAXS(line),"TYPE %s COMPRESSED=%s",type,compress); } else if (pgpcrypt) snprintf(MAXS(line),"TYPE %s CRYPTED",type); else snprintf(MAXS(line),"TYPE %s",type); sendcommand(sockfd,line,reply); if (!test && !str_beq(reply,"200 ") && quiet<2) { errno=0; snprintf(MAXS(tmp),"remote site does not support file of %s",line); message(prg,'F',tmp); } snprintf(MAXS(tmp),"SIZE %s",sizes); sendheader(sockfd,tmp); snprintf(MAXS(tmp),"DATE %s",date); if (sendheader(sockfd,tmp) && quiet<2) message(prg,'W',"remote site does not support dates"); if (exe) sendcommand(sockfd,"ATTR EXE",NULL); else sendcommand(sockfd,"ATTR NONE",NULL); } /* comment to send? */ if (*comment || *redirect) { *line=0; if (*comment) strcat(line,comment); if (*redirect) { if (*line) { snprintf(MAXS(tmp),"%s\r\n%s",line,redirect); strcpy(line,tmp); } else strcpy(line,redirect); } iso2utf(tmp,line); if (spool) fprintf(oshf,"COMMENT\t%s\n",tmp); else { snprintf(MAXS(line),"COMMENT %s",tmp); sendcommand(sockfd,line,NULL); } } /* send the file data */ if (spool) { fclose(oshf); strcpy(osdfn,oshfn); osdfn[strlen(osdfn)-1]='d'; if (fcopy(file,osdfn,S_IRUSR|S_IWUSR)<0) { unlink(oshfn); unlink(osdfn); } else { strcpy(tmp,oshfn); oshfn[strlen(oshfn)-1]='h'; rename(tmp,oshfn); spooled_info(iso_name,osdfn,do_compress); } } else { strcpy(tmp,type); if ((cp=strchr(tmp,'='))) *cp=0; str_tolower(tmp); if (do_compress) strcat(tmp," compressed"); if (pgpcrypt) strcat(tmp," crypted"); if (send_data(sockfd,size,file,tinfo,iso_name,tmp,mtp,&attime)==0) { /* summarize transfer statistics */ if (attime) { tfn++; tsize+=size; tttime+=attime; } /* do some logging */ outlog(recipient,host,utf_name,sizes); notespeed(host,size,attime); } } } } /* start spool daemon or close the connection */ if (spool) { if (del) { if (del<2 && quiet<2) message(prg,'W',"no matching files found in outgoing spool"); } else { if (spooling==2) start_spooldaemon(localhost); } } else { sendcommand(sockfd,"QUIT",NULL); close(sockfd); } /* print total transfer statistics */ if (tsize && info && tfn>1 && quiet<2) { thruput=tsize*1000/tttime; if (tsize/1024>9999) snprintf(MAXS(tmp),"%d files sent with %.1f MB",tfn,tsize/1024/1024); else if (tsize>9999) snprintf(MAXS(tmp),"%d files sent with %.1f kB",tfn,tsize/1024); else snprintf(MAXS(tmp),"%d files sent with %d byte",tfn,(int)tsize); if (thruput>9999) snprintf(MAXS(line),"%s at %.1f kB/s",tmp,thruput/1024); else snprintf(MAXS(line),"%s at %d byte/s",tmp,(int)thruput); message("",'I',line); } /* delete tmp-files */ cleanup(); exit(0); } /* * cleanexit - clean termination routine * * A very simple interrupt handler */ void cleanexit() { printf("\r\n"); cleanup(); exit(0); } /* * cleanup - delete tmp files and send LOG command to local server */ void cleanup() { int sockfd; /* socket file descriptor */ char line[MAXLEN], /* one line of text */ reply[MAXLEN], /* reply string from server */ server[FLEN]; /* saft server */ /* ignore all relevant signals */ signal(SIGTERM,SIG_IGN); signal(SIGABRT,SIG_IGN); signal(SIGQUIT,SIG_IGN); signal(SIGHUP,SIG_IGN); signal(SIGINT,SIG_IGN); if (verbose<2) verbose=0; *reply=0; #ifndef DEBUG rmtmpdir(tmpdir); if (outlogging) { /* inform local saft server about outgoing log file */ if (access(outlogtmp,R_OK)==0 && !test && (sockfd=open_connection("127.0.0.1",SAFT))>=0) { /* check local saft server */ sock_getline(sockfd,reply); if (str_beq(reply,"220 ") && strstr(reply,"SAFT")) { /* send LOG command */ snprintf(MAXS(line),"LOG %s %s",pw_name,outlogtmp); sock_putline(sockfd,line); sock_getline(sockfd,reply); str_trim(reply); /* emergency exit */ if (*reply=='4') { unlink(outlogtmp); exit(0); } /* forward address to real saft server? */ if (str_beq(reply,"510 ")) { strcpy(server,strrchr(reply,' ')+1); /* close current connection */ sendcommand(sockfd,"QUIT",NULL); shutdown(sockfd,2); /* connect real saft server */ if (!test && (sockfd=open_connection(server,SAFT))>=0) { sock_getline(sockfd,reply); if (str_beq(reply,"220 ") && strstr(reply,"SAFT")) { /* send LOG command */ snprintf(MAXS(line),"LOG %s %s",pw_name,outlogtmp); sock_putline(sockfd,line); sock_getline(sockfd,reply); str_trim(reply); /* emergency exit */ if (*reply=='4') { unlink(outlogtmp); exit(0); } } } } } /* close the connection */ sendcommand(sockfd,"QUIT",NULL); shutdown(sockfd,2); } } unlink(outlogtmp); #endif } /* * pgp_encrypt - encrypt a file with pgp * * INPUT: pgpcrypt - public key or IDEA encryption * pgprid - pgp recipient id * file - input file name * * OUTPUT: file - output file name */ void pgp_encrypt(int pgpcrypt, char *pgprid, char *file) { char *cp, /* simple character pointer */ cmd[OVERSIZE], /* the command for system() */ tmp[MAXLEN], /* temporary string */ line[MAXLEN], /* one text line */ rmcmd[MAXLEN]; /* the rm command */ struct stat finfo; /* information about a file */ FILE *inf; /* input file */ inf=NULL; /* determine which tmp-files to delete in system-trap */ if (str_eq(file,tartmp) || str_eq(file,texttmp)) snprintf(MAXS(rmcmd), "trap \"rm -f %s %s 2>/dev/null\" 1 2 3 15",file,pgptmp); else snprintf(MAXS(rmcmd),"trap \"rm -f %s 2>/dev/null\" 1 2 3 15",pgptmp); if (!quiet) message(prg,'I',"call to pgp..."); /* look for matching pgp-IDs */ if (strlen(pgprid)>1) { snprintf(MAXS(cmd),"%s -kvf %s > %s 2>/dev/null",pgp_bin,pgprid,pgptmp); if (verbose) { snprintf(MAXS(tmp),"shell-call: %s",cmd); message(prg,'I',tmp); } system(cmd); if (stat(pgptmp,&finfo)<0 || finfo.st_size==0 || !(inf=rfopen(pgptmp,"r"))) { errno=0; message(prg,'F',"call to pgp failed"); } while (fgetl(line,inf) && !strstr(line,"matching key")); fclose(inf); if ((cp=strchr(line,'.'))) *cp=0; if (!str_eq(line,"1 matching key found")) { if (!quiet) { snprintf(MAXS(line),"ambigous pgp-ID '%s'",pgprid); message(prg,'W',line); inf=rfopen(pgptmp,"r"); while (fgetl(line,inf)) printf("%s",line); fclose(inf); } *pgprid=0; } } else *pgprid=0; /* pgp needs user input? */ if (pgpcrypt=='c' || !*pgprid) { snprintf(MAXS(cmd),"%s;%s +armor=off -f%c < %s > %s", rmcmd,pgp_bin,pgpcrypt,file,pgptmp); if (verbose) { snprintf(MAXS(tmp),"shell-call: %s",strchr(cmd,';')+1); message(prg,'I',tmp); } if (system(cmd) || stat(pgptmp,&finfo)<0 || finfo.st_size==0) { errno=0; message(prg,'F',"call to pgp failed"); } printf("\n"); /* pgp needs no user input */ } else { snprintf(MAXS(cmd),"%s;%s +armor=off -fe %s < %s > %s 2>/dev/null", rmcmd,pgp_bin,pgprid,file,pgptmp); if (verbose) { snprintf(MAXS(tmp),"shell-call: %s",strchr(cmd,';')+1); message(prg,'I',tmp); } if (system(cmd) || stat(pgptmp,&finfo)<0 || finfo.st_size==0) { errno=0; message(prg,'F',"call to pgp failed (wrong pgp user id?)"); } } strcpy(file,pgptmp); } /* * pgp_sign - create detached pgp signature file and send SIGN header line * * INPUT: pgpsign - pgp user id * infile - input file name * sockfd - socket descriptor */ void pgp_sign(const char *pgpsign, const char *infile, int sockfd) { int check; /* check if sig is ok */ char *cp, /* simple string pointer */ tmp[MAXLEN], /* temporary string */ sign[MAXLEN], /* signature */ line[MAXLEN], /* one line of text */ cmd[2*MAXLEN]; /* the command for popen() */ FILE *pipe; /* input file descriptor */ check=0; *sign=0; if (!quiet && !pgppass) message(prg,'I',"call to pgp..."); snprintf(MAXS(cmd),"%s %s -fsba %s < %s",pgp_bin,pgpvm,pgpsign,infile); if (verbose) { snprintf(MAXS(tmp),"shell-call: %s",cmd); message(prg,'I',tmp); } if (!(pipe=popen(cmd,"r"))) message(prg,'F',"call to pgp (signature) failed"); /* read signature file in NVT format */ while (fgetl(line,pipe)) { if ((cp=strchr(line,'\n'))) *cp=0; if (str_eq(line,"-----BEGIN PGP MESSAGE-----")) check+=1; if (str_eq(line,"-----END PGP MESSAGE-----")) check+=2; strcat(sign,line); strcat(sign,"\r\n"); } pclose(pipe); if (!pgppass) printf("\n"); if (check!=3) message(prg,'F',"call to pgp (signature) failed"); iso2utf(tmp,sign); snprintf(MAXS(sign),"SIGN %s",tmp); if (sockfd) sendcommand(sockfd,sign,NULL); } /* * outspool - create and open outgoing spool header file * * INPUT: user - own user name * outgoing - outgoing spool directory * * OUTPUT: oshf - outgoing spool header file name * * RETURN: FILE pointer if ok, NULL if failed * * This function does not use a locking or a atomar test&create function * because this would not work with NFS. To avoid race conditions (remember: * a spool daemon is normally running, too!) a tmp-header file is created. */ FILE *outspool(const char *user, const char *outgoing, char *oshf) { struct stat finfo; /* information about a file */ struct timeval tv; #if !defined(SOLARIS2) && !defined(IRIX) struct timezone tz; #endif /* get time structure */ #if defined(SOLARIS2) || defined(IRIX) # ifdef _SVID_GETTOD gettimeofday(&tv); # else gettimeofday(&tv,NULL); # endif #else gettimeofday(&tv,&tz); #endif /* build (tmp) header file name */ snprintf(oshf,MAXLEN-1,"%s/%s_%d.t",outgoing,user,(int)tv.tv_usec); /* does the file already exist? */ if (stat(oshf,&finfo)==0) return(NULL); return(rfopen(oshf,"w")); } /* * reply - dummy function, only needed for linking */ void reply(int x) { } /* * start_spooldaemon - start local spool daemon for outgoing files * * INPUT: localhost - name of the local host */ void start_spooldaemon(char *localhost) { int sockfd; /* socket file descriptor */ char *host, /* name of local SAFT server */ reply[MAXLEN], /* reply string from remote server */ tmp[MAXLEN], /* temporary string */ line[MAXLEN]; /* one line of text */ /* open connection to the own server */ sockfd=open_connection(localhost,SAFT); if (sockfd==-1) snprintf(MAXS(tmp),"cannot create a network socket " "- cannot start local spool daemon"); if (sockfd==-2) snprintf(MAXS(tmp),"cannot open connection to %s " "- cannot start local spool daemon",localhost); if (sockfd==-3) snprintf(MAXS(tmp),"%s is unknown (name server down?) " "- cannot start local spool daemon",localhost); if (sockfd<0) { errno=0; message(prg,'F',tmp); } /* no remote server or protocol error? */ sock_getline(sockfd,line); if (!str_beq(line,"220 ") || !strstr(line,"SAFT")) { errno=0; snprintf(MAXS(tmp),"No SAFT server on port %d at %s " "- cannot start local spool daemon",SAFT,localhost); message(prg,'F',tmp); } sendcommand(sockfd,"START SPOOLDAEMON",reply); str_trim(reply); /* forward to local SAFT-server set? */ if (str_beq(reply,"510 ") && (host=strrchr(reply,' '))) { host++; /* close current connection */ sendcommand(sockfd,"QUIT",NULL); shutdown(sockfd,2); /* open connection to the own SAFT server */ sockfd=open_connection(host,SAFT); if (sockfd==-1) snprintf(MAXS(tmp),"cannot create a network socket " "- cannot start local spool daemon"); if (sockfd==-2) snprintf(MAXS(tmp),"cannot open connection to %s " "- cannot start local spool daemon",host); if (sockfd==-3) snprintf(MAXS(tmp),"%s is unknown (name server down?) " "- cannot start local spool daemon",host); if (sockfd<0) { errno=0; message(prg,'F',tmp); } /* no remote server or protocol error? */ sock_getline(sockfd,line); if (!str_beq(line,"220 ") || !strstr(line,"SAFT")) { errno=0; snprintf(MAXS(tmp),"No SAFT server on port %d at %s " "- cannot start local spool daemon",SAFT,host); message(prg,'F',tmp); } sendheader(sockfd,"START SPOOLDAEMON"); sendheader(sockfd,"QUIT"); } } /* * outlog - create temporary user outgoing log file * * INPUT: from - own user name * to - recipient user name * host - recipient host name * file - file name * sizes - original and compressed file size */ void outlog(char *to, char *host, char *file, char *sizes) { char currentdate[FLEN]; /* current date */ FILE *outf; /* output file */ time_t timetick; /* unix time (in seconds) */ if (outlogging && strcmp(host,localhost) && strcmp(host,"localhost") && (outf=rfopen(outlogtmp,"a"))) { /* get current date */ timetick=time(0); strftime(currentdate,20,"%Y-%m-%d %H:%M:%S",localtime(&timetick)); /* write to log file */ fprintf(outf,"FROM\t%s\nTO\t%s@%s\nDATE\t%s\nFILE\t%s\nSIZES\t%s\n\n", pw_name,to,host,currentdate,file,sizes); fclose(outf); } return; } /* * forward - forward a file with complete header from stdin * * INPUT: host - host to send to * mtp - maximum thruput */ void forward(char *host, float mtp) { int sockfd; /* socket file descriptor */ unsigned long size; /* size of the file */ float ttime; char *arg, /* SAFT command argument */ from[FLEN], /* sender user name */ recipient[FLEN], /* recipient at serverhost */ line[MAXLEN], /* one line of text */ tmp[MAXLEN]; /* temporary string */ size=0; *line=*recipient=*from=0; get_header("FROM",from); get_header("TO",recipient); /* look for correct SAFT server and open connection */ sockfd=saft_connect("file",recipient,from,host,tmp); /* loop over the other header lines */ while (fgetl(line,stdin)) { str_trim(line); if (str_neq_nocase("DATA",line,strlen(line))) break; if (str_neq_nocase("RESEND",line,strlen(line))) { errno=0; message(prg,'F',"the RESEND command is not supported with the -X option"); } /* look for SIZE command */ if ((arg=strchr(line,' '))) { if (str_neq_nocase("SIZE",line,4)) sscanf(arg+1,"%ld",&size); } /* send this SAFT command */ sendheader(sockfd,line); } if (!size) { errno=0; message(prg,'F',"SIZE command is missing"); } send_data(sockfd,size,"","","STDIN","",mtp,&ttime); } /* * get_header - read a header line from stdin * * INPUT: cmd - command type * * OUTPUT: arg - the command argument */ void get_header(const char *cmd, char *arg){ char *cp, /* a simple character pointer */ line[MAXLEN], /* one line of text */ tmp[MAXLEN]; /* temporary string */ /* read one header line */ fgetl(line,stdin); str_trim(line); /* split command and argument */ if (strlen(line)>3 && (cp=strchr(line,' '))) { *cp=0; strcpy(arg,cp+1); str_toupper(line); } if (!str_eq(cmd,line)) { errno=0; line[MAXLEN-80]=0; snprintf(MAXS(tmp), "illegal SAFT command \"%s\", \"%s\" was expected",line,cmd); message(prg,'F',tmp); } } /* * guess_ftype - guess file type * * INPUT: file - file name with path * * OUTPUT: type - file type as long string * * RETURN: b for binary, s for source, t for text * * Remark: you MUST call stat(file) and check the return value before * calling guess_ftype(), because of symlink loop detection. */ char guess_ftype(const char *file, char *type) { int i; char *cp, **cpp, tmp[MAXLEN], /* temporary string */ link[MAXLEN], /* symlink file name */ cmd[MAXLEN]; /* the command for popen() */ FILE *pipe; /* input file descriptor */ static const char *set[]= /* source extension type array */ { "Makefile","Makefile.", ".c",".f",".f77",".for",".f90",".p",".pas",".java",".h",".ada",".pl",".sl", ".cc",".tcl",".tk",".ps","" }; static char *tet[]= /* text extension type array */ { "README",".txt","" }; static char *sft[]= /* source file type array */ { "source","shell","program","command","script", "perl","pascal"," c ","c++","java","fortran","" }; static const char *tft[]= /* text file type array */ { "text","ASCII","english","" }; static const char *bft[]= /* binary file type array */ { "data","archive","zip","stripped","" }; static char *cet[]= /* compressed extension type array */ { ".zip",".z",".zoo",".gz",".bz",".bz2",".tgz",".rpm", ".mp3",".gif",".jpg",".tif",".tiff",".png",".avi",".mpeg","" }; /* first look for known file names and extensions */ /* source types */ for (i=0;*set[i];i++) { if (*set[i]=='.') { if ((cp=strrchr(file,'.'))) if str_eq(cp,set[i]) return('s'); } else { if str_eq(file,set[i]) return('s'); } } /* text types */ for (i=0;*tet[i];i++) { if (*tet[i]=='.') { if ((cp=strrchr(file,'.'))) if str_eq(cp,tet[i]) return('t'); } else { if str_eq(file,tet[i]) return('t'); } } /* compressed binary types */ if (*dontcompress[0]) cpp=dontcompress; /* list from sendfile.cf */ else cpp=cet; for (i=0;*cpp[i];i++) { /*printf(">%s<\n",cpp[i]);*/ if ((cp=strrchr(file,'.')) && str_eq(cp,cpp[i])) { strcpy(type,"already compressed"); return('b'); } } /* next, try with file command */ /* read output from file command */ snprintf(MAXS(cmd),"file '%s'",file); if ((pipe=popen(cmd,"r")) && fgetl(tmp,pipe)) { pclose(pipe); /* get type string */ if (str_beq(tmp,file)) strcpy(type,tmp+strlen(file)); else strcpy(type,tmp); if ((cp=strchr(type,'\n'))) *cp=0; /* follow symbolic link */ if (strstr(type,"symbolic link")) { strcpy(link,strrchr(type,' ')+1); return guess_ftype(link,type); } if (verbose) { snprintf(MAXS(tmp),"%s is of type %s",file,type); message(prg,'I',tmp); } /* look for characteristic substrings */ str_tolower(type); for (i=0;*bft[i];i++) if (strstr(type,bft[i])) return('b'); for (i=0;*sft[i];i++) if (strstr(type,sft[i])) return('s'); for (i=0;*tft[i];i++) if (strstr(type,tft[i])) return('t'); } /* default type is binary */ return('b'); } /* * linkspeed - check if link to host is fast enough * * INPUT: host - remote host to send to * lanspeed - min. speed for LAN in kB/s * compress - the compression method * * OUTPUT: compress - empty compression method if fast link found * * RETURN: 1 for slow, 0 for faster than lanspeed */ int linkspeed(const char *host, int lanspeed, char **compress) { int speed; /* found speed */ char msg[MAXLEN], /* message output string */ speeddir[MAXLEN], /* directory with speed info files */ hostfile[MAXLEN]; /* file with speed for remote host */ FILE *inf; /* input file */ struct stat finfo; /* information about a file */ speed=0; /* local host IS fast! :-) */ if (str_eq(host,localhost) || str_eq(host,"localhost")) { if (verbose) message(prg,'I',"disabling compressing for localhost"); *compress=""; return(0); } /* on too low lanspeed value we suppose a slow link */ if (lanspeed<1) return(1); /* create speeds dir if necessary */ snprintf(MAXS(speeddir),"%s/speeds",userspool); if (stat(speeddir,&finfo)<0 || !S_ISDIR(finfo.st_mode)) { unlink(speeddir); if (mkdir(speeddir,S_IRUSR|S_IWUSR|S_IXUSR)<0) return(1); chmod(speeddir,S_IRUSR|S_IWUSR|S_IXUSR); } snprintf(MAXS(hostfile),"%s/%s",speeddir,host); /* if host file is missing return slow link */ if (!(inf=rfopen(hostfile,"r"))) return(1); fscanf(inf,"%d",&speed); fclose(inf); if (speednext) { printf("\n"); for (oflp=hlp->flist; oflp; oflp=oflp->next) { utf2iso(0,NULL,file,NULL,oflp->fname); printf("%s@%s : %s (%ld KB)\n", oflp->to,hlp->host,file,(oflp->size+512)/1024); } } /* printf("\n(To delete these files use: %s -Sd )\n",prg); */ return(0); } /* * spooled_info - print information about spooled file * * INPUT: file - original file name * sdf - spool data file name * compressed - compressed flag */ void spooled_info(const char *file, const char *sdf, int compressed) { int size; char tmp[MAXLEN]; /* temporary string */ struct stat finfo; /* information about a file */ if (quiet>1) return; if (stat(sdf,&finfo)<0) { snprintf(MAXS(tmp),"cannot access spool file %s",sdf); message(prg,'E',tmp); return; } size=(finfo.st_size+512)/1024; if (compressed) snprintf(MAXS(tmp),"'%s' spooled (%d KB [compressed])",file,size); else snprintf(MAXS(tmp),"'%s' spooled (%d KB)",file,size); message(prg,'I',tmp); } /* * usage - print short help usage text */ int usage() { /* HAVE_GETOPTLONG_H is dead code for sendfile! */ #if defined(HAVE_GETOPTLONG_H) fprintf(stderr,"usage: %s [OPTIONS] file [...] user[@host]\n",prg); fprintf(stderr," or: %s [OPTIONS] -a archivename file-or-directory [...] user[@host]\n",prg); fprintf(stderr,"options:\n"); fprintf(stderr," -a, --archive sends file-or-director(s) as\n archivename to user[@host]\n"); fprintf(stderr," -s, --source send file(s) in source mode\n"); fprintf(stderr," -t, --text send file(s) in text mode\n"); fprintf(stderr," -d, --delete delete previous sent file(s)\n"); fprintf(stderr," -c, --comment comment a single file\n"); fprintf(stderr," -o, --overwrite overwrite already sent file(s) with same name\n"); fprintf(stderr," -b, --bounce resend file(s) to user[@host]\n"); fprintf(stderr," -u, --uncompressed send file(s) uncompressed\n"); fprintf(stderr," -v, --verbose verbose mode\n"); fprintf(stderr," -q, --quiet quiet mode 1: no transfer messages or questions\n"); fprintf(stderr," -Q, --real-quiet quiet mode 2: no transfer\n information and warning messages\n"); fprintf(stderr," -P, --stdin read data stream from stdin\n normaly this is a pipe\n"); /* fprintf(stderr," -X, --extended this uses the extended\n header feature, this implies -P\n"); */ fprintf(stderr," -V, --version show version\n"); fprintf(stderr," -h, --help print this help\n"); fprintf(stderr," -ps, --pgp-sign pgp-sign the file\n"); fprintf(stderr," -pe=ID, --pgp-encrypt pgp-encrypt the file\n\n"); fprintf(stderr,"Default mode: send file(s) in binary mode and compressed.\n"); fprintf(stderr,"For a full description of all options, see 'man %s'.\n\n",prg); fprintf(stderr,"Example: %s rabbit.gif beate@juhu.lake.de\n\n",prg); fprintf(stderr,"Report bug to: Ulli Horlacher \n"); #endif fprintf(stderr,"\n"); fprintf(stderr,"usage: %s [OPTIONS] file [...] user[@host]\n",prg); fprintf(stderr," or: %s [OPTIONS] -a=archive-name file-or-directory [...] user[@host]\n",prg); fprintf(stderr,"options: -s send file(s) in source mode\n"); fprintf(stderr," -t send file(s) in text mode\n"); fprintf(stderr," -g send file(s) in guessed mode (does not work in every case!)\n"); fprintf(stderr," -d delete previously sent file(s)\n"); fprintf(stderr," -o overwrite file(s) with the same name\n"); fprintf(stderr," -u send file(s) uncompressed\n"); fprintf(stderr," -v verbose mode\n"); fprintf(stderr," -q quiet mode 1: no transfer messages or questions\n"); fprintf(stderr," -Q quiet mode 2: no transfer, information or warning messages\n"); fprintf(stderr," -P read file from stdin (this is usually a pipe)\n"); fprintf(stderr," -S spool file(s) for later processing\n"); fprintf(stderr," -l list file(s) in outgoing spool\n"); fprintf(stderr," -V show version\n"); fprintf(stderr," -m LIMIT maximum thruput at LIMIT KB/s\n"); fprintf(stderr," -a=name send all files in one archive\n"); fprintf(stderr," -c='comment' add a one line text comment to a single file\n"); fprintf(stderr," -ps pgp-sign\n"); fprintf(stderr," -pe[=ID] pgp-encrypt [for ID]\n"); fprintf(stderr,"example: %s rabbit.gif beate@juhu.lake.de\n",prg); fprintf(stderr,"see also: sfconf\n"); /* fprintf(stderr,"Default mode: send file(s) in binary mode and compressed.\n"); fprintf(stderr,"For a full description of all options, see 'man %s'.\n",prg); */ return(2); }