/*
* File: sendfiled.c
*
* Author: Ulli Horlacher (framstag@rus.uni-stuttgart.de)
*
* Contrib.: Thomas Tissen (tici@uni-paderborn.de)
* Olaf Erb (erb@insu1.etec.uni-karlsruhe.de)
* Rainer Bawidamann (widi@sol.wohnheim.uni-ulm.de)
* Andreas "Ako" Koppenhöfer (koppenhoefer@belwue.de)
* Chris Foote (chris@senet.com.au)
* Daniel Kobras <kobras@lists.tat.physik.uni-tuebingen.de>
* Colin Phipps <cph@cph.demon.co.uk>
* Stefan `Sec` Zehl <sec@42.org>
*
* History:
*
* 1995-08-11 Framstag initial version
* 1995-09-10 Framstag added delete and resend function
* 1995-11-01 Framstag eliminated some security flaws
* added pgp signature entry
* 1995-11-05 Framstag added NeXT support
* 1995-11-14 Framstag added user config files
* 1995-11-21 Framstag added chat client support
* 1996-01-04 Framstag added allow-only flag to NOSENDFILE
* 1996-01-11 Framstag added global and user config files
* 1996-01-27 Framstag added maxspool and minfree config option
* 1996-01-31 Framstag bug fixes for mail2user
* 1996-02-04 Olaf Erb added notification=both
* 1996-02-05 Framstag some code cleanup
* 1996-02-06 Framstag added ATTR EXE
* bug fixes for attribute handling
* 1996-02-19 Framstag fixed problems with NFS and MSG
* fixed statfs-problem with Solaris-2 (?)
* enhanced msg2tty with non-blocking write
* 1996-02-20 Framstag changed msg-tty and msg-fifo files to support
* NFS (msg2tty)
* 1996-02-21 widi better Solaris-2 support
* 1996-02-21 Framstag bug fix with maxfiles option
* better notification check
* 1996-02-22 Framstag replaced string "localhost" with the
* real name of the local host
* 1996-02-23 Framstag mail-notification now contains output
* of "receive -l", too
* 1996-03-17 Framstag security bug (?) fixed:
* no more chown on symlinks
* 1996-03-27 Framstag security bug fixed V2.0:
* no more chown on any links
* 1996-04-01 Framstag corrected logfile bug
* swapped O_SYNC and O_NONBLOCK
* 1996-04-02 Framstag fixed FROM line handling
* added forwarding by user
* 1996-04-04 Framstag allowed COMPRESSED=GZIP for TYPE option
* 1996-04-08 Framstag changed signature command
* better checking for same files
* 1996-04-12 Framstag added pgp support
* added own SIGN attribute
* 1996-05-10 Framstag added global alias file support
* 1996-05-12 Framstag added checking of SPOOL/.nosendfile
* 1996-05-22 Framstag added -c configfile runtime option
* 1996-06-21 Framstag added global log files
* 1996-06-22 Framstag better default date setting
* 1996-06-23 Framstag nicer log file formats
* 1996-06-13 Framstag differ between SYSV and BSD du
* 1996-09-24 Framstag protocol-change:
* CHARSET charset --> TYPE TEXT=charset
* 1996-10-23 Framstag define O_SYNC for BSD
* 1996-12-29 Framstag added domain option in sendfile.cf
* 1997-01-01 Framstag fixed mkdir bug
* 1997-01-07 Framstag better spool file creation handling
* added debug facility
* 1997-01-19 Framstag fixed resend bug
* 1997-01-20 Framstag fixed bug with TEXT=charset attribute
* 1997-01-22 Framstag added file post-processing feature
* 1997-01-25 Framstag fixed TYPE default declaration (binary)
* 1997-01-30 Framstag better SIZE parsing
* 1997-02-03 Framstag sprintf() -> snprintf()
* 1997-02-09 Framstag new msg2tty interface (msgh,msg)
* 1997-02-10 Framstag added setegid-call (security!)
* 1997-02-12 Framstag better IRIX and HP-UX support
* 1997-02-13 Framstag better OSF/1 support
* 1997-02-23 Framstag modified str_* function names
* extended with TYPE=MIME
* 1997-03-27 Framstag added DEBUG command
* fixed minfree checking bug
* 1997-05-05 Framstag better IRIX support (blksize)
* 1997-06-10 Ako more reliable retransmit operation
* 1997-06-16 Framstag added pseudo-user /dev/null for
* network speed testing
* 1997-06-17 Framstag added packet_size config option
* 1997-06-19 Framstag changed ruid and rgid to global variables
* added message logging option
* 1997-06-23 Framstag new own user log file for messages: msglog
* 1997-07-07 Framstag activated outgoing spooling
* 1997-07-09 Framstag added parallel option (sendfile.cf)
* 1997-07-10 Framstag fixed date bug with messages
* 1997-08-21 Framstag added config flag for allowing DEL command
* 1997-09-12 Framstag allow empty date field on resend command
* 1997-09-14 Framstag moved spoolid() from sendfiled.c to spool.c
* 1997-09-30 Framstag added -f free_space option
* 1997-11-22 Framstag better SAFT URL checking
* 1997-11-23 Framstag moved NOSENDFILE to ALLOW and DENY files
* 1997-12-07 Framstag better locking checking
* 1997-12-10 Framstag allowed COMPRESSED=BZIP2 for TYPE option
* added PATH variable in sendfiled.cf
* 1997-12-12 Framstag fixed PATH bug
* no more testing for gzip binary
* 1997-12-19 Framstag added forcepgp option
* 1998-01-04 Framstag added forward dection for outgoing spooling
* added usage()
* 1998-01-05 Framstag reactivated outgoing logging
* 1998-01-15 Framstag save time stamp when bouncing file from outgoing spool
* back to the user
* 1998-01-16 Framstag added -Q option
* 1998-01-20 Framstag fixed socket closing and delivery bugs in sfsd
* 1998-01-21 Framstag fixed closedir bug in check_outspool()
* 1998-01-25 Framstag fixed forwarding bug in sfsd()
* 1998-03-01 Framstag changed /dev/null testing recipient to :NULL:
* 1998-03-11 Framstag userconfig directory may now in $HOME
* 1998-03-16 Framstag fixed sfsd-fork bug (too many sfsd running)
* 1998-05-19 Framstag fixed maxfiles counting bug in spoolid()
* 1998-07-04 Ako set tcp timeout option
* 1998-07-10 Framstag pid is now in OUTGOING/.lock
* 1998-07-11 Framstag notification my be USERSPOOL/config/notify
* 1998-07-12 Framstag log last msg-sender in
* USERSPOOL/config/msg@localhost
* 1998-08-20 Framstag added maxthruput option
* 1998-08-21 Chris Foote fixed free_space-bug with BIG partitions
* 1998-08-21 Framstag fixed sfsd mismatch bug
* 1998-10-29 Framstag fixed iso2utf space encoding bug (O-SAFT)
* 1999-03-21 Framstag added +batchmode to pgp-call
* 2000-06-22 Framstag fixed Linux glibc 2.1 incompatibilities
* 2000-12-10 Daniel Kobras fixed notify security bug
* 2000-12-26 Framstag fixed several security bugs as reported in
* Bug#76048 of Debian Bug Tracking System
* 2000-12-27 Framstag fixed bug: spaces in file names
* 2000-12-28 Framstag fixed :NULL: recipient bug
* 2001-01-10 Framstag fopen() ==> rfopen()
* 2001-01-17 cph fixed auth/log security bug
* 2001-01-17 Framstag mail2user() now runs in a subprocess
* 2001-02-02 Framstag fixed openlog() bug
* 2001-02-06 Framstag added timeout on waiting response from client
* 2001-08-26 sec workaround for sete[ug]id on FreeBSD
*
*
* The sendfile-daemon of the sendfile package.
* sendfiled receives files for a specified recipient, stores them in the
* sendfile spool directory and informs the recipient.
* It also receives messages for a specified recipient and display them
* on the recipients terminal.
* sendfiled is spawned via the inetd superserver. See the README file
* for installing hints.
*
* Copyright © 1995-2001 Ulli Horlacher
* This file is covered by the GNU General Public License
*/
#include "config.h" /* various definitions */
#include <time.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <utime.h>
#include <stdio.h>
#include <ctype.h>
#include <netdb.h>
#include <fcntl.h>
#include <utmp.h>
#include <grp.h>
#include <dirent.h>
#include <pwd.h>
#include <signal.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "reply.h" /* the 3 digit reply codes with text messages */
#include "peername.h" /* get the name of the calling host */
#include "string.h" /* extended string functions */
#include "message.h" /* information, warning and error messages */
#include "spool.h" /* operations on files in the sendfile spool */
#include "net.h" /* network stuff */
#include "io.h" /* socket read/write */
#include "utf7.h" /* UTF-7 coding */
#include "address.h" /* address routines */
#include "lock.h" /* file locking */
#if defined(HAVE_GETOPT_H)
#include <getopt.h>
#else
int getopt(int, char * const *, const char *);
extern int opterr;
extern int optind;
extern int optopt;
extern char *optarg;
#endif
#if defined(SOLARIS2)
int gethostname(char *, int);
#endif
/*
#if defined(LINUX)
int putenv(const char *);
int gethostname(char *, size_t);
int symlink(const char *, const char *);
#endif
*/
#if defined(IRIX) || defined(IRIX64)
#include <sys/statfs.h>
#elif defined(AIX3)
#include <sys/statfs.h>
int statfs(char *, struct statfs *);
#elif defined(OSF1)
#include <sys/mount.h>
#include <ustat.h>
int statfs(char *p, struct statfs *, int);
#elif defined(HAVE_SYS_STATVFS_H)
#include <sys/statvfs.h>
#define statfs statvfs
#define FSBS 1024 /* dirty hack against broken statvfs block information */
#elif defined(BSD) || defined(ULTRIX)
#include <sys/param.h>
#include <sys/mount.h>
#else
#include <sys/vfs.h>
#endif
#ifdef SYSV
#define DU "du -ks "
#else
#define DU "du -s "
#endif
/* stupid AIX comes with no include files for networking and other stuff */
#if defined(AIX3) || defined(ULTRIX)
#include "bsd.h"
#endif
#ifndef AIX
#ifndef CONVEXOS
FILE *popen(const char *, const char *);
#endif
int pclose(FILE *);
#endif
#ifdef ULTRIX
int statfs(char *, struct fs_data *);
#endif
#ifdef NEXT
int statfs(char *, struct statfs *);
#endif
#ifdef BSD
#ifndef O_SYNC
#define O_SYNC O_FSYNC
#endif
#endif
#ifdef HPUX
#define seteuid(a) setuid(a)
#define setegid(a) setgid(a)
#endif
#ifndef _PATH_UTMP
#define _PATH_UTMP "/etc/utmp"
#endif
#define DEBUG(a) if (dbf) fprintf(dbf,"%s\n",a)
#define timeout 300
/* get a command line */
int getaline(char *);
/* get next SAFT command from client */
int getcmd(char *, char *);
/* write one line to header spool file */
void writeheader(int, const char *, const char *);
/* notify recipient and send reply to client */
void notify_reply(int *, char *, const char *, const char *, const char *,
int, int);
/* write a message to all ttys of the user */
int msg2tty(const char *, const char *, char *, int);
/* send a mail to the recipient */
void mail2user(const char *, const char *, const char *);
/* check killfile */
int restricted(const char *, const char *, char);
/* missed in <unistd.h> in some systems */
int seteuid(uid_t);
int setegid(gid_t);
/* sendfile spool daemon for outgoing files */
int sfsd(int, int, int, int, float);
/* send a file from the outgoing spool */
int send_spooldata(int, char *, char *, char *, char *, char *, float, int);
/* send a status report via mail to the local user */
int mail_report(const char *);
/* look for correct SAFT server and open connection */
int saftserver_connect(char *, char *);
/* check outgoing spool if there are expired files */
void check_outspool(int);
/* check if user is allowed to use sendfile
and create the user spool directories */
int check_userspool(char *, int);
/* bounce back a file from outgoing spool to the sender's incoming spool */
int bounce_file(char *, char *);
/* copy a file to a pipe descriptor */
int copy2pipe(const char *, int);
/* get the compressed and original file size */
int get_sizes(char *, unsigned long *, unsigned long *);
/* write debug output */
void dbgout(const char *);
/* simple interrupt handler for SIGCHLD */
void sigchld();
/* free disk space on spool device */
long free_space();
/* send a file to stdout */
int send_file(int);
/* open user log file */
int openlog(const char *);
/* clean termination routine */
void cleanexit();
/* timeout termination routine */
void timeoutexit();
/* delete tmp-file */
void cleanup();
/* send a message to a remote SAFT server */
int send_msg(const char *, const char *, const char *);
/* set effective uid and gid for recipient */
void setreugid();
/* emulate su(1) */
int sudo(const char *, const char *);
/* print short help usage text */
int usage();
/* global variables */
char
*prg, /* name of the game */
*config, /* config file name */
domain[FLEN], /* domain name for get_domainname() */
localhost[FLEN], /* name of the local host */
path[MAXLEN], /* $PATH for environment */
chfile[MAXLEN], /* challenge file name */
csfile[MAXLEN], /* challenge sign file name */
userspool[MAXLEN], /* user spool directory */
userconfig[MAXLEN]; /* user config directory */
int
test=0, /* flag for test mode: receiving to /dev/null */
auth=0, /* flag for correct authentification */
quiet=3, /* no user messages to stdout */
client=0, /* flag to determine client or server */
verbose=0, /* flag for verbose mode */
packet_size=0; /* packet size */
uid_t ruid; /* recipient's user id */
gid_t rgid; /* recipient's group id */
FILE *dbf; /* debug file */
int main(int argc, char *argv[]) {
int
status, /* return status */
opt, /* arg-option to test for */
bytes, /* number of bytes to be received */
infd, /* input file descriptor */
outfd, /* input file descriptor */
shfd, /* spool header file descriptor */
sdfd, /* spool data file descriptor */
lfd, /* log file descriptor */
nblocks, /* number of packet length blocks */
bn, /* block number to read */
maxfiles, /* maximal number of allowed files per user */
minfree, /* minimum free spool disk space in MB */
maxspool, /* maximum spool disk space quota, total in MB */
bounce, /* days after files are bounced to sender */
parallel, /* flag for multple spool daemons */
retry, /* minutes to wait for retrying to deliver */
spoolsize, /* current spool size in KB */
wconf, /* flag for writing config file (0=reading) */
notify, /* flag for sending a message */
bell, /* flag for tty bell */
sys_bell, /* system flag for tty bell */
fetchfile, /* flag for allowing SAFT fetchfile commands */
forwarding, /* flag for user file forwarding */
msglog, /* flag for logging incoming messages */
sys_msglog, /* system flag for logging incoming messages */
queue, /* flag for processing outgoing spool queue */
piping, /* flag for user file post-processing */
spooling, /* flag for outgoing spooling */
deleting, /* flag for allowing delete command */
userconfighome, /* flag for linking the user config to HOME */
sys_deleting, /* system flag for allowing delete command */
del_success, /* flag for successfull deleting a file */
flags, /* source, text, compress, tar and exe flag */
challenge, /* challenge for fetchfile authentification */
id, /* spool file id */
n; /* simple loop variable */
unsigned long
transmitted, /* bytes already transmitted */
size, /* size of the file */
osize; /* original size of the file (uncompressed) */
float mtp; /* maximum thruput for outgoing files */
char
*cp, /* simple char pointer */
*argp, /* argument string pointer */
*peer, /* sender host name */
*realr, /* real recipient name */
*aliasr, /* alias recipient name */
log, /* type of global logging */
acceptonly, /* flag for accepting only files or messages */
sys_notification, /* system flag for notification */
notification[MAXLEN], /* notification type (message, mail or action) */
line[MAXLEN], /* incoming command line */
rpipe[MAXLEN], /* receiving pipe */
arg[MAXLEN], /* the argument(s) of the command line */
cmd[MAXLEN], /* the command itself */
type[MAXLEN], /* file type: binary, source or text */
otype[MAXLEN], /* original file type with charset info */
subtype[MAXLEN], /* COMPRESSED or CRYPTED subtype */
compress[FLEN], /* compression type */
sizes[FLEN], /* original and compressed file size */
charset[MAXLEN], /* name of the character set */
attribute[MAXLEN], /* tar or exe attribute */
sign[MAXLEN], /* pgp signature */
comment[MAXLEN], /* file comment in UTF-7 */
pgp_bin[MAXLEN], /* the pgp binary */
forcepgp[FLEN], /* force pgp option */
sys_forcepgp[FLEN], /* force pgp option default */
date[MAXLEN], /* date string of file */
currentdate[FLEN], /* current date */
shfile[MAXLEN], /* spool header file name */
sdfile[MAXLEN], /* spool data file name */
tmp[3*MAXLEN], /* temporary string */
real[MAXLEN], /* sender real name in UTF-7 */
forward[MAXLEN], /* user forward address */
sender[MAXLEN], /* user@senderhost (real name) */
utfsender[MAXLEN], /* sender in UTF-7 */
logsender[MAXLEN], /* sender for log file */
filename[MAXLEN], /* file name in UTF-7 */
recipient[MAXLEN], /* local user */
mailto[MAXLEN], /* notification mail recipient */
saftserver[MAXLEN], /* real saft server name */
packet[OVERSIZE], /* data packet to read */
msgh[MAXLEN], /* message header to user-tty */
msg[3*MAXLEN], /* message to user-tty */
logdata[OVERSIZE]; /* log file data */
unsigned char *ucp; /* simple unsigend char pointer */
struct stat finfo; /* information about a file */
struct filelist *flp; /* file list pointer */
struct senderlist
*sls, /* sender list start */
*slp; /* sender list pointer */
time_t timetick,tt1,tt2; /* unix time (in seconds) */
FILE
*inf, /* for various files */
*outf, /* output file */
*pp; /* pipe stream */
/* set default values */
id=0;
lfd=0;
mtp=0;
bell=1;
ruid=0;
rgid=0;
sdfd=0;
shfd=0;
auth=0;
infd=0;
flags=0;
queue=0;
retry=0;
bounce=0;
piping=1;
notify=0;
msglog=0;
minfree=0;
fetchfile=0;
deleting=1;
parallel=1;
maxspool=0;
spooling=2;
sys_bell=1;
maxfiles=200;
forwarding=1;
sys_msglog=0;
acceptonly=0;
transmitted=0;
sys_deleting=1;
userconfighome=0;
log='b';
sys_notification='t';
*sign=0;
*date=0;
*chfile=0;
*csfile=0;
*mailto=0;
*sender=0;
*domain=0;
*comment=0;
*filename=0;
*forcepgp=0;
*utfsender=0;
*recipient=0;
*attribute=0;
*saftserver=0;
*sys_forcepgp=0;
config=CONFIG;
prg=argv[0];
strcpy(notification,"t");
strcpy(charset,CHARSET);
strcpy(type,"BINARY");
strcpy(otype,"BINARY");
dbf=NULL;
sls=NULL;
flp=NULL;
inf=NULL;
outf=NULL;
realr=NULL;
/* determine pgp program */
strcpy(pgp_bin,PGP);
if ((cp=getenv("SF_PGP"))) strcpy(pgp_bin,cp);
/* switch off buffering for STDIN */
setvbuf(stdout,NULL,_IONBF,0);
/* setbuf(stdout,NULL); */
/* set tcp timeout option */
#ifdef SO_KEEPALIVE
{
int flag=1;
setsockopt(fileno(stdin),SOL_SOCKET,SO_KEEPALIVE,
(void *)&flag,sizeof(flag));
}
#endif
/* scan the command line */
while ((opt=getopt(argc, argv, "Vh?fvqQdc:")) > 0) {
switch (opt) {
case ':':
case 'h':
case '?': exit(usage());
case 'c': config=optarg; break;
case 'd': dbf=rfopen(DBF,"a"); break;
case 'v': verbose=1; quiet=0; break;
case 'q': queue=1; break;
case 'Q': queue=2; break;
case 'f': printf("%ld\n",free_space()); exit(0);
case 'V': message(prg,'I',"version "VERSION" revision "REVISION); exit(0);
}
}
#ifdef DBG
if (!dbf) dbf=rfopen(DBF,"a");
#endif
if (dbf) {
timetick=time(0);
strftime(tmp,20,"%Y-%m-%d %H:%M:%S",localtime(&timetick));
dbgout(tmp);
}
/* get the local host name */
if (gethostname(localhost,FLEN-1)<0) strcpy(localhost,"localhost");
/* parse the global config-file */
if ((inf=rfopen(config,"r"))) {
while (fgetl(line,inf)) {
/* prepare line to be parsed */
if ((cp=strchr(line,'#'))) *cp=0;
if ((cp=strchr(line,'='))) *cp=' ';
str_tolower(str_trim(line));
/* is there an option and an argument? */
if ((argp=strchr(line,' '))) {
*argp=0; argp++;
if (str_eq(line,"bell")) {
if (str_eq(argp,"off")) sys_bell=0;
continue;
}
if (str_eq(line,"userconfig")) {
if (str_eq(argp,"home")) userconfighome=1;
continue;
}
if (str_eq(line,"forwarding")) {
if (str_eq(argp,"off")) forwarding=0;
continue;
}
if (str_eq(line,"piping")) {
if (str_eq(argp,"off")) piping=0;
continue;
}
if (str_eq(line,"deleting")) {
if (str_eq(argp,"off")) sys_deleting=0;
continue;
}
if (str_eq(line,"fetchfile")) {
if (str_eq(argp,"on")) fetchfile=1;
continue;
}
if (str_eq(line,"path")) {
if (*argp == '/') {
snprintf(MAXS(path),"PATH=%s",argp);
putenv(path);
}
continue;
}
if (str_eq(line,"pgp")) {
if (*argp == '/') strcpy(pgp_bin,argp);
continue;
}
if (str_eq(line,"forcepgp")) {
snprintf(MAXS(sys_forcepgp),"%s",argp);
continue;
}
if (str_eq(line,"spooling")) {
if (str_eq(argp,"off")) spooling=0;
if (str_eq(argp,"nostart")) spooling=1;
if (str_eq(argp,"on")) spooling=2;
continue;
}
if (str_eq(line,"parallel")) {
if (str_eq(argp,"off")) parallel=0;
continue;
}
if (str_eq(line,"maxthruput")) {
mtp=atof(argp);
if (mtp<0) mtp=0;
continue;
}
if (str_eq(line,"msglog")) {
if (str_eq(argp,"on")) sys_msglog=1;
continue;
}
if (str_eq(line,"maxfiles")) {
maxfiles=atoi(argp);
continue;
}
if (str_eq(line,"maxspool")) {
maxspool=atoi(argp);
continue;
}
if (str_eq(line,"minfree")) {
minfree=atoi(argp);
continue;
}
if (str_eq(line,"packet")) {
packet_size=atoi(argp);
continue;
}
if (str_eq(line,"bounce")) {
bounce=atoi(argp);
continue;
}
if (str_eq(line,"retry")) {
retry=atoi(argp);
continue;
}
if (str_eq(line,"acceptonly")) {
acceptonly=*argp;
continue;
}
if (str_eq(line,"saftserver")) {
strcpy(saftserver,argp);
continue;
}
if (str_eq(line,"domain")) {
strcpy(domain,argp);
continue;
}
if (str_eq(line,"notification")) {
if (str_eq(argp,"mail")) sys_notification='m';
if (str_eq(argp,"both")) sys_notification='b';
if (str_eq(argp,"none")) sys_notification=0;
continue;
}
if (str_eq(line,"log")) {
if (str_eq(argp,"in")) log='i';
if (str_eq(argp,"out")) log='o';
if (str_eq(argp,"both")) log='b';
if (str_eq(argp,"none")) log=0;
continue;
}
}
}
fclose(inf);
}
/* set tcp packet length */
if (packet_size<1) packet_size=PACKET;
/* process outgoing spool queue? */
if (queue) {
if (getuid()) {
errno=0;
message(prg,'F',"only root is allowed to process the outgoing spool");
}
switch (sfsd(queue,parallel,bounce,retry,mtp)) {
case -1: message(prg,'F',"cannot start spool daemon");
case 0: message(prg,'I',"spool daemon started"); break;
case 1: message(prg,'W',"a sendfile spool daemon is already running");
}
exit(0);
}
/* send the server ready message */
reply(220);
/* main loop for command line parsing and getting data */
for (;;) {
/* establish timeout signal handler */
signal(SIGALRM,timeoutexit);
alarm(timeout);
/* get next SAFT command and argument */
status=getcmd(cmd,arg);
if (status<0) {
notify_reply(¬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)+4<MAXLEN) {
snprintf(MAXS(utfsender),"%s@%s %s",arg,peer,real);
snprintf(MAXS(tmp),"%s@%s (%s)",arg,peer,real);
utf2iso(0,sender,NULL,NULL,tmp);
if ((cp=strchr(utfsender,' '))) {
*cp=0;
snprintf(MAXS(logsender),"%s (%s)",utfsender,cp+1);
*cp=' ';
} else
strcpy(logsender,utfsender);
} else {
snprintf(MAXS(utfsender),"???@%s",peer);
snprintf(MAXS(sender),"???@%s",peer);
}
reply(200);
continue;
}
/* CHARSET command? (only for compatibilty reason) */
if (str_eq(cmd,"CHARSET")) {
/* is there an argument? */
if (*arg==0) {
reply(505);
continue;
}
/* save the charset name */
strcpy(charset,arg);
reply(200);
continue;
}
/* DATE command? */
if (str_eq(cmd,"DATE")) {
/* is there an argument? */
if (*arg==0) {
reply(505);
continue;
}
/* save the date */
utf2iso(0,NULL,date,NULL,arg);
/* parse ISO-8601 date & time string */
if ((cp=strchr(date,'T'))) *cp=' ';
if (!strchr(date,'-')) {
strcpy(tmp,date);
snprintf(MAXS(date),"%c%c%c%c-%c%c-%c%c %c%c:%c%c:%c%c",
tmp[0],tmp[1],tmp[2],tmp[3],
tmp[4],tmp[5],
tmp[6],tmp[7],
tmp[9],tmp[10],
tmp[11],tmp[12],
tmp[13],tmp[14]);
}
reply(200);
continue;
}
/* SIGN command? */
if (str_eq(cmd,"SIGN")) {
/* is there an argument? */
if (*arg==0) {
reply(505);
continue;
}
strcpy(sign,arg);
reply(200);
continue;
}
/* FILE command? */
if (str_eq(cmd,"FILE")) {
/* forward address set? */
if (*saftserver) {
/* is this a msg-only server? */
if (acceptonly=='m') printf("510-This SAFT-server can only receive "
"messages, no files.\r\n");
if (*recipient)
printf("510 For sending files use: %s@%s\r\n",recipient,saftserver);
else
printf("510 For sending files use: user@%s\r\n",saftserver);
fflush(stdout);
continue;
}
/* is this a msg-only server? */
if (acceptonly=='m') {
reply(512);
continue;
}
/* user forward? */
if (forwarding && *forward) {
printf("510 User has set a forward to %s\r\n",forward);
fflush(stdout);
continue;
}
/* receiving files currently disabled? */
if (stat(SPOOL"/.nosendfile",&finfo)==0) reply(421);
/* is there an argument? */
if (*arg==0) {
reply(505);
continue;
}
auth=0;
flp=NULL;
/* save the filename (still in UTF-7) */
strcpy(filename,arg);
reply(200);
continue;
}
/* ATTR command? */
if (str_eq(cmd,"ATTR")) {
/* is there an argument? */
if (*arg==0) {
reply(505);
continue;
}
str_toupper(arg);
/* exe attribute? */
if (str_eq(arg,"EXE")) {
strcpy(attribute,arg);
flags=(flags|F_EXE)&~F_TAR;
reply(200);
continue;
}
/* tar attribute? */
if (str_eq(arg,"TAR")) {
strcpy(attribute,arg);
flags=(flags|F_TAR)&~F_EXE;
reply(200);
continue;
}
/* reset attribute? */
if (str_eq(arg,"NONE")) {
*attribute=0;
flags=flags&~F_TAR&~F_EXE;
reply(200);
continue;
}
reply(504);
continue;
}
/* TYPE command? */
if (str_eq(cmd,"TYPE")) {
/* parse the type command and check if it is valid */
str_toupper(arg);
*compress=0;
/* default compression method is gzip */
if (strstr(arg," COMPRESSED")) strcpy(compress,"gzip");
/* other compression method specified? */
if (strstr(arg," COMPRESSED=")) {
*compress=0;
cp=strchr(arg,'=');
*cp=0;
cp++;
if (str_eq(cp,"GZIP")) strcpy(compress,"gzip");
if (str_eq(cp,"BZIP2")) strcpy(compress,"bzip2");
if (!*compress) {
reply(504);
continue;
}
}
/* test if compression programm is available */
if (str_eq(compress,"bzip2") && !whereis(BZIP2)) {
reply(504);
continue;
}
/* we cannot test gzip, because IRIX and others have non-standard
* places for it! STUPID!!
*/
/* default encryption method is pgp */
if (strstr(arg," CRYPTED=PGP")) {
cp=strchr(arg,'=');
*cp=0;
}
/* save the type */
strcpy(type,arg);
strcpy(otype,arg);
if (str_eq(compress,"gzip")) strcat(otype,"=GZIP");
if (str_eq(compress,"bzip2")) strcat(otype,"=BZIP2");
/* charset specified? */
if (strncmp(type,"TEXT=",5)==0) {
/* save subtype (CRYPTED or COMPRESSED) */
*subtype=0;
if ((cp=strrchr(type,' '))) {
strcpy(subtype,cp);
*cp=0;
}
/* extract CHARSET from TYPE string */
cp=strchr(type,'=');
*cp=0;
strcpy(charset,cp+1);
strcat(type,subtype);
}
/* check type format syntax */
if (!(str_eq(type,"TEXT") ||
str_eq(type,"TEXT CRYPTED") ||
str_eq(type,"TEXT COMPRESSED") ||
str_eq(type,"MIME") ||
str_eq(type,"MIME CRYPTED") ||
str_eq(type,"MIME COMPRESSED") ||
str_eq(type,"SOURCE") ||
str_eq(type,"SOURCE CRYPTED") ||
str_eq(type,"SOURCE COMPRESSED") ||
str_eq(type,"BINARY") ||
str_eq(type,"BINARY CRYPTED") ||
str_eq(type,"BINARY COMPRESSED"))) {
reply(501);
continue;
}
/* save the flags */
flags=flags&~F_SOURCE&~F_TEXT&~F_MIME&~F_COMPRESS&~F_CRYPT;
if (strstr(type,"TEXT")) flags=flags|F_TEXT;
if (strstr(type,"MIME")) flags=flags|F_MIME;
if (strstr(type,"SOURCE")) flags=flags|F_SOURCE;
if (strstr(type,"CRYPTED")) flags=flags|F_CRYPT;
if (strstr(type,"COMPRESSED")) flags=flags|F_COMPRESS;
reply(200);
continue;
}
/* SIZE command? */
if (str_eq(cmd,"SIZE")) {
/* is there an argument? */
if (*arg==0) {
reply(505);
continue;
}
/* save the size(s) */
status=get_sizes(arg,&size,&osize);
if (status) {
reply(status);
continue;
}
strcpy(sizes,arg);
transmitted=0;
/* sending not to /dev/null */
if (!test) {
/* quota exceeded for sendfile? */
if (free_space() <= minfree+size/1048576)
notify_reply(¬ify,notification,sender,recipient,
mailto,bell,452);
/* spool quota limit set? */
if (maxspool) {
pp=popen(DU SPOOL,"r");
if (pp && fgetl(tmp,pp)) {
sscanf(tmp,"%d",&spoolsize);
/* too much spool usage? */
if (spoolsize+size/1024>maxspool*1024)
notify_reply(¬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)<packet_size) {
close(sdfd);
/*
unlink(sdfile);
unlink(shfile);
*/
notify_reply(¬ify,notification,sender,recipient,mailto,bell,415);
}
if (!test && writen(sdfd,packet,packet_size)<packet_size) {
close(sdfd);
/*
unlink(sdfile);
unlink(shfile);
*/
notify_reply(¬ify,notification,sender,recipient,mailto,bell,452);
}
}
/* copy the last bytes to the spool file */
if ((n=bytes-nblocks*packet_size)) {
if (readn(0,packet,n)<n) {
close(sdfd);
/*
unlink(sdfile);
unlink(shfile);
*/
notify_reply(¬ify,notification,sender,recipient,mailto,bell,415);
}
if (!test && writen(sdfd,packet,n)<n) {
close(sdfd);
/*
unlink(sdfile);
unlink(shfile);
*/
notify_reply(¬ify,notification,sender,recipient,mailto,bell,452);
}
}
if (!test) close(sdfd);
/* get the receive date */
timetick=time(0);
strftime(currentdate,20,"%Y-%m-%d %H:%M:%S",localtime(&timetick));
/* reformat file name for log file */
if (str_eq(attribute,"TAR")) {
filename[sizeof(filename)-12]=0;
strcat(filename," (archive)");
}
if (!test) {
/* write to the log file */
if ((lfd=openlog("log"))) {
writeheader(lfd,"FROM",logsender);
writeheader(lfd,"FILE",filename);
writeheader(lfd,"DATE",currentdate);
if (*comment) writeheader(lfd,"COMMENT",comment);
if (*sign) writeheader(lfd,"SIGN",sign);
write(lfd,"\n",1);
close(lfd);
}
/* receiving to a pipe? */
if (*rpipe) {
/* open post-processing pipe */
snprintf(MAXS(tmp),"cat %s/%s %s/%s | %s && rm -f %s/%s %s/%s",
userspool,shfile,
userspool,sdfile,
rpipe,
userspool,shfile,
userspool,sdfile);
/*
sprintf(tmp,"sudo return for %s: %d",recipient,sudo(recipient,tmp));
dbgout(tmp);
*/
*notification=0;
}
}
/* add sender and spool number to notification list */
if (*notification == '/') {
/* first argument shall be sender address */
if (strchr(notification,' ') == strrchr(notification,' ')) {
strcpy(tmp,utfsender);
if ((cp=strchr(tmp,' '))) *(cp+1)=0;
if (strlen(notification)+strlen(tmp)+1<sizeof(notification))
strcat(notification,tmp);
}
sprintf(tmp,"%d ",id);
if (strlen(notification)+15<sizeof(notification))
strcat(notification,tmp);
}
/* all ok */
reply(201);
/* reset uid and gid to root */
seteuid(0);
setegid(0);
/* add entry to global logfile if required */
/* ### todo: this part needs locking! ### */
if ((log=='b' || log=='i') && (outf=rfopen(INLOG,"a"))) {
stat(INLOG,&finfo);
if (finfo.st_size==0)
fprintf(outf,"# use utf7decode to view this file\n\n");
fprintf(outf,"FROM\t%s\nTO\t%s\nDATE\t%s\nFILE\t%s\nSIZES\t%s\n\n",
logsender,recipient,currentdate,filename,sizes);
fclose(outf);
}
/* reset attributes */
*filename = *sign = *comment = *attribute = *sizes = *date = 0;
flags=transmitted=0;
flp=NULL;
strcpy(charset,CHARSET);
strcpy(type,"BINARY");
/* recipient has to be notified at the end */
notify=1;
continue;
}
/* RESEND command? */
if (str_eq(cmd,"RESEND")) {
/* sender, recipient and filename already specified? */
if (*sender==0 || *recipient==0 || *filename==0 || *sizes==0) {
reply(503);
continue;
}
/* test mode? */
if (test)
transmitted=0;
else {
/* check if this file has been already sent */
if ((sls=scanspool(sender))) {
/* loop over files list */
for (flp=sls->flist; flp!=NULL; flp=flp->next) {
/* is it the same file? */
if (str_eq(filename,flp->fname) &&
(str_eq(date,flp->date) || !*date) &&
flags==flp->flags) {
/* with same sizes? */
snprintf(MAXS(tmp),"%ld %ld",flp->csize,flp->osize);
if (str_eq(tmp,sizes)) {
/* number of bytes already transmitted */
transmitted=flp->tsize;
break;
}
}
}
}
}
/* emulate reply(230) */
printf("230 %ld bytes have already been transmitted.\r\n",transmitted);
fflush(stdout);
continue;
}
/* VERSION command? */
if (str_eq(cmd,"VERSION")) {
reply(215);
continue;
}
/* COMMENT command? */
if (str_eq(cmd,"COMMENT")) {
/* is there an argument for "COMMENT" command? */
if (*arg==0) {
reply(505);
continue;
}
strcpy(comment,arg);
reply(200);
continue;
}
/* insider joke :-) */
if (str_eq(cmd,"HOPPEL")) {
reply(203);
continue;
}
/* AUTH before ID? */
if (str_eq(cmd,"AUTH")) {
reply(503);
continue;
}
/* ID command? */
if (str_eq(cmd,"ID")) {
auth=0;
if (!fetchfile) {
reply(502);
continue;
}
/* is there an argument? */
if (*arg==0) {
reply(505);
continue;
}
/* too many arguments? */
if (strchr(arg,' ')) {
reply(504);
continue;
}
/* is pgp available? */
if (!whereis(pgp_bin)) reply(421);
/* check the user's spool and get his uid/gid */
if (check_userspool(arg,userconfighome)<0 || chdir(userspool)<0)
reply(410);
srand(time(0));
challenge=rand();
printf("331 challenge: %d\r\n",challenge);
fflush(stdout);
/* get the next command line from the client, must be "auth" */
status=getcmd(cmd,arg);
if (status<0) {
notify_reply(¬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; n<MAXLEN-1; n++) {
/* next char from socket */
c=getchar();
/* quit if there is no more a connection to the client */
if (c==EOF) return(-1);
/* surpress non-ASCII chars */
if (c<9 || c==11 || c==12 || (c>13 && c<32) || c>126) {
ctrl=1;
n--;
} else {
/* copy the char into the command line string */
ptr[n]=c;
/* check for EOL */
if (c=='\n') break;
if (c=='\r') n--;
}
}
/* input line overrun? */
if (n==MAXLEN-1 && ptr[n]!='\n') {
/* read to next lf */
while (c!='\n') c=getchar();
n=0;
reply(506);
} else
if (ctrl) reply(205);
ptr[n]=0;
/* trim all white spaces */
if (n) str_trim(ptr);
return(strlen(ptr));
}
/*
* getcmd - get next SAFT command from client
*
* OUTPUT: cmd - SAFT command
* arg - argument of SAFT command
*
* RETURN: status from getaline()
*/
int getcmd(char *cmd, char *arg) {
int status; /* return value */
char
*cp, /* simple char pointer */
line[MAXLEN]; /* incoming command line */
*cmd=0;
*arg=0;
status=0;
while (status==0) status=getaline(line);
if (status>0) {
/* extract the command name and the argument */
strcpy(cmd,line);
cp=strchr(cmd,' ');
if (cp) {
*cp=0;
strcpy(arg,cp+1);
}
str_toupper(cmd);
}
return(status);
}
/*
* writeheader - write one line of header information to log file
*
* INPUT: fd - file descriptor
* attribute - name of the header attribute
* value - contents of the header attribute
*/
void writeheader(int fd, const char *attribute, const char *value) {
int hsize; /* header string size */
char header[2*MAXLEN]; /* header string */
snprintf(MAXS(header),"%s\t%s\n",attribute,value);
hsize=strlen(header);
if (write(fd,header,hsize)<hsize) reply(412);
}
/*
* notify_reply - notify user and sent reply code if given; a fatal error
* (reply code 4xx) will terminate sendfiled
*
* INPUT: notify - notify flag
* notification - kind of notification
* sender - sender name
* recipient - local recipient
* mailto - address to send mail to
* bell - flag for adding bell
* replycode - server reply code to send
*/
void notify_reply(int *notify, char *notification,
const char *sender, const char *recipient, const char *mailto,
int bell, int replycode) {
char msgh[MAXLEN]; /* message to user-tty */
if (!test) {
if (*notify) {
/* set time out */
signal(SIGALRM,cleanexit);
alarm(60);
#ifdef HAEGAR
snprintf(msgh,MAXLEN-3
"\n>>> INFO <<< SAFT-Daemon has a new file for you!"
"\n\nFrom: %s ( saft://%s/%s )\n\nType \"receive\".\n",
sender,localhost,recipient);
#else
snprintf(msgh,MAXLEN-3,
"%s has sent a file to you (%s@%s). Type \"receive\".",
sender,recipient,localhost);
#endif
switch (*notification) {
case 'b': mail2user(mailto,sender,msgh);
case 't': if (strchr(mailto,'@')) {
snprintf(msgh,MAXLEN-1,"new file from %s",sender);
send_msg(msgh,mailto,recipient);
} else {
if (bell) strcat(msgh,"\007");
msg2tty(recipient,msgh,"",O_NONBLOCK);
}
break;
case 'm': mail2user(mailto,sender,msgh); break;
case '/': strcat(notification,">/dev/null 2>&1 &");
sudo(recipient,notification);
}
/* reset time out */
signal(SIGALRM,SIG_IGN);
}
}
*notify=0;
/* send reply if given */
if (replycode) reply(replycode);
}
/*
* msg2tty - send a one line message to the recipient (tty(s) or FIFO)
*
* INPUT: recipient - recipient of the message
* msgh - the message header
* msg - the message
* mode - non-blocking or synchronous mode
*
* RETURN: 0 if successfull, -1 if failed
*/
int msg2tty(const char *recipient, const char *msgh, char *msg, int mode) {
char
*cp, /* simple character pointer */
tty[FLEN], /* name of tty */
user[9], /* username */
output[MAXLEN], /* msgh+msg */
msgcf[MAXLEN]; /* message control file */
int
wall, /* force writing to all ttys */
utmpfd, /* file descriptor for utmp */
mfd, /* message file descriptor */
success; /* return code */
struct utmp uinfo; /* information about a user */
struct stat finfo; /* information about a file */
FILE *inf; /* input file */
wall=0;
success=0;
user[8]=0;
if (test) return(0);
/* change effective uid to recipient for security reasons */
setreugid();
snprintf(MAXS(msgcf),"%s/config/tty@%s",userspool,localhost);
/* force writing to all ttys which are open? */
if (*msg && str_beq(msg,"wall!")) {
wall=1;
msg=strchr(msg,'!')+1;
}
if (*msg)
snprintf(MAXS(output),"\r\n%s\n\r%s\r\n",msgh,msg);
else
snprintf(MAXS(output),"\r\n%s\n\r",msgh);
/* is there a message control file? */
if (!wall && success<=0 && (inf=rfopen(msgcf,"r"))) {
/* read the tty name */
fgetl(tty,inf);
if ((cp=strchr(tty,'\n'))) *cp=0;
fclose(inf);
/* belongs the tty to the recipient and is it writable? */
if (stat(tty,&finfo)==0 && finfo.st_uid==ruid &&
((finfo.st_mode&S_IWOTH) || (finfo.st_mode&S_IWGRP))) {
/* write to dedicated tty */
mfd=open(tty,O_WRONLY|mode);
success=write(mfd,output,strlen(output));
close(mfd);
}
}
/* no write success so far? */
if (success<=0) {
success=0;
/* search the utmp file (not standarisized, grrrr) */
utmpfd=open(_PATH_UTMP,O_RDONLY);
if (utmpfd<0) utmpfd=open("/var/adm/utmp",O_RDONLY);
if (utmpfd<0) utmpfd=open("/var/run/utmp",O_RDONLY);
if (utmpfd<0) {
seteuid(0);
setegid(0);
return(-1);
}
/* scan through utmp (currently logged in users) */
while (read(utmpfd,(char *)&uinfo,sizeof(uinfo))>0) {
#if defined(NEXT) || defined(BSD) || defined(ULTRIX) || defined(SOLARIS1)
strncpy(user,uinfo.ut_name,8);
if (str_eq(recipient,user)) {
#ifdef JEDPARSE
}
#endif
#else
strncpy(user,uinfo.ut_user,8);
if (uinfo.ut_type==USER_PROCESS && str_eq(recipient,user)) {
#endif
/* get the tty */
snprintf(MAXS(tty),"/dev/%s",uinfo.ut_line);
/* is the tty writeable? */
if (stat(tty,&finfo)==0 &&
((finfo.st_mode&S_IWOTH) || (finfo.st_mode&S_IWGRP))) {
mfd=open(tty,O_WRONLY|mode);
/* write message to tty */
success=success | write(mfd,output,strlen(output));
close(mfd);
}
}
}
close(utmpfd);
}
/* reset uid to root */
seteuid(0);
setegid(0);
if (success>0) return(0); else return(-1);
}
/*
* mail2user - send the recipient a mail in a subprocess
*
* INPUT: recipient - recipient of the mail
* sender - sender of the file
* msg - the message
*/
void mail2user(const char *recipient, const char *sender, const char *msg) {
char *cp, /* simple character pointer */
line[MAXLEN], /* one text line */
cmd[MAXLEN]; /* command for pipe */
FILE *pin, /* input pipe */
*pout; /* output pipe */
struct passwd *pwe; /* password entry struct */
pid_t pid;
/* security check: disallow superuser/group */
pwe=getpwuid(ruid);
if (pwe==NULL || ruid==0 || rgid==0) return;
/* spawn subprocess */
signal(SIGCHLD,SIG_IGN);
pid=fork();
if (pid) return;
/* change user */
/* note: setuid does not work if euid>0 ! STUPID! */
chdir(pwe->pw_dir);
seteuid(0);
setegid(0);
if (setgid(rgid)<0) exit(1);
initgroups(pwe->pw_name,pwe->pw_gid);
if (setuid(ruid)<0) exit(1);
/* strip off bell */
if ((cp=strchr(msg,7))) *cp=0;
/* delete ' in sendername */
while ((cp=strchr(sender,'\''))) *cp=' ';
/* open pipe to sendmail */
snprintf(MAXS(cmd),SENDMAIL" %s",recipient);
pout=popen(cmd,"w");
/* fill out mail message */
if (pout) {
/* fprintf(pout,"From: %s\n",recipient); */
fprintf(pout,"To: %s\n",recipient);
fprintf(pout,"Subject: new file from %s\n\n",sender);
/* try to open receive pipe */
pin=popen(RECEIVE" -l","r");
if (fgetl(line,pin)==NULL) {
pclose(pin);
pin=popen("receive -l","r");
if (fgetl(line,pin)==NULL) {
pclose(pin);
pin=popen("/client/bin/receive -l","r");
if (fgetl(line,pin)==NULL) {
pclose(pin);
pin=NULL;
}
}
}
/* add output from receive command, too */
if (pin) {
fprintf(pout,"\n");
while (fgetl(line,pin)) fprintf(pout," %s",line);
pclose(pin);
}
/*
fprintf(pout,"\n\n");
fprintf(pout,"To get your file(s), do: rlogin saft.uni-hildesheim.de\n");
fprintf(pout,"and then type: receive\n");
*/
fprintf(pout,".\n");
pclose(pout);
}
exit(0);
}
/*
* restricted - check killfile
*
* INPUT: sender - sender name
* recipient - local recipient
* type - type of restriction: m(essage) or f(ile)
*
* RETURN: 1 if not allowed to send, 0 if allowed
*/
int restricted(const char *sender, const char *recipient, char type) {
int m;
char *cp,
killfile[MAXLEN],
kfu[MAXLEN],
kfm[MAXLEN],
line[MAXLEN],
from[MAXLEN];
FILE *inf;
if (test) return(0);
setreugid();
snprintf(MAXS(killfile),"%s/config/restrictions",userspool);
*kfm=*kfu=0;
/* open and check killfile */
inf=rfopen(killfile,"r");
if (inf==NULL) return(0);
strcpy(from,sender);
if ((cp=strchr(from,' '))) *cp=0;
while (fgetl(line,inf)) {
line[MAXLEN-1]=0;
sscanf(line,"%s%s",kfu,kfm);
if (kfm[0]==0)
m='b';
else
m=tolower(kfm[0]);
kfm[1]=0;
if (simplematch(from,kfu,1) && (m==type || m=='b')) {
fclose(inf);
return(1);
}
}
fclose(inf);
return(0);
}
/*
* mkrdir - make recursive directory
*
* INPUT: pathname - directory name
* mode - permission mode bits
*
* RETURN: 0 if ok, else errorcode from mkdir(2)
*/
int mkrdir(const char *pathname, mode_t mode) {
return 0;
}
/*
* free_space - free disk space on spool device
*
* RETURN: MBs of free disk space, -1 on error
*/
long free_space() {
#ifdef ULTRIX
struct fs_data fsinfo; /* information about the file system */
#else
struct statfs fsinfo; /* information about the file system */
#endif
#if defined(IRIX) || defined(IRIX64)
if (statfs(SPOOL,&fsinfo,sizeof(struct statfs),0)==0)
return ((long)(fsinfo.f_bsize/1048576.0L*fsinfo.f_bfree));
#elif defined(ULTRIX)
if (statfs(SPOOL,&fsinfo)==0)
return (((struct fs_data_req *)&fsinfo)->bfreen/1024);
#elif defined(OSF1)
if (statfs(SPOOL,&fsinfo,sizeof(struct statfs))==0)
return ((long)(fsinfo.f_bsize/1048576.0L*fsinfo.f_bavail));
#else
if (statfs(SPOOL,&fsinfo)==0) {
#ifdef FSBS
/* hack, hack :-) */
fsinfo.f_bsize=FSBS;
#endif
return ((long)(fsinfo.f_bsize/1048576.0L*fsinfo.f_bavail));
}
#endif
return(-1);
}
/*
* get_sizes - get the compressed and original file size
*
* INPUT: string - the sizes in one string
*
* OUTPUT: size - the compressed size
* osize - the original size
*
* RETURN: 0 on success, reply-code on error
*/
int get_sizes(char *string, unsigned long *size, unsigned long *osize) {
char
max[FLEN],
s1[MAXLEN],
*s2;
#ifdef RLIMIT_FSIZE
struct rlimit rl;
#endif
strcpy(s1,string);
/* get maximum file size for this process */
#ifdef RLIMIT_FSIZE
getrlimit(RLIMIT_FSIZE,&rl);
snprintf(MAXS(max),"%lu",(unsigned long)rl.rlim_cur);
#else
/* this is 2**15 = max_signed_int */
strcpy(max,"2147483647");
#endif
/* get compressed and original file size string */
s2=strchr(s1,' ');
if (!s2) return(501);
*s2=0;
s2++;
/* more than maximum file size specified? */
if (strlen(s1)>strlen(max) ||
strlen(s2)>strlen(max) ||
(strlen(s1)==strlen(max) && strcmp(s1,max)>0) ||
(strlen(s2)==strlen(max) && strcmp(s2,max)>0)) return(453);
/* return numeric compressed and original file size */
sscanf(s1,"%lu",size);
sscanf(s2,"%lu",osize);
return(0);
}
/*
* send_msg - send a SAFT message to a remote server
*
* INPUT: msg - the message
* to - user@host recipient
*
* RETURN: 0 on success, -1 on failure
*/
int send_msg(const char *msg, const char *to, const char *recipient) {
int
sockfd; /* socket file descriptor */
char
*cp, /* simple char pointer */
user[FLEN], /* user name */
host[MAXLEN], /* host name */
tmp[MAXLEN],
line[MAXLEN]; /* one line of text */
cp=strchr(to,'@');
if (cp) {
*cp=0;
snprintf(MAXS(user),"%s",to);
snprintf(MAXS(host),"%s",cp+1);
} else {
snprintf(MAXS(user),"%s",to);
strcpy(host,localhost);
}
sockfd=open_connection(host,SAFT);
if (sockfd<0) return(-1);
/* no remote server or protocol error? */
sock_getline(sockfd,line);
if (!str_beq(line,"220 ") || !strstr(line,"SAFT")) return(-1);
/*
snprintf(MAXS(tmp),"connected to %s",host);
dbgout(tmp);
*/
snprintf(MAXS(line),"FROM %s autogenerated+ACA-SAFT+ACA-message",recipient);
snprintf(MAXS(line),"FROM %s",recipient);
sock_putline(sockfd,line);
if (!str_beq(getreply(sockfd),"200 ")) {
close(sockfd);
return(-1);
}
snprintf(MAXS(line),"TO %s",user);
sock_putline(sockfd,line);
if (!str_beq(getreply(sockfd),"200 ")) {
close(sockfd);
return(-1);
}
iso2utf(tmp,(char*)msg);
snprintf(MAXS(line),"MSG %s",tmp);
sock_putline(sockfd,line);
if (!str_beq(getreply(sockfd),"200 ")) {
close(sockfd);
return(-1);
}
sock_putline(sockfd,"QUIT");
getreply(sockfd);
close(sockfd);
return(0);
}
/*
* sfsd - sendfile spool daemon
*
* INPUT: queue - flag for processing the outgoing spool queue
* parallel - flag for multple spool daemons sending parallel
* bounce - days after files are bounced to sender
* retry - minutes to wait for retrying to deliver
* mtp - maximum thruput
*
* RETURN: 1 if spool daemon started, 0 if a spool daemon is already running
*/
int sfsd(int queue, int parallel, int bounce, int retry, float mtp) {
int
lockf, /* lock file descriptor */
error, /* error flag */
sockfd, /* socket file descriptor */
sockfd2; /* 2nd socket file descriptor for forwards */
char
*cp, /* simple char pointer */
*rs, /* reply string from the remote server */
*lockfn, /* lock file name */
errmsg[MAXLEN], /* error text from SAFT-connect */
tmp[MAXLEN], /* tmp string */
ahost[MAXLEN], /* alternate host to connect */
line[MAXLEN], /* incoming command line */
arecipient[MAXLEN], /* alternate recipient */
lrecipient[MAXLEN]; /* last recipient */
pid_t pid; /* process id */
struct hostlist
*hls, /* host list start */
*hlp; /* host list pointer */
struct outfilelist
*oflp; /* outgoing file list pointer */
FILE *inf; /* input file */
pid=-1;
error=0;
*errmsg=0;
rs="";
lockfn=OUTGOING"/.lock";
if (retry<0) retry=10;
if (bounce<=0) bounce=5;
/* test outgoing spool */
if (chdir(OUTGOING)<0) {
if (queue==1) message(prg,'F',OUTGOING);
snprintf(MAXS(tmp),OUTGOING" : %s",strerror(errno));
dbgout(tmp);
exit(1);
}
/* respect lock file when not in interactive mode */
if (queue!=1) {
/* try to create lock file */
if ((lockf=open(lockfn,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))<0) reply(421);
/* try to set a lock */
if (wlock_file(lockf)<0) {
/* lock failed -> sfsd is already running) */
close(lockf);
return(1);
}
/* ignore exit status from child process */
signal(SIGCHLD,SIG_IGN);
/* spawn subprocess */
pid=fork();
/* error? */
if (pid<0) return(-1);
/* child process has no lock any more - this is STUPID! */
/* the parent process has to wait and check if the child relocks */
if (pid) {
close(lockf);
sleep(1);
if ((lockf=open(lockfn,O_WRONLY,S_IRUSR|S_IWUSR))<0 ||
tlock_file(lockf)!=1)
return(-1);
else
return(0);
}
/* this is the spool daemon child process */
/* relock */
unlink(lockfn);
if ((lockf=open(lockfn,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))<0) {
snprintf(MAXS(tmp),"cannot open %s : %s",lockfn,strerror(errno));
dbgout(tmp);
exit(1);
}
if (wlock_file(lockf)<0) {
snprintf(MAXS(tmp),"cannot lock %s : %s",lockfn,strerror(errno));
dbgout(tmp);
exit(1);
}
snprintf(MAXS(tmp),"%d\n",getpid());
write(lockf,tmp,strlen(tmp));
/* disconnect from client */
setsid();
}
/* set process name (argv[0]) */
/* does not work on all plattforms
if ((int n=strlen(prg))>5) {
for (int i=0;i<n;i++) prg[i]=0;
strcpy(prg,"sfsd");
}
*/
/* process outgoing queue */
for (;;) {
dbgout("sfsd-loop");
/* scan outgoing spool */
hls=scanoutspool("");
if (!hls) {
if (queue==1) message(prg,'F',"outgoing spool is not accessible");
dbgout("outgoing spool is not accessible");
exit(1);
}
/* loop over all destination hosts */
for (hlp=hls; hlp && hlp->flist; hlp=hlp->next) {
/* ignore exit status from child process */
signal(SIGCHLD,SIG_IGN);
pid=-1;
/* fork an extra spool daemon if parallel option is set */
if (parallel) pid=fork();
if (!parallel || pid==0) {
/* initiate the connection to the server host */
sockfd=saftserver_connect(hlp->host,errmsg);
if (sockfd<0) {
if (*errmsg) dbgout(errmsg);
if (parallel) exit(0);
continue;
}
snprintf(MAXS(tmp),"connected to %s",hlp->host);
if (queue==1) message(prg,'I',tmp);
dbgout(tmp);
*lrecipient=0;
/* process all files for this host */
for (oflp=hlp->flist; oflp; oflp=oflp->next) {
error=0;
*arecipient=0;
/* goto next spool file if this one is not accessible */
if (!(inf=rfopen(oflp->oshfn,"r"))) continue;
/* read the spool header file */
while (fgetl(line,inf)) {
if ((cp=strchr(line,'\n'))) *cp=0;
/* check the TO line */
if (str_beq(line,"TO")) {
if ((cp=strchr(line,'@'))) *cp=0;
/* is this recipient the same as the last one
(without forwarding)? */
strcpy(ahost,hlp->host);
strcpy(arecipient,line+3);
if (str_eq(arecipient,lrecipient)) continue;
/* is there a forward address for this recipient? */
sock_putline(sockfd,line);
error=check_forward(sockfd,arecipient,ahost,errmsg);
/* recipient is not available */
if (error==-1) {
fclose(inf);
bounce_file(oflp->oshfn,errmsg);
break;
}
/* forward address found */
if (error==1) {
/* same host? */
if (str_eq(hlp->host,ahost)) {
snprintf(MAXS(line),"TO %s",arecipient);
sock_putline(sockfd,line);
rs=getreply(sockfd);
if (!str_beq(rs,"200")) {
fclose(inf);
break;
}
error=0;
continue;
}
error=2;
fclose(inf);
/* try to connect to forward address */
if (parallel) if (fork()!=0) break;
sockfd2=saft_connect("file",arecipient,oflp->from,ahost,errmsg);
/* no connection? */
if (sockfd2<0) {
if (parallel) exit(1);
break;
}
snprintf(MAXS(tmp),"connected to %s (forward redirection)",
hlp->host);
if (queue==1) message(prg,'I',tmp);
dbgout(tmp);
/* reread spool header file and send it to the forward address */
if (!(inf=rfopen(oflp->oshfn,"r"))) {
if (parallel) exit(1);
sendcommand(sockfd2,"QUIT",NULL);
shutdown(sockfd2,2);
break;
}
while (fgetl(line,inf)) {
if ((cp=strchr(line,'\n'))) *cp=0;
if (str_beq(line,"TO")) continue; /* TO line has been already sent */
sock_putline(sockfd2,line);
rs=getreply(sockfd2);
if (!str_beq(rs,"200") && !str_beq(rs,"202")) {
error=1;
break;
}
}
fclose(inf);
/* on error bounce back file */
if (error==1) {
sendcommand(sockfd2,"QUIT",NULL);
shutdown(sockfd2,2);
bounce_file(oflp->oshfn,rs+4);
if (parallel) exit(1);
break;
}
/* send the spool data file */
error=send_spooldata(sockfd2,oflp->oshfn,oflp->from,
oflp->to,hlp->host,oflp->fname,
mtp,queue==1);
sendcommand(sockfd2,"QUIT",NULL);
shutdown(sockfd2,2);
/* send mail on successful delivery */
if (!error) mail_report(ahost);
/* return to normal spool processing */
if (parallel) exit(error);
if (!error) error=2; /* forward address found and delivered */
break;
}
/* last recipient with no forward address */
strcpy(lrecipient,arecipient);
} else {
/* send header line (this is not the TO line!) */
sock_putline(sockfd,line);
rs=getreply(sockfd);
if (!str_beq(rs,"200") && !str_beq(rs,"202")) {
error=1;
break;
}
}
}
fclose(inf);
/* on error bounce back file */
if (error==1) bounce_file(oflp->oshfn,rs+4);
/* send the spool data file if there are no errors so far */
if (!error) send_spooldata(sockfd,oflp->oshfn,oflp->from,
oflp->to,hlp->host,oflp->fname,
mtp,queue==1);
} /* all files are now sent for this destination */
sendcommand(sockfd,"QUIT",NULL);
shutdown(sockfd,2);
mail_report(hlp->host);
if (pid==0) exit(0);
}
}
/* check for expired files */
check_outspool(bounce);
/* started with option -q ? */
if (queue==1) exit(0);
sleep(retry*60);
#ifdef deadcode
/* handle child process termination and wait for next queue run */
t0=time(0);
while ((ssec=t0-time(0)+retry*60)>0) {
sigchld();
signal(SIGCHLD,sigchld);
snprintf(MAXS(tmp),"sleep %d s",ssec);
dbgout(tmp);
sleep(ssec);
}
#endif
}
}
/*
* send_spooldata - send a file from the outgoing spool
*
* INPUT: sockfd - socket file descriptor
* oshfn - outgoing spool header file
* from - sender name
* to - recipient name
* host - server host
* fname - original file name
* mtp - maximum thruput
* queue - process interactivly
*
* RETURN: 0 if ok, -1 on failure
*/
int send_spooldata(int sockfd, char *oshfn, char *from, char *to,
char *host, char *fname, float mtp, int queue) {
int status; /* return status from send_data */
char
osdfn[MAXLEN], /* outgoing spool data file */
isoname[MAXLEN], /* filename in ISO Latin-1 */
tmp[MAXLEN], /* tmp string */
line[MAXLEN]; /* one text line */
struct stat finfo; /* information about a file */
FILE *mailf; /* mail file */
time_t timetick; /* unix time (in seconds) */
/* status report */
if (queue) {
utf2iso(0,isoname,NULL,NULL,fname);
snprintf(MAXS(tmp),"sending %s to %s",isoname,to);
message(prg,'I',tmp);
}
snprintf(MAXS(tmp),"sending %s to %s@%s",fname,to,host);
dbgout(tmp);
strcpy(osdfn,oshfn);
osdfn[strlen(osdfn)-1]='d';
if (stat(osdfn,&finfo)<0) {
snprintf(MAXS(tmp),"cannot access %s",osdfn);
dbgout(tmp);
return(-1);
}
/* send file data and check return status */
status=send_data(sockfd,finfo.st_size,osdfn,"",osdfn,"",mtp,NULL);
/* error? */
if (status<0) return(status);
/* delete this file from outgoing spool */
unlink(oshfn);
unlink(osdfn);
/* write status log */
mkdir(SPOOL"/LOG",S_IRUSR|S_IWUSR|S_IXUSR);
snprintf(MAXS(tmp),SPOOL"/LOG/%s:%s",from,host);
if ((mailf=rfopen(tmp,"a"))) {
chmod(tmp,S_IRUSR|S_IWUSR);
snprintf(MAXS(tmp),"'%s' to %s",fname,to);
utf2iso(1,line,NULL,NULL,tmp);
timetick=time(0);
strftime(tmp,21,DATEFORMAT,localtime(&timetick));
fprintf(mailf,"sending %s@%s at %s : ",line,host,tmp);
if (status)
fprintf(mailf,"already transmitted\n");
else
fprintf(mailf,"ok\n");
fclose(mailf);
}
return(0);
}
/*
* saftserver_connect - look for correct SAFT server and open connection
*
* INPUT: host - host to connect
*
* OUTPUT: host - connected host (may be different because of forward)
* error - error string
*
* RETURN: socket file descriptor; -1 on error
*/
int saftserver_connect(char *host, char *error) {
int
port, /* tcp port to connect to */
status, /* status code */
sockfd, /* socket file descriptor */
hopcount; /* count for forwarding addresses */
char
*colon,
tmp[MAXLEN], /* temporary string */
server[MAXLEN], /* generic saft server */
line[MAXLEN]; /* one line of text */
port=SAFT;
hopcount=0;
*error=0;
if ((colon=strrchr(host,':'))) {
*colon=0;
port=atoi(colon+1);
}
/* try to connect to the recipient's server */
for (hopcount=1; hopcount<11; hopcount++) {
/* tell where to send to */
snprintf(MAXS(tmp),"opening connection to %s:%d",host,port);
dbgout(tmp);
/* initiate the connection to the server */
sockfd=open_connection(host,port);
if (sockfd==-3 && port==SAFT) {
snprintf(MAXS(server),"saft.%s",host);
snprintf(MAXS(tmp),"opening connection to %s:%d",server,SAFT);
dbgout(tmp);
sockfd=open_connection(server,SAFT);
switch (sockfd) {
case -1: strcpy(error,"cannot create a network socket");
case -2: snprintf(error,MAXLEN-1,"cannot open connection to %s",server);
case -3: snprintf(error,MAXLEN-1,"%s has no internet-address",server);
}
} else {
switch (sockfd) {
case -1: strcpy(error,"cannot create a network socket");
case -2: snprintf(error,MAXLEN-1,"cannot open connection to %s",host);
case -3: snprintf(error,MAXLEN-1,"%s has no internet-address",host);
}
}
if (sockfd<0) {
dbgout(error);
if (port!=SAFT) {
if (colon) *colon=':';
return(-1);
}
}
/* no remote server or protocol error? */
sock_getline(sockfd,line);
if (!str_beq(line,"220 ") || !strstr(line,"SAFT")) {
snprintf(error,MAXLEN-1,"No SAFT server on port %d at %s",port,host);
dbgout(error);
if (colon) *colon=':';
return(-1);
}
/* is there a forward set? */
sock_putline(sockfd,"FILE test");
status=check_forward(sockfd,tmp,host,error);
if (status==-1) return(-1);
if (status==1) {
snprintf(MAXS(tmp),"forward points to %s",host);
dbgout(tmp);
colon=NULL;
port=487;
continue;
}
/* connection is successfull */
snprintf(MAXS(tmp),"connected to %s:%d",host,port);
*error=0;
dbgout(tmp);
return(sockfd);
}
snprintf(error,MAXLEN-1,"maximum hopcount reached (forward loop?)");
dbgout(error);
return(-1);
}
/*
* mail_report - send a status report via mail to the local user
*
* INPUT: host - recipient host name
*
* RETURN: 0 if ok; -1 in failure
*/
int mail_report(const char *host) {
char
*cp,
mailfn[FLEN], /* status log mail file name */
line[MAXLEN], /* incoming command line */
sender[MAXLEN], /* sender name */
tmp[MAXLEN]; /* tmp string */
FILE
*mailf, /* mail file */
*pp; /* output pipe */
#ifdef NEXT
FILE *dp; /* directory pipe */
#else
struct dirent *dire; /* directory entry */
DIR *dp; /* directory pointer */
#endif
/* look for status log files to send via mail */
if (chdir(SPOOL"/LOG")<0) return(-1);
#ifdef NEXT
/* stupid NeXT has a broken readdir(); this is a dirty workaround */
/* open LOG dir */
snprintf(MAXS(cmd),"ls *:%s 2>/dev/null",host);
if ((dp=popen(cmd,"r")) == NULL) {
exit(0);
if (parallel) continue;
}
/* scan through LOG directory */
while (fgetl(mailfn,dp)) {
if ((cp=strrchr(mailfn,'\n'))) *cp=0;
#ifdef JEDPARSE
}
#endif
#else
/* open LOG dir */
dp=opendir(".");
/* scan through LOG directory */
while ((dire=readdir(dp))) {
strcpy(mailfn,dire->d_name);
snprintf(MAXS(tmp),"*:%s",host);
if (simplematch(mailfn,tmp,0)<1) continue;
#endif
mailf=rfopen(mailfn,"r");
if (!mailf) continue;
strcpy(sender,mailfn);
cp=strchr(sender,':');
*cp=0;
/* open pipe to sendmail */
pp=popen(SENDMAIL" -t","w");
if (!pp) continue;
/* fill out mail message */
fprintf(pp,"From: root\n");
fprintf(pp,"To: %s\n",sender);
fprintf(pp,"Subject: sendfile spool daemon status report for %s\n\n",
host);
while (fgetl(line,mailf)) fprintf(pp,"%s",line);
fprintf(pp,"\n.\n");
pclose(pp);
fclose(mailf);
unlink(mailfn);
}
#ifdef NEXT
pclose(dp);
#else
closedir(dp);
#endif
return(0);
}
/*
* sigchld - simple interrupt handler for SIGCHLD
*/
/* replaced by: signal(SIGCHLD,SIG_IGN);
void sigchld() {
#ifdef NEXT
int status;
wait(&status);
#else
waitpid(-1,NULL,WNOHANG);
#endif
return;
}
*/
/*
* check_outspool - check outgoing spool if there are expired files and
* bounce them to the sender's incoming spool
*
* INPUT: bounce - number of days after file is expired
*/
void check_outspool(int bounce) {
char
tmp[MAXLEN], /* temporary string */
oshfn[FLEN]; /* outgoing spool header file name */
time_t timetick; /* unix time (in seconds) */
struct stat finfo; /* information about a file */
#ifdef NEXT
FILE *pp; /* pipe */
#else
struct dirent *dire; /* directory entry */
DIR *dp; /* directory pointer */
#endif
timetick=time(0);
if (chdir(OUTGOING)<0) return;
/* mega stupid NeXT has broken readdir() */
#ifdef NEXT
/* open spool dir */
if ((pp=popen("ls *.h 2>/dev/null","r")) == NULL) return;
/* scan through spool directory */
while (fgetl(oshfn,pp)) {
if ((cp=strrchr(oshfn,'\n'))) *cp=0;
#ifdef JEDPARSE
}
#endif
#else
/* open spool dir */
if (!(dp=opendir("."))) return;
/* scan through outgoing spool directory */
while ((dire=readdir(dp))) {
/* ignore non-header files */
snprintf(MAXS(oshfn),"%s",dire->d_name);
if (!str_eq(&oshfn[strlen(oshfn)-2],".h")) continue;
#endif
/* hier noch hinzufuegen: ungueltige Files nach outgoing/BAD schieben */
if (stat(oshfn,&finfo)<0) continue;
/* spool time expired? */
if (timetick>finfo.st_mtime+bounce*DAYSEC) {
snprintf(MAXS(tmp),"no connection within %d days",bounce);
bounce_file(oshfn,tmp);
}
}
#ifdef NEXT
pclose(pp);
#else
closedir(dp);
#endif
}
/*
* bounce_file - bounce back a file from outgoing spool to the
* sender's incoming spool
*
* INPUT: file - outgoing spool header file name
* comment - comment to add
*
* RETURN: 0 on success, -1 on failure
*/
int bounce_file(char *file, char *comment) {
int
id; /* spool file id */
char
*cp, /* simple character pointer */
*arg, /* the argument(s) of the header line */
cwd[MAXLEN], /* current working directory */
hline[MAXLEN], /* header line */
tmp[MAXLEN], /* temporary string */
shfn[MAXLEN], /* spool header file name */
sdfn[MAXLEN], /* spool data file name */
oshfn[MAXLEN], /* outgoing spool header file name */
osdfn[MAXLEN]; /* outgoing spool data file name */
FILE
*inf, /* input file */
*outf; /* output file */
struct stat finfo; /* information about a file */
struct passwd *pwe; /* password entry struct */
struct utimbuf utb; /* binary time */
if ((cp=strrchr(file,'/')))
cp++;
else
cp=file;
snprintf(MAXS(oshfn),OUTGOING"/%s",cp);
snprintf(MAXS(osdfn),OUTGOING"/%s",cp);
osdfn[strlen(osdfn)-1]='d';
if (stat(oshfn,&finfo)<0) return(-1);
if (!getcwd(MAXS(cwd))) return(-1);
/* get user name and spool directory */
if (!(pwe=getpwuid(finfo.st_uid))) return(-1);
snprintf(MAXS(userspool),SPOOL"/%s",pwe->pw_name);
/* create user spool directory if necessary */
if (mkdir(userspool,S_IRUSR|S_IWUSR|S_IXUSR)==0)
chown(userspool,pwe->pw_uid,pwe->pw_gid);
/* change uid and get user name */
if (setegid(finfo.st_gid)<0) exit(1);
if (seteuid(finfo.st_uid)<0) exit(1);
if (!(inf=rfopen(oshfn,"r")) || chdir(userspool)<0) {
fclose(inf);
seteuid(0);
setegid(0);
chdir(cwd);
return(-1);
}
/* get next spool id */
id=spoolid(MAXLEN);
if (id<=0 || id>MAXLEN) {
fclose(inf);
seteuid(0);
setegid(0);
chdir(cwd);
return(-1);
}
/* set file names */
snprintf(MAXS(shfn),"%d.h",id);
snprintf(MAXS(sdfn),"%d.d",id);
if (!(outf=rfopen(shfn,"w"))) {
fclose(inf);
seteuid(0);
setegid(0);
chdir(cwd);
return(-1);
}
/* copy header file */
while (fgetl(hline,inf)) {
/* prepare the header line */
if ((cp=strchr(hline,'\n'))) *cp=0;
cp=strchr(hline,'\t');
if (!cp) continue;
arg=cp+1;
*cp=0;
/* ignore old COMMENT */
if (str_eq(hline,"COMMENT")) continue;
/* add new bounce COMMENT */
if (str_eq(hline,"TO")) {
snprintf(MAXS(tmp),"cannot sent to %s : %s",arg,comment);
iso2utf(arg,tmp);
fprintf(outf,"COMMENT\t%s\n",arg);
continue;
}
/* add other header lines */
fprintf(outf,"%s\t%s\n",hline,arg);
}
fclose(inf);
fclose(outf);
/* copy spool data file */
if (fcopy(osdfn,sdfn,S_IRUSR|S_IWUSR)<0) {
/* on failure delete new spool file */
unlink(shfn);
unlink(sdfn);
} else {
/* on success delete outgoing spool file */
unlink(oshfn);
unlink(osdfn);
}
/* set old time stamps */
utb.actime=utb.modtime=finfo.st_mtime;
utime(shfn,&utb);
utime(sdfn,&utb);
seteuid(0);
setegid(0);
chdir(cwd);
return(0);
}
/*
* check_userspool - check if user is allowed to use sendfile and create the
* user spool directories
*
* INPUT: user - user login name
*
* RETURN: 0 if ok, -1 if failed
*/
int check_userspool(char *user, int userconfighome) {
int allowfound; /* flag if the allow file is not empty */
char
*cp, /* simple character pointer */
tmp[MAXLEN], /* temporary string */
luser[MAXLEN]; /* check local user */
FILE
#ifdef NEXT
*pp, /* pipe stream */
#endif
*inf; /* for various files */
struct passwd *pwe; /* password entry struct */
struct stat finfo; /* information about a file */
allowfound=0;
if (test) return(-3);
/* look in the sendfile allow file */
if ((inf=rfopen(ALLOW,"r"))) {
while (fgetl(luser,inf)) {
if ((cp=strchr(luser,'#'))) *cp=0;
str_trim(luser);
if (*luser) {
allowfound=1;
if (str_eq(luser,user)) break;
}
}
fclose(inf);
/* is the user not in the allow list? */
if (allowfound && !str_eq(luser,user)) {
reply(521);
*user=0;
return(-1);
}
}
/* look in the sendfile disallow file if the allow file is empty */
if (!allowfound && (inf=rfopen(DENY,"r"))) {
while (fgetl(luser,inf)) {
if ((cp=strchr(luser,'#'))) *cp=0;
str_trim(luser);
/* is the user in the deny list? */
if (str_eq(luser,user)) {
reply(521);
*user=0;
return(-1);
}
}
fclose(inf);
}
if (test) {
ruid=rgid=auth=0;
strcpy(userspool,".");
*userconfig=0;
return(0);
}
/* get user information */
pwe=NULL;
#ifdef NEXT
/* stupid NeXT has a broken getpwnam(); this is a dirty workaround */
snprintf(MAXS(tmp),"( nidump passwd . ; nidump passwd / ) | "
"awk -F: '$1==\"%s\"{print $3,$4;exit}'",user);
pp=popen(tmp,"r");
if (fgetl(tmp,pp) && *tmp!='\n' && *tmp!=0) {
pwe=(struct passwd *) malloc(sizeof(struct passwd));
sscanf(tmp,"%d %d",&ruid,&rgid);
pwe->pw_uid=ruid;
pwe->pw_gid=rgid;
}
pclose(pp);
#else
pwe=getpwnam(user);
#endif
/* no such user? */
if (pwe==NULL) {
reply(520);
*user=0;
return(-1);
}
/* going to change ruid/rgid, so reset auth flag */
auth=0;
ruid=pwe->pw_uid;
rgid=pwe->pw_gid;
/* build user spool string */
user[32]=0;
snprintf(MAXS(userspool),SPOOL"/%s",user);
snprintf(MAXS(userconfig),"%s/config",userspool);
/* create user spool directory for user */
if (mkdir(userspool,S_IRUSR|S_IWUSR|S_IXUSR)==0) chown(userspool,ruid,rgid);
setreugid();
/* create user config directory in $HOME? */
if (stat(userconfig,&finfo)<0) {
if (userconfighome) {
snprintf(tmp,MAXLEN-8,"%s/.sendfile",pwe->pw_dir);
mkdir(tmp,S_IRUSR|S_IWUSR|S_IXUSR);
symlink(tmp,userconfig);
strcat(tmp,"/spool");
symlink(userspool,tmp);
} else
mkdir(userconfig,S_IRUSR|S_IWUSR|S_IXUSR);
}
seteuid(0);
setegid(0);
return(0);
}
/*
* copy2pipe - copy a file to a pipe descriptor
*
* INPUT: from - input file name
*
* OUTPUT: pfd - pipe descriptor
*
* RETURN: 0 on success, -1 on failure
*/
int copy2pipe(const char *from, int pfd) {
int fdin; /* input file descriptor */
int bytes; /* read bytes */
int status; /* error status */
unsigned long blksize;/* file system block size */
char *buf; /* copy buffer */
struct stat finfo; /* information about a file */
status=0;
/* open source file */
fdin=open(from,O_RDONLY,0);
if (fdin<0) return(-1);
/* get file system block size for copy operation */
stat(from,&finfo);
#ifdef HAVE_ST_BLKSIZE
blksize=finfo.st_blksize;
#else
blksize=1024;
#endif
/* ANSI C can not dynamicly allocate with buf[blksize] */
buf=(char *)malloc(blksize);
if (!buf) return(-1);
/* read until EOF */
while ((bytes=read(fdin,buf,blksize)) > 0) {
/* write to destination file */
if (write(pfd,buf,bytes)<0) {
/* write error */
status=-1;
break;
}
}
close(fdin);
free(buf);
return(status);
}
/*
* openlog - open user log file
*
* INPUT: log - name of log file
*
* RETURN: log file descriptor
*
* Be sure to call this function with correct seteuid/setegid settings!
*/
int openlog(const char *log) {
int
n, /* simple loop counter */
lfd; /* log file descriptor */
char tmp[MAXLEN]; /* temporary string */
struct stat finfo; /* information about a file */
/* security check */
if (rgid!=getegid()) { if (setegid(rgid)<0) return(0); }
if (ruid!=geteuid()) { if (seteuid(ruid)<0) return(0); }
/* create logfile if not there */
close(open(log,O_CREAT|O_EXCL,S_IRUSR|S_IWUSR));
/* try several times to lock-write the logfile */
lfd=open(log,O_WRONLY|O_APPEND);
for (n=1; n<5; n++) {
if (wlock_file(lfd) >= 0) {
/* add header to empty log file */
stat(log,&finfo);
if (finfo.st_size==0) {
strcpy(tmp,"# use utf7decode to view this file\n\n");
write(lfd,tmp,strlen(tmp));
}
}
return(lfd);
}
return(0);
}
/*
* send_file - send a file to stdout
*
* INPUT: fd - file descriptor
*
* RETURN: number of transfered bytes, -1 if transfer failed
*/
int send_file(int fd) {
int bytes; /* number of read bytes */
unsigned long tbytes; /* total number of bytes which has been sent */
char packet[OVERSIZE]; /* data packet to send */
/* fallback */
if (packet_size<1) packet_size=512;
/* send the file data in packets */
bytes=0;
tbytes=0;
while ((bytes=read(fd,packet,packet_size))>0) {
alarm(timeout);
write(fileno(stdout),packet,bytes);
tbytes+=bytes;
}
if (bytes<0)
return(bytes);
else
return(tbytes);
}
/*
* dbgout - write debug output
*
* INPUT: line - one line of text
*/
void dbgout(const char *line) {
if (dbf) {
fprintf(dbf,"(%d) >%s<\n",(int)getpid(),line);
fflush(dbf);
}
}
/*
* cleanexit - clean termination routine
*
* A very simple interrupt handler
*/
void cleanexit() {
/* ignore all relevant signals */
signal(SIGTERM,SIG_IGN);
signal(SIGABRT,SIG_IGN);
signal(SIGQUIT,SIG_IGN);
signal(SIGHUP,SIG_IGN);
signal(SIGINT,SIG_IGN);
cleanup();
exit(0);
}
/*
* timeoutexit - clean up after timeout
*/
void timeoutexit() {
reply(429);
cleanexit();
}
/*
* cleanup - delete tmp files
*/
void cleanup() {
#ifndef DEBUG
if (*chfile) unlink(chfile);
if (*csfile) unlink(csfile);
#endif
}
/*
* setreugid - set effective uid and gid for recipient
*
* RETURN: nothing, but terminates program on error
*/
void setreugid() {
if (rgid != getegid())
if (rgid && setegid(rgid)<0) {
printf("490 Internal error on setegid(%u): %s\r\n",
(unsigned int)rgid,strerror(errno));
exit(1);
}
if (ruid != geteuid())
if (ruid && seteuid(ruid)<0) {
printf("490 Internal error on seteuid(%u): %s\r\n",
(unsigned int)ruid,strerror(errno));
exit(1);
}
}
/*
* sudo - emulate the shell su command:
* execute a command in an async subprocess owned by another user
*
* INPUT: user - login name
* cmd - shell command to execute
*
* RETURN: 0 on success, <0 on failure
*/
int sudo(const char *user, const char *cmd) {
pid_t pid;
char tmp[MAXLEN]; /* the command itself */
struct passwd *pwe; /* password entry struct */
/* get user name */
if (!(pwe=getpwnam(user))) return(-3);
/* security check: disallow superuser/group */
if (pwe->pw_uid==0 || pwe->pw_gid==0) return(-2);
/* spawn subprocess */
signal(SIGCHLD,SIG_IGN);
pid=fork();
if (pid<0) return(-1);
if (pid) {sleep(1); return(0);}
/* change user */
/* note: setuid does not work if euid>0 ! STUPID! */
chdir(pwe->pw_dir);
seteuid(0);
setegid(0);
if (setgid(pwe->pw_gid)<0) exit(1);
initgroups(user,pwe->pw_gid);
if (setuid(pwe->pw_uid)<0) exit(1);
/* set some usefull environment variables */
snprintf(MAXS(tmp),"HOME=%s",pwe->pw_dir);
putenv(tmp);
snprintf(MAXS(tmp),"SHELL=%s",pwe->pw_shell);
putenv(tmp);
snprintf(MAXS(tmp),"USER=%s",user);
putenv(tmp);
snprintf(MAXS(tmp),"LOGNAME=%s",user);
putenv(tmp);
putenv("TERM=");
/* call external program */
dbgout(cmd);
system(cmd);
exit(0);
return(0);
}
/*
* usage - print short help usage text
*/
int usage() {
fprintf(stderr,"\n");
fprintf(stderr,"usage: %s [OPTIONS] (normally %s is called by inetd!)\n",prg,prg);
fprintf(stderr,"options: -d debug mode on, output goes to %s\n",DBF);
fprintf(stderr," -f print free space in MB on spool partition, then exit\n");
fprintf(stderr," -q process outgoing spool queue one time\n");
fprintf(stderr," -Q run as outgoing spool daemon\n");
fprintf(stderr," -c file alternate configuration file\n");
fprintf(stderr,"see also: sfdconf\n");
return(2);
}
syntax highlighted by Code2HTML, v. 0.9.1