/******************************************************************************
 *
 * FILE:	wwwmail.c
 * PURPOSE:	this is a customized version of 'rsh' which remote-shells
 *		to the hard-coded 'remote' host to mail a file from a
 *		chroot-ed web environment.  In this web context, the remote
 *		host and the local host are the SAME machine.  If they are
 *		not the same machine then the remote host must be able to
 *		access the file to be mailed (eg: via nfs, bad idea).
 * AUTHOR:	Denice Deatrich
 * DATE:	Jan-2001
 *
 * CHANGES:	
 * USAGE:	./mail  "-s subj -t user@host.some.where -f /pathto/file.txt"
 *
 * NOTES:	This utility requires that the 'remote user' in the rcmd()
 *		function allows the 'local user' access.  Do the following
 *		to make this work on a RH Linux system:
 *		- the remote user should be a non-privileged user with a
 *		  ~/.rhosts entry for the local user.  Note that you may
 *		  disable 'login' for the remote user in the password file,
 *		  but a valid shell is still required in the passwd file.
 *		  Example:  localuser= www   remoteuser= dummy  web-host= ns
 *		  --> thus ~dummy/.rhosts on the remote host contains:
 *		   ns www
 *		- on the remote host tcpwrappers [ /etc/hosts.allow ] must
 *		  allow 'rsh' from the web-host, and the 'rsh-server' rpm
 *		  must be installed and configured.
 *		- the /etc/services file in the web chroot-ed tree must exist,
 *		  and must contain one line for the shell:
 *		   shell    514/tcp    cmd             # no passwords used
 *
 *		Note also that the args are passed as a single QUOTED string,
 *		and are then parsed for anomolies.
 *
 *		This utility invokes 'metasend' on the remote host, a mail
 *		utility avaliable on linux (metamail rpm).
 *
 * COMPILE:	cc -o mail wwwmail.c
 *		- this utility must be installed setuid-root, it uses a
 *		  privileged port:
 *		    chown root mail
 *		    chmod u+s mail
 *		[ on solaris compile-and-link with  -lsocket -lnsl ]
 *
 * SEE ALSO:	man rcmd, man hosts.allow, man services
 *
 ******************************************************************************
*/

/* INCLUDE FILES */
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

/* DEFINED CONSTANTS */
#define BUFSZ		512	/* arbitrary size of cmd-line for rsh */

/* EXTERNAL DECLARATIONS */
extern int	errno;

/* STRUCTURE DEFINITIONS */

/* GLOBAL VARIABLES */
/** warning: hard-coded!! **/
static char *cmdbuf = "/usr/bin/metasend -b -m text/plain";
static char *rootbuf = "/www";	/* real path to prepend to buffer */
static char *topdir = "/downloads/";	/* top path allowed for web access */

/******************************************************************************
 * FUNCTION:    exitWithError
 * PURPOSE:     print error message and exit with status 1;
 *
 * ARGUMENTS:   errSet   - if system errno has been set
 *		lineno   - line number in source file where this fnc called
 *
 * RETURNS:     n/a
 *
 * NOTES:
 *****************************************************************************/
void exitWithError( int errSet, int lineno )
{
  if( errSet ) {
    fprintf( stderr, "ERR: %s at line %d\n", strerror(errno), lineno );
  }
  else {
    fprintf( stderr, "ERR: at line %d\n", lineno );
  }
  fflush( stderr );
  exit( 1 );
}

/******************************************************************************
 * FUNCTION:    main
 * PURPOSE:     entry point to this task
 *
 * ARGUMENTS:   argc, argv - the usual
 *
 * RETURNS:     n/a
 *
 * LOGIC:	1. check for illegal uid, then give up root priv
 * 		2. parse carefully the cmd-line args
 * 		3. get back root priv.
 * 		4. launch rcmd
 * 		5. give up priv, and deal with cleanup
 *		--> It's mostly just error-checking...
 * 		
 * NOTES:	It's incredibly important to parse the command-line arg 
 *		very carefully.
 *		See the comments on parsing below.
 *****************************************************************************/
