/* * 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 * Colin Phipps * Stefan `Sec` Zehl * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #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 #elif defined(AIX3) #include int statfs(char *, struct statfs *); #elif defined(OSF1) #include #include int statfs(char *p, struct statfs *, int); #elif defined(HAVE_SYS_STATVFS_H) #include #define statfs statvfs #define FSBS 1024 /* dirty hack against broken statvfs block information */ #elif defined(BSD) || defined(ULTRIX) #include #include #else #include #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 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(¬ify,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(¬ify,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)+4maxspool*1024) notify_reply(¬ify,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(¬ify,notification,sender,recipient,mailto,bell,410); setreugid(); /* go to the user spool directory */ if (chdir(userspool)<0) notify_reply(¬ify,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(¬ify,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(¬ify,notification,sender,recipient,mailto,bell,412); if (id<0 || id>maxfiles) notify_reply(¬ify,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(¬ify,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)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(¬ify,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(¬ify,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; n13 && 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)>> 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;iflist; 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); }