This is zend.c in view mode; [Download] [Up]
static char rcsid[] = "$Id: zend.c,v 1.14 1993/02/21 13:05:20 gerben Exp $"; /* Zend --- a mail transport program for large files and directories Copyright (C) 1992 Gerben C. Th. Wierda This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Gerben Wierda Goudriaanstraat 1 1222 SG Hilversum The Netherlands gerben@rna.indiv.nluug.nl */ /* * * zend.c -- main module and data for zend * */ #include "zend.h" #include <signal.h> #define ZEND_ID "version 1.0" Variable global[] = { /* * Mail header strings: */ { STRING, MAILFROMPROMPT}, { STRING, MAILTOPROMPT}, { STRING, MAILSUBJECTPROMPT}, { STRING, MAILDATEPROMPT}, { STRING, MAILREPLYTOPROMPT}, /* * Zend prompts */ { STRING, ZENDIDPROMPT}, { STRING, SYSTEMNAMEPROMPT}, { BOOLEAN, UUCPMODEPROMPT}, { STRING, SENDINGUSERPROMPT}, { STRING, RECEIVINGUSERPROMPT}, { USHORT, LINESPROMPT}, { ULONG, ZENDSIZEPROMPT}, { ULONG, RECEIVESIZEPROMPT}, { ULONG, ZENDTRUESIZEPROMPT}, { ULONG, RECEIVETRUESIZEPROMPT}, { STRING, ACTIONPROMPT}, { ULONG, SENDINGJOBIDPROMPT}, { ULONG, RECEIVINGJOBIDPROMPT}, { STRING, FILEPROMPT}, { STRING, DIRPROMPT}, { STRING, TYPEPROMPT}, { BOOLEAN, COMPRESSEDPROMPT}, { USHORT, CHUNKNRPROMPT}, { USHORT, NROFCHUNKSPROMPT}, { STRING, REASONPROMPT}, { ULONG, DUMMYULONGPROMPT}, { STRING, ROLEPROMPT}, { STRING, SPOOLDIRPROMPT}, /* Stuff for timeouts. */ { USHORT, PACKETTIMEOUTPROMPT }, { USHORT, JOBTIMEOUTPROMPT }, { ULONG, TIMESTAMPPROMPT }, { STRING, SENDINGSYSTEMPROMPT }, /* Access restrictions. */ { STRING, ALLOWUSERSPROMPT }, { STRING, EXCLUDEUSERSPROMPT }, { STRING, ALLOWSYSTEMSPROMPT }, { STRING, EXCLUDESYSTEMSPROMPT }, { NONE, NIL} /* sentinel */ }; Variable remoteglobal[sizeof( global) / sizeof( global[0])]; /* extern */ int nrOfGlobalVariables = sizeof( global) / sizeof( global[0]) -1; /* extern */ StringPointer mailUser = NIL; /* extern */ Boolean verboseMode = FALSE; /* extern */ uid_t sendinguser_uid = 0; /* extern */ unsigned int process_id; #if DEBUG /* extern */ Boolean dump = FALSE; #endif /* extern */ Boolean checkTimeoutMode = FALSE; /* extern */ Boolean receiveMode = FALSE; /* extern */ Boolean cleanupMode = FALSE; /* extern */ time_t timestamp; /* extern */ Boolean logErrors = FALSE; #if _ANSI_DEFUN_ extern void init( void) #else extern void init() #endif { memcpy( remoteglobal, global, sizeof( global)); } static void #if _ANSI_DEFUN_ usage( void) #else usage() #endif { fprintf( stderr, "\ Usage: zend user[@remotehost] file|dir [ user[@remotehost] file|dir ... ]\n"); } /* signal handling */ #define SIGNALS_TO_HANDLE SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGABRT /* * Set a bunch of signals to `handler'. The bunch of signals is given by * the variable argument list, which should be terminated with zero. */ static void #if _ANSI_DEFUN_ set_signals( void (*handler)(int), ...) #else set_signals( handler, va_alist) void (*handler)(); va_dcl #endif { va_list argpt; int sig; va_begin( argpt, sig); while (sig = va_arg( argpt, int)) { /* protect background processes */ if (signal( sig, SIG_IGN) != SIG_IGN && handler != SIG_IGN) { signal( sig, handler); } } } static void #if _ANSI_DEFUN_ interrupt( int sig) #else interrupt( sig) int sig; #endif { signal( sig, SIG_IGN); zerr( "Aborted.\n"); file_cleanup(); signal( sig, SIG_DFL); exit( -3); } #if _ANSI_DEFUN_ void main( int argc, char *argv[]) #else void main( argc, argv) int argc; char *argv[]; #endif { int c; int status = 0; char *progName; time(×tamp); #if _ANSI_LIBRARY_ && _IOLBF setvbuf( stdout, NULL, _IOLBF, BUFSIZ); setvbuf( stderr, NULL, _IOLBF, BUFSIZ); #endif process_id = getpid(); sendinguser_uid = getuid(); #if 0 /* Obsolete. (since 1.12) */ if (setuid( 0) != 0) { /* {NOTE: this assumes that privileged user (root) has uid 0, which is not necessarily true on non-UNIX Posix systems} */ zerr( "ZEND should be installed setuid root.\n"); exit(-2); } /* At this point we're running with root as both real and effective uid. */ #endif /* * Security: check that effective user owns the spool directory, and * can access it. */ { struct stat statBuf; if (stat( HOMEDIR, &statBuf) != 0) { zerr( "cannot stat spool directory %s (%s).\n", HOMEDIR, strerror( errno)); exit(-2); } if (!S_ISDIR( statBuf.st_mode)) { zerr( "%s is not a directory.\n", HOMEDIR); exit(-2); } if (statBuf.st_uid != geteuid()) { struct passwd *pwStruct = getpwuid( statBuf.st_uid); if (pwStruct) { zerr( "ZEND should be setuid %s!\n", pwStruct->pw_name); } else { zerr( "ZEND should be setuid %d!\n", statBuf.st_uid); } exit(-2); } if ((statBuf.st_mode & S_IRWXU) != S_IRWXU) /* {Don't use access() since that checks rights of REAL user.} */ { zerr("cannot access spool directory %s\n", HOMEDIR); exit( -2); } } /* * At this point we're still running with the original real and * effective uids, that is, `sendinguser_uid' as real uid and zend's * setuid (not necessarily root) as effective uid. This is changed * only when we do work on behalf of the user, in subprocesses * created by system_as() or popen_as(). (Previous versions of * zend used to set both real and effective uid to root, which * required to obtain `sendinguser_uid' beforehand; now we could have * used getuid() directly instead.) */ /* Security: normalize umask. */ umask( S_IWGRP|S_IWOTH); /* Security: blank out environment, to avoid lies to subprograms. */ { #ifndef environ extern char **environ; #endif if (environ) { environ[0] = NULL; } else { static char *dummy_env[1] = { NULL }; environ = dummy_env; } } init(); /* * Let program's invokation name determine the action. */ assert(argc > 0 && argv != NIL); progName = basename( argv[0]); if (strcmp( "zend-R" , progName) == 0) { receiveMode = TRUE; } else if (strcmp( "zend-T", progName) == 0) { checkTimeoutMode = TRUE; } while ((c = getopt( argc, argv, "vRTC:")) != -1) { switch (c) { case 'v': verboseMode = TRUE; break; /* * Alternatively, determine the action from command-line options * if you object to the above method. */ case 'R': receiveMode = TRUE; break; case 'T': checkTimeoutMode = TRUE; break; case 'C': if (strcmp( optarg, "LEANUP") == 0) { cleanupMode = TRUE; break; } /* FALL THROUGH */ case '?': usage(); exit( -1); } } if (read_config() != 0) { exit( -2); } /* * Check proper installation: non-root setuid zend cannot deliver in * user's home directory. */ if (geteuid() != ROOT && get_global_string( global, SPOOLDIRPROMPT) == NIL) { zerr("config error: %s should be non-empty for non-root setuid zend.\n", SPOOLDIRPROMPT); exit( -2); } set_global( global, ZENDIDPROMPT, ZEND_ID); if (receiveMode) { logErrors = TRUE; #if 0 /* Obsolete. */ freopen( "/dev/console", "w", stderr); setbuf( stderr, NIL); #endif #if DEBUG zerr( "%s started as receiver.\n", ZEND_ID); #endif receive(); } else if (checkTimeoutMode) { logErrors = TRUE; #if 0 /* Obsolete. */ freopen( "/dev/console", "w", stderr); setbuf( stderr, NIL); #endif #if DEBUG zerr( "%s started to check timeout.\n", ZEND_ID); #endif check_timeout(); } else if (cleanupMode) { /* This should be called only from /etc/rc. (which implies ruid root) */ if (sendinguser_uid != 0) { usage(); exit(-1); } cleanup_stale_locks(); } else if (optind == argc || ((argc - optind) % 2) != 0) { usage(); exit(-1); } else { #if DEBUG zerr( "%s started as sender.\n", ZEND_ID); #endif set_signals( interrupt, SIGNALS_TO_HANDLE, 0); while (optind<argc) { char *remoteUser, *localFile; remoteUser = argv[optind++]; localFile = argv[optind++]; /* (already checked for correct number of parameters above.) */ if (!starttransfer( remoteUser, localFile)) { status++; } } } exit( status); } /* Remove partially created job. */ static void #if _ANSI_DEFUN_ abort_job( Ulong jobId) #else abort_job( jobId) #endif { char buf [PATHBUFSIZ]; sprintf( buf, "%s/F.%lu", HOMEDIR, jobId); unlink( buf); sprintf( buf, "%s/D.%lu", HOMEDIR, jobId); rmdir( buf); } /* Spool output of `command' (run as sending user) to `spoolfile'. */ static Boolean #if _ANSI_DEFUN_ spool( const char *command, const char *spoolfile) #else spool( command, spoolfile) const char *command; const char *spoolfile; #endif { FILE *cmdOutput; int spoolFd, st; char buf[BUFSIZ]; size_t n; Boolean status = TRUE; unlink( spoolfile); /* so creat read-only will not fail if it exists. */ if ((spoolFd = creat( spoolfile, S_IRUSR)) < 0) { #if DEBUG zerr( "spool: cannot create %s. (%s)\n", spoolfile, strerror( errno)); #endif return FALSE; } if ((cmdOutput = popen_as_user( command, "r")) == NULL) { close( spoolFd); return FALSE; } while ((n = fread( buf, sizeof( char), sizeof( buf), cmdOutput)) != 0) { ssize_t written; if ((written = write( spoolFd, buf, n)) != n) { #if DEBUG zerr( "spool: write failed. (%s)\n", (written < 0) ? strerror( errno) : "Full disk?"); #endif status = FALSE; break; } } if ((st = pclose( cmdOutput)) != 0) { #if DEBUG zerr( "spool: pclose( %s) failed. (%s)\n", command, strerror( errno)); #endif status = FALSE; } if (close( spoolFd) != 0) { #if DEBUG zerr( "spool: close( %s) failed. (%s)\n", spoolfile, strerror( errno)); #endif status = FALSE; } return status; } #if _ANSI_DEFUN_ extern Boolean starttransfer( const char *remoteUser, const char *localFile) #else extern Boolean starttransfer( remoteUser, localFile) char *remoteUser; char *localFile; #endif /* return TRUE when successfully spooled. */ { char buf[PATHBUFSIZ], spoolfile[PATHBUFSIZ]; char *sep; char *qDir, *qFile; Ulong jobId, trueSize; Boolean isFile; struct passwd *pwStruct; struct stat statBuf; Ulong maxZendTrueSize = get_global_ulong( global, ZENDTRUESIZEPROMPT); Ulong maxZendSize = get_global_ulong( global, ZENDSIZEPROMPT); FILE *tmpStream; if (verboseMode) { fprintf( stderr, "%s -> %s\n", localFile, remoteUser); } set_global( global, ROLEPROMPT, ROLE_SENDER); /* * Global var "Action:" is "Are You There?" */ set_global( global, ACTIONPROMPT, ACTION_AYT); /* * find out about local user. Refuse to proceed if we cannot find out. */ if ((pwStruct = getpwuid( sendinguser_uid)) == NULL) { zerr( "who ARE you???\n"); exit( -2); } set_global( global, SENDINGUSERPROMPT, pwStruct->pw_name); /* * Get some statistics from the file, like size and sum. */ if (stat( localFile, &statBuf) != 0) { zerr( "Unable to stat %s\n", localFile); return FALSE; } if (S_ISREG( statBuf.st_mode)) { trueSize = (Ulong)(statBuf.st_size); isFile = TRUE; set_global( global, TYPEPROMPT, TYPE_FILE); } else if (S_ISDIR( statBuf.st_mode)) { sprintf( buf, "%s -s %s", DUPROG, qFile = quote_for_sh( localFile)); free( qFile); if ((tmpStream = popen_as_user( buf, "r")) == NIL) { zerr( "Unable to du %s\n", localFile); return FALSE; } if (fscanf( tmpStream, "%lu", &trueSize) != 1) { pclose( tmpStream); zerr( "Unable to determine size from du %s\n", localFile); return FALSE; } pclose( tmpStream); trueSize *= DUBLOCKSIZE; /* Note that this is just an estimate. */ isFile = FALSE; set_global( global, TYPEPROMPT, TYPE_DIR); } else { zerr( "Unable to zend %s. File must be regular file or directory.\n", localFile); return FALSE; } if (maxZendTrueSize < trueSize) { zerr( "Transfer true size of %s %s too big (%lu, max %lu).\n", (isFile ? "file" : "directory"), localFile, trueSize, maxZendTrueSize); return FALSE; } set_global( global, ZENDTRUESIZEPROMPT, trueSize); /* * Get filename part from localFile and put it into global[] * (mind trailing '/'s) */ strcpy( buf, localFile); while ((sep = strrchr( buf, '/')) != NIL) { *sep = '\0'; /* separate directory- and filename. */ if (sep[1] != '\0') { if (sep == buf) /* refuse to zend root. */ { zerr( "Cannot zend \"/\"!\n"); return FALSE; } break; } } if (sep != NIL) { set_global( global, FILEPROMPT, sep + 1); set_global( global, DIRPROMPT, buf); } else { set_global( global, FILEPROMPT, localFile); set_global( global, DIRPROMPT, "."); } /* * Kludge to catch "." and ".." as filenames: get absolute pathname. */ sep = get_global_string( global, FILEPROMPT); if (strcmp( sep, ".") == 0 || strcmp( sep, "..") == 0) { sprintf( buf, "cd %s && %s", qFile = quote_for_sh( localFile), PWDPROG); free( qFile); if ((tmpStream = popen_as_user( buf, "r")) != NIL) { sep = fgets( buf, sizeof( buf), tmpStream); if (pclose( tmpStream) != 0) { sep = NULL; } } if (tmpStream == NIL || sep == NIL || *sep != '/') { zerr( "Unable to determine absolute pathname %s\n", localFile); return FALSE; } if (*(sep += strlen(sep) - 1) == '\n') { *sep = '\0'; } if (sep == buf + 1) /* refuse to zend root. */ { zerr( "Cannot zend \"/\"!\n"); return FALSE; } *(sep = strrchr( buf, '/')) = '\0'; #if DEBUG zerr( "normalize filename %s -> %s/%s\n", localFile, buf, sep + 1); #endif set_global( global, FILEPROMPT, sep + 1); set_global( global, DIRPROMPT, buf); } /* * separate remote user from remote system */ if ((sep = strchr( strcpy( buf, remoteUser), '@')) != NIL) { *sep = '\0'; /* separate user- and system name */ set_global( global, RECEIVINGUSERPROMPT, buf); set_global( remoteglobal, SYSTEMNAMEPROMPT, sep + 1); } else { set_global( global, RECEIVINGUSERPROMPT, remoteUser); set_global( remoteglobal, SYSTEMNAMEPROMPT, get_global_string( global, SYSTEMNAMEPROMPT)); } if ((jobId = new_job()) == RESERVEDID) { return FALSE; } set_global( global, SENDINGJOBIDPROMPT, jobId); sprintf( spoolfile, "%s/F.%lu", HOMEDIR, jobId); add_file_to_cleanup( spoolfile, FALSE); /* * Try compressed first. If this fails, or the result would be bigger * than the original, retry uncompressed. */ qDir = quote_for_sh( get_global_string( global, DIRPROMPT)); qFile = quote_for_sh( get_global_string( global, FILEPROMPT)); if (isFile) { sprintf( buf, "%s <%s/%s", COMPRESSPROG, qDir, qFile); } else { sprintf( buf, "cd %s && %s cf - %s | %s", qDir, TARPROG, qFile, COMPRESSPROG); } if (!spool( buf, spoolfile) || stat( spoolfile, &statBuf) != 0 || trueSize < (Ulong)statBuf.st_size) { if (isFile) { sprintf( buf, "%s %s/%s", CATPROG, qDir, qFile); } else { sprintf( buf, "cd %s && %s cf - %s", qDir, TARPROG, qFile); } if (!spool( buf, spoolfile)) { free( qDir), free( qFile); zerr( "Unable to copy/tar/compress file/dir %s\n", localFile); abort_job( jobId); return FALSE; } set_global( global, COMPRESSEDPROMPT, FALSE); } else { set_global( global, COMPRESSEDPROMPT, TRUE); } free( qDir), free( qFile); if (stat( spoolfile, &statBuf) != 0) { zerr( "Unable to stat %s\n", spoolfile); abort_job( jobId); return FALSE; } if (maxZendSize < (Ulong)(statBuf.st_size)) { zerr( "Transfer mail size too big.\n"); abort_job( jobId); return FALSE; } set_global( global, ZENDSIZEPROMPT, (Ulong)(statBuf.st_size)); set_global( global, TIMESTAMPPROMPT, (Ulong) timestamp); /* * Write all globals to a job specific config file: */ if (!write_globs_to_jobfile( global, jobId)) { abort_job( jobId); return FALSE; } /* * Send the ayt message: */ mail_message(); zlog( "START"); /* Restore global parameters that we may need the next time (kludge). */ set_global( global, ZENDTRUESIZEPROMPT, maxZendTrueSize); set_global( global, ZENDSIZEPROMPT, maxZendSize); return TRUE; } #if _ANSI_DEFUN_ extern void receive( void) #else extern void receive() #endif { StringPointer action; while (TRUE) { int result; /* * Skip unknown entries and walk until EOF or first delimiter. */ result = parsestreamtoglob( remoteglobal, stdin); if (result == PARSEEOF) { /* * No delimiter and eof. */ zerr( "No delimiter found in mail from %s. Job discarded.\n", get_global_string( remoteglobal, MAILFROMPROMPT)); return; } if (result == PARSEDELIMITER) { break; } } action = get_global_string( remoteglobal, ACTIONPROMPT); /* * For every kind of message there is a handler: */ if (strcmp( ACTION_AYT, action) == 0) { handle_ayt(); } else if (strcmp( ACTION_UAR, action) == 0) { handle_uar(); } else if (strcmp( ACTION_CNK, action) == 0) { handle_cnk(); } else if (strcmp( ACTION_ACK, action) == 0) { handle_ack(); } else if (strcmp( ACTION_RSN, action) == 0) { handle_rsn(); } else if (strcmp( ACTION_ERR, action) == 0) { handle_err(); } } extern void #if _ANSI_DEFUN_ check_timeout( void) #else check_timeout() #endif { char buf[PATHBUFSIZ]; FILE *dirStream; Ulong jobId, jobTimestamp; Ushort timeout; struct stat jobStat; /* * Loop over all jobfiles in Zend's home directory. */ sprintf( buf, "cd %s && %s C.*", HOMEDIR, ECHOPROG); if ((dirStream = popen_as_zend( buf, "r")) == NIL) { zerr( "Unable to execute \"%s\"\n", buf); return; } /* {{Alternatively, use opendir/readdir/closedir instead of pipe.}} */ #if DEBUG zerr( "%s\n", buf); #endif while (fscanf(dirStream, " C.%lu", &jobId) > 0) { sprintf( buf, "%s/C.%lu", HOMEDIR, jobId); if (stat( buf, &jobStat) != 0) { zerr( "Unable to stat jobfile (%lu) (%s)\n", jobId, strerror(errno)); continue; } /* * Start with a clean slate, and read variables. */ set_global( global, TIMESTAMPPROMPT, (Ulong) 0); set_global( global, JOBTIMEOUTPROMPT, 0); set_global( global, PACKETTIMEOUTPROMPT, 0); if (read_jobfile_to_globs( global, jobId) != 0) { zerr( "Unable to read jobfile (%lu)\n", jobId); continue; } /* * First, check for job timeout. */ if ((timeout = get_global_ushort(global, JOBTIMEOUTPROMPT)) == 0) { timeout = JOB_TIMEOUT; } if ((jobTimestamp = get_global_ulong(global, TIMESTAMPPROMPT)) == 0) { /* Time stamp shouldn't be missing, but in case it does... */ jobTimestamp = (Ulong) jobStat.st_mtime; } if (jobTimestamp + timeout * 24 * 60 * 60L < timestamp) { mail_err( "Job %lu expired. Will kill job.", jobId); /* {{Should we notify the receiving user?}} */ /* * Job Timeout expired. Kill the job. */ zlog( "EXPIRED (killing job)"); kill_job(jobId); continue; } /* * Packet timeout. Only the receiver checks for it. */ /* * This also occurs when the UAR is not answered, e.g. when zend on the * other side is not installed as mail alias. There is no packet to ask * for yet. */ if (strcmp(get_global_string(global, ROLEPROMPT), ROLE_RECEIVER) != 0) { continue; } if ((timeout = get_global_ushort(global, PACKETTIMEOUTPROMPT)) && jobStat.st_mtime + timeout * 24 * 60 * 60L < timestamp) { /* * Packet timeout expired. Fire resend packet. */ zlog( "TIMEOUT (request resend)"); set_global( global, ACTIONPROMPT, ACTION_RSN); set_global( remoteglobal, SYSTEMNAMEPROMPT, get_global_string( global, SENDINGSYSTEMPROMPT)); #if DEBUG zerr( "Writing jobfile...\n"); #endif write_globs_to_jobfile( global, jobId); mail_resend(); } } pclose( dirStream); } /*====================================================================== * $Log: zend.c,v $ * Revision 1.14 1993/02/21 13:05:20 gerben * We zend an error to the receiver at job timeout * * Revision 1.13 1993/02/02 21:00:00 gerben * *** empty log message *** * * Revision 1.14 1993/02/02 17:55:03 tom * touch jobfile after rezend. * * Revision 1.13 1993/02/02 11:45:45 tom * add some comments. * * Revision 1.12 1993/01/28 18:54:26 tom * allow zend to be installed setuid non-root. Plug some more security holes * (environment; umask; subprogram execution as real user; be more careful with * file access modes; create spoolfile through a pipe so user process does not * have to access the spool directory.) * * Revision 1.11 1993/01/10 23:47:53 tom * shell-quote user-supplied names; Posixate access modes; handle "." and "..". * * Revision 1.10 1992/12/10 05:27:33 tom * add signal handling; -CLEANUP flag (for use in /etc/rc to cleanup stale * locks); plug security holes by switching to original uid when executing * programs wherever possible, and using absolute program pathnames elsewhere; * allow trailing '/' after filename; send uncompressed if compression would * increase the file size (yes this can happen!). * *======================================================================*/
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.