main( int argc, char *argv[] )
{
  struct servent	*sp;		/** ptr to servicebyname struct **/
  struct stat		sbuf;		/** stat buf for file to be mailed **/
  char			tmpbuf[BUFSZ];	/** temp buffer **/
  char			buf[BUFSZ];	/** buffer to pass to remote shell **/
  char			*p, *tmp;	/** tmp pointers **/
  char			ch;		/** char **/
  int			lineno = -1;	/** debug **/
  int			errSet = 0;	/** debug **/
  int			uid = -1;	/** uid of user **/
  int			i, j, bufsz;	/** counters, size of buf[] **/
  int			rfd;		/** remote cmd file descriptor **/
  int			len;		/** length of a string **/
  int			argbad;		/** 1 if arg is bad, else 0 **/
  FILE			*fp;		/** tmp FILE ptrs **/
  /** the following are hard-coded!! */
  char			*host[] = { "ns" };	/** host to contact **/
  const char		*luser = "www";		/** local user **/
  const char		*ruser = "dummy";	/** remote user **/
  const char		*usage =
    "\"-s some_subj -t user@host.some.where -f /pathto/file.txt\"";

  uid = getuid();
  if( uid > 100 ) {		/** give up root privilege **/
    if( seteuid(uid) == -1 ) {
      exitWithError( 1, __LINE__ );
    }
  }
  else {	/** we could also just limit this to the uid of the httpd **/
    fprintf( stderr, "Who are you?  uid is too small.  Aborting..\n" );
    exitWithError( 0, __LINE__ );
  }
  if( (sp = getservbyname("shell", "tcp")) != NULL ) {
    if( argc != 2 ) {
      fprintf( stderr, "USAGE: %s %s\n", argv[0], usage );
      exitWithError( 0, __LINE__ );
    }
    memset( buf, '\0', BUFSZ );	/** fill bufs with nulls **/
    memset( tmpbuf, '\0', BUFSZ );
    strncpy( tmpbuf, argv[1], BUFSZ-1 ); /* work on a buffer copy */
    argbad = 0;
    i = 1;
    p = strtok( tmpbuf, " \t" );
    len = strlen( p );
    /** check command-line args for illegal chars **/
    do {
      switch( i ) {	/*** Parse each argument carefully ***/
      case 1: /* First arg: -s */
        if( strcmp(p, "-s") != 0 ) {
          argbad = 1;
        }
	break;
      case 2: /* a one-word_subject is the second arg -- it may be composed
                 of alphanum chars, and/or '-' and/or '_' */
        for( j=0; j<len; j++ ) {
          ch = *(p+j);
          if( !(isalnum(ch) || (unsigned int)ch=='_' ||
           (unsigned int)ch=='-') ) {
            argbad = 1;
          }
        }
	break;
      case 3: /* -t */
        if( strcmp(p, "-t") != 0 ) {
          argbad = 1;
        }
	break;
      case 4: /* email address for the destined user -- it may be composed
                 of alphanum chars, and/or '_' and/or '.' and '@'  */
        for( j=0; j<len; j++ ) {
          ch = *(p+j);
          if( !(isalnum(ch) || (unsigned int)ch=='_' ||
           (unsigned int)ch=='@' || (unsigned int)ch=='.') ) {
            argbad = 1;
          }
        }
	break;
      case 5: /* -f */
        if( strcmp(p, "-f") != 0 ) {
          argbad = 1;
        }
	break;
      case 6: /* /path/to/sometextfile */

        /** must not contain '..' **/
        if( strstr(p, "..") != NULL ) {
          argbad = 1;
        }
        /** must not overrun the buffer **/
        else if( strlen(cmdbuf) + strlen(argv[1])+ strlen(rootbuf)
         > BUFSZ-10 ) {
          argbad = 1;
        }
        /** must be a full path beginning with '/' **/
        else if( *p != '/' ) {
          argbad = 1;
        }
        /** must exist and not be symlink and must not be too big **/
        else if( stat(p, &sbuf) != 0 ) {
          argbad = 1;
        }
        else if( S_ISLNK(sbuf.st_mode) || sbuf.st_size > 50000 ) {
          argbad = 1;
        }
        /** furthermore the file path must begin with 'topdir' (hard-coded) **/
        else if( strncmp(p, topdir, strlen(topdir)) != 0 ) {
          argbad = 1;
        }
        /** may only contain alphanum chars, '_', '-', '.', and/or "/" **/
        else {
          sprintf( buf, "%s %s", cmdbuf, argv[1] );
          tmp = strstr( buf, p );
          sprintf( tmp, " %s%s", rootbuf,p );
          for( j=0; j<len; j++ ) {
            ch = *(p+j);
            if( !(isalnum(ch) || (unsigned int)ch=='_' ||
             (unsigned int)ch=='.' || (unsigned int)ch=='/') ) {
              argbad = 1;
            }
          }
        }
	break;
      }
      p = strtok( NULL, " \t" );
      if( p ) {
        len = strlen( p );
      }
      i++;
    }while( i <= 6 && p && ! argbad );
    if( argbad ) {
      fprintf( stderr, "USAGE: %s %s\n", argv[0], usage );
      exitWithError( 0, __LINE__ );
    }

    if( uid > 0 ) {	/** get back root privilege for the rcmd() call **/
      if( seteuid(0) == -1 ) {
        exitWithError( 1, __LINE__ );
      }
    }
    /* fprintf( stderr, "DBG: cmd is %s\n", buf ); */
    if( (rfd = rcmd(host, sp->s_port, luser, ruser, buf, 0)) != -1 ) {
      if( seteuid(uid) == -1 ) { /* give up priv. again. */
        exitWithError( 1, __LINE__ );
      }
      if( (fp = fdopen(rfd, "r")) != NULL ) {
        /** capture the output; you won't see the output in a web
         ** environment, but from the command-line it may be helpful..
         **/
        while( (ch = getc(fp)) != EOF ) {
          fputc( ch, stderr );
        }
        fclose( fp );
      }
      else {
        exitWithError( 1, __LINE__ );
      }
      fprintf( stderr, "mail sent.\n" );
    }
    else {
      fprintf( stderr, "Remote command failed ..\n" );
      exitWithError( 0, __LINE__ );
    }
  }
  else {
    exitWithError( 1, __LINE__ );
  }

  exit( 0 );

}/* main() ends */