/*
 *  magicfilter.c
 *
 *  Copyright © 1993-95 H. Peter Anvin
 *
 *  "Magic" default filter for lpr/lpd which attempt to identify
 *  the type of file and filter it accordingly
 *
 */

#include "magicfilter.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/fcntl.h>
#include <sys/types.h>
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

char *inblock;			/* Block read */
int inblock_size;		/* Bytes reserved for the inblock */
int inblock_len;		/* Number of bytes read (<= inblock_size) */
int done = 0;			/* Set to 1 if no more iterations */
char *program;			/* Set to argv[0] in main() */
char *config_file = NULL;	/* Configuration file */
int debug_flag = 0;		/* If debugging */

/* What to call ourselves in case of failure */
#define ERR_NAME                (config_file ? config_file : program)

extern const struct datatype dtypetab[]; /* Table of magic */

/* Location of the mailer */
#ifndef NOMAIL
#ifndef SENDMAIL
#ifdef _PATH_SENDMAIL
/* Path defined in paths.h */
#define SENDMAIL _PATH_SENDMAIL
#else
#ifdef HAVE_SENDMAIL
/* Path defined by autoconf */
#define SENDMAIL PATH_SENDMAIL
#endif
#endif
#endif
#ifndef SENDMAIL
/* We couldn't find a mailer anywhere -- disable mail */
#define NOMAIL 1
#endif
#endif

/* Header of mail message */
#ifndef NOMAIL
const char *mailheader =	/* Header of complaint message */
"To: %s\n\
Subject: Rejected print job\n\

Your print job was automatically rejected as an invalid data type.\n\n";
#endif

/* ------------------------------------------------------------------------- *
 * Fudging of certain functions if we do not have them.  None of these
 * are very good to use, but are better than nothing.
 * ------------------------------------------------------------------------- */

#ifndef HAVE_DUP2
int dup2(int fd1, int fd2)

#ifdef F_DUPFD
{
  close(fd2);
  return fcntl(fildes, F_DUPFD, fd2);
}
#else
{
  int rtn;

  close(fd2);			/* Might return error if unused, is OK */
  rtn = dup(fd1);

  if ( rtn >= 0 && rtn != fd2 )
    {
      fprintf(stderr, "%s: dup() trick failed -- aborting\n", ERR_NAME);
      exit(1);
    }

  return rtn;
}
#endif

#endif

#ifdef HAVE_TMPNAM
/* If we don't have L_tmpnam, we can't use the provided tmpnam */
#ifndef L_tmpnam
#define tmpnam(X) my_tmpnam(X)
#undef HAVE_TMPNAM
#endif
#endif

#ifndef HAVE_TMPNAM
/*
 * Lame implementation of tmpnam which works on most Unices
 */

#define L_tmpnam (15+3*sizeof(pid_t))

char *tmpnam(char *buf)
{
  static char tmp_buf[L_tmpnam];
  static int count = 0;

  if ( !buf ) buf = tmp_buf;

  sprintf(buf, "/tmp/_magf.%u.%02x", (unsigned int) getpid(), count++);
  return buf;
}
#endif

/* ------------------------------------------------------------------------- *
 *  run(command)
 *
 * Similar to system(), but always run a specified shell and does not
 * fork.  We don't want to fork because we are creating a pipe, anyway.
 *
 * The shell is defined in SYSSHELL and is called as
 * ``<SYSSHELL> -c command_line''
 * ------------------------------------------------------------------------- */

#ifndef SYSSHELL
#ifdef _PATH_BSHELL
#define SYSSHELL _PATH_BSHELL
#else
#define SYSSHELL "/bin/sh"
#endif
#endif
#ifndef SHELLFLAG
#define SHELLFLAG "-c"
#endif

void run(char *command)
{
  execl(SYSSHELL,SYSSHELL,SHELLFLAG,command,(char *)NULL);
  exit(255);			/* If we get here, the exec failed */
}


/* ------------------------------------------------------------------------- *
 *  fork_daemon()
 *
 * Fork a process that doesn't have to be wait'ed on.  This it done by
 * forking *twice* and orphanning the grandchild process, leaving it
 * adopted by init.  Do a reasonable job of propagating errors.
 *
 * Returns 0 for the child and 1 for the parent if OK; otherwise -1.
 * (The reason the parent returns 1 is that is *doesn't know* the pid of
 * the spawned child process.)
 * ------------------------------------------------------------------------- */

/* This is for non-POSIX compliant systems */
#ifndef WEXITSTATUS
#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif
#ifndef WIFEXITED
#define WIFEXITED(stat_val) (((stat_val) & 0xff) == 0)
#endif

#ifndef HAVE_WAITPID
/* We don't have waitpid(), we have to emulate it */
#ifdef HAVE_WAIT4
/* With wait4() we should be set */
#define waitpid(p,s,o) wait4(p, s, o, NULL)
#else
/* This is a feeble implementation using wait(); it doesn't implement
   the full waitpid() semantics, but should be close enough for
   what we need.  Use with caution. */
pid_t waitpid(pid_t pid, int *status, int options)
{
  pid_t retval;
  do
    retval = wait(status);
  while ( retval > 0 && retval != pid );
  return retval;
}
#endif
#endif

int fork_daemon(void)
{
  pid_t pid;
  int status;

  pid = fork();
  if ( pid < 0 )
    return -1;			/* Error on first fork */
  else if ( pid == 0 )
    {
      pid = fork();
      if ( pid < 0 )
	_exit(errno);
      else if ( pid == 0 )
	return 0;		/* Child process */
      else
	_exit(0);
    }
  else
    {
      waitpid(pid,&status,0);
      if (status)		/* Did we return error? */
	{
	  errno = WIFEXITED(status) ? WEXITSTATUS(status) : EINTR;
	  return -1;
	}
      return 1;			/* Parent process */
    }
}

/* ------------------------------------------------------------------------- *
 *  cat(file)
 *  act_cat(opt_string)
 *
 * Feed the input to a specified file without any processing whatsoever.
 *
 * act_cat() is the action; it will attempt to extract a prefix and suffix
 * string from the included option string as well.
 * ------------------------------------------------------------------------- */

void cat(FILE *f)
{
  if ( inblock_len > 0 )
    fwrite(inblock,1,inblock_len,f);

  while( ( inblock_len = fread(inblock,1,inblock_size,stdin) ) > 0 )
    {
      fwrite(inblock,1,inblock_len,f);
    }
}

typedef void datafunc(FILE *);
void do_prefix_suffix(char *opt_str, datafunc *convfunc, FILE *f)
{
  char *p, *prefix, *suffix = NULL;
  int n;

  if ( (n = getmagic(opt_str,&p,NULL,NO_WILD)) >= 0 )
    {
      if ( n > 0 )
	{
	  if ( !(prefix = malloc(n)) )
	    {
	      fprintf(stderr, "%s: Out of memory\n", ERR_NAME);
	      exit(1);
	    }
	  getmagic(opt_str,&p,prefix,NO_WILD);
	  fwrite(prefix,1,n,f); /* Write prefix string */
	  free(prefix);
	}
      if ( (n = getmagic(p,NULL,NULL,NO_WILD)) > 0 )
	{
	  if ( !(suffix = malloc(n)) )
	    {
	      fprintf(stderr, "%s: Out of memory\n", ERR_NAME);
	      exit(1);
	    }
	  getmagic(p,NULL,suffix,NO_WILD);
	}
    }
  
  convfunc(f);

  if ( suffix )
    {
      fwrite(suffix,1,n,f);
      free(suffix);
    }
}

void act_cat(char *opt_str)
{
  if ( debug_flag )
    fprintf(stderr, "cat %s\n", opt_str);
  
  do_prefix_suffix(opt_str, cat, stdout);
  done = 1;
}

/* ------------------------------------------------------------------------- *
 *  act_addcr()
 *
 * Feed the input to the output, substituting:
 *    \n -> \r\n     \f -> \r\f
 *
 * It also adds \r\f at the end of the file.
 * 
 * Used for printers which need CR's at line endings.  Note that if your
 * printer supports a different character set (say Roman-8) than your host
 * (say Latin-1) you may be better off using ACT_FILTER and a translating
 * filter such as "lpchar".
 * ------------------------------------------------------------------------- */

void addcr(FILE *f)
{
  char *p;
  int i;

  do
    {
      for ( i = inblock_len, p = inblock ; i ; i--, p++ )
	{
	  switch ( *p )
	    {
	    case '\n':
	    case '\f':
	      putc('\r', f);
	      /* Fall through */

	    default:
	      putc(*p, f);
	      break;
	    }
	}
    
      inblock_len = fread(inblock, 1, inblock_size, stdin);
    }
  while ( inblock_len > 0 );

  putc('\r', f);
  putc('\f', f);
}


void act_addcr(char *opt_str)
{
  if ( debug_flag )
    fprintf(stderr, "text %s\n", opt_str);

  do_prefix_suffix(opt_str, addcr, stdout);
  done = 1;
}

/* ------------------------------------------------------------------------- *
 *  act_ps()
 *
 * Like act_addcr() but add a Ctrl-D to the end (PostScript End of Job).
 * Keeps the printer from running two jobs together.  nenscript in
 * particular is bad with this.
 * ------------------------------------------------------------------------- */

void act_ps(void)
{
  if ( debug_flag )
    fprintf(stderr, "postscript\n");
  
  addcr(stdout);
  putchar('\4');
  done = 1;
}


/* ------------------------------------------------------------------------- *
 *  act_drop()
 *
 * Just sets the done flag.
 * ------------------------------------------------------------------------- */

void act_drop(void)
{
  if ( debug_flag )
    fprintf(stderr, "ignore\n");

  done = 1;
}


/* ------------------------------------------------------------------------- *
 *  act_reject(message,user,host)
 *
 * Sends a rejection message to the user.  A bad file (such as an executable
 * binary) was encountered.
 * ------------------------------------------------------------------------- */

#ifndef NOMAIL
void act_reject(char *message, char *user, char *host)
{
  int fildes[2];		/* File descriptors for pipe */
  pid_t pid;
  FILE *msg;			/* Outgoing pipe */
  char address[BUFSIZ];		/* Buffer to create address */
  int status;

  if ( debug_flag )
    fprintf(stderr, "reject %s\n", message);

#ifdef BANG_ADDRESS
  sprintf(address,"%s!%s", host, user);
#else
  sprintf(address,"%s@%s", user, host);
#endif

  if ( pipe(fildes) )
    {
      perror(ERR_NAME);
      exit(1);
    }

  pid = fork();

  if ( pid < 0 )		/* Fork failed */
    {
      perror(ERR_NAME);
      exit(1);
    }
  else if ( pid == 0 )		/* Child process */
    {
      close(fildes[1]);
      dup2(fildes[0],fileno(stdin)); /* Redirect stdin */
      execl(SENDMAIL,SENDMAIL,address,(char *) NULL);
      exit(255);		/* Exec failed */
    }
  else				/* Parent process */
    {
      close(fildes[0]);
      msg = fdopen(fildes[1],"wb"); /* Make pipe file pointer */

      fprintf(msg, mailheader, address);
      fprintf(msg, "%s\n", message);
      fclose(msg);

      waitpid(pid,&status,0);

      if ( status )
	{
	  fprintf(stderr, "%s: %s failed\n", ERR_NAME, SENDMAIL);
	}
    }

  done = 1;
}
#else
#define act_reject(MSG,USR,HST) act_drop()
#endif    


/* ------------------------------------------------------------------------- *
 *  act_filter(command)
 * 
 * Set up a pipe and filter the input through that command.  Output goes
 * to stdout.
 * ------------------------------------------------------------------------- */

void act_filter(char *command)
{
  int fildes[2];		/* File descriptors for pipe */
  FILE *out;			/* Output file pointer */
  pid_t pid;			/* Child process ID */
  int status;

  if ( debug_flag )
    fprintf(stderr, "filter %s\n", command);

  if ( pipe(fildes) )
    {
      perror(ERR_NAME);
      exit(1);
    }

  pid = fork();

  if ( pid < 0 )		/* Fork failed */
    {
      perror(ERR_NAME);
      exit(1);
    }
  else if ( pid == 0 )		/* Child process */
    {
      close(fildes[1]);
      dup2(fildes[0],fileno(stdin)); /* Redirect stdin */
      run(command);
    }
  else				/* Parent process */
    {
      close(fildes[0]);
      out = fdopen(fildes[1],"wb"); /* Make pipe file pointer */
      cat(out);			/* Send output into pipe */
      fclose(out);

      waitpid(pid,&status,0);

      if ( status )
	{
	  fprintf(stderr, "%s: %s failed\n", ERR_NAME, command);
	}
    }

  done = 1;
}

/* ------------------------------------------------------------------------- *
 *  act_ffilter(command)
 * 
 * Create a temp file; then filter the temp file through that command.
 * Output goes to stdout.
 * ------------------------------------------------------------------------- */

void act_ffilter(char *command)
{
  static char tmpbuf[L_tmpnam+6] = "FILE=";
  FILE *out;			/* Output file pointer */
  int outfd;			/* Output file descriptor */
  pid_t pid;			/* Child process ID */
  int status;

  if ( debug_flag )
    fprintf(stderr, "ffilter %s\n", command);

  if ( !tmpnam(tmpbuf+5) || !(out = fopen(tmpbuf+5, "wb")) )
    {
      perror(ERR_NAME);
      exit(1);
    }

  cat(out);			/* Send output to temp file */
  fclose(out);

  pid = fork();

  if ( pid < 0 )		/* Fork failed */
    {
      perror(ERR_NAME);
      exit(1);
    }
  else if ( pid == 0 )		/* Child process */
    {
      putenv(tmpbuf);		/* Store name in $FILE */
      outfd = open(tmpbuf+5, O_RDONLY);
      dup2(outfd,fileno(stdin)); /* Redirect stdin */
      run(command);
    }
  else				/* Parent process */
    {
      waitpid(pid,&status,0);	/* Wait for process to finish */

      if ( status )
	{
	  fprintf(stderr, "%s: %s failed\n", ERR_NAME, command);
	}

      unlink(tmpbuf+5);		/* Remove temp file */
    }

  done = 1;
}

/* ------------------------------------------------------------------------- *
 *  act_pipethru(command)
 *
 * Pipes the output through a given command, creating a second pipe to
 * fetch the input for re-processing.
 *
 * This is done the "proper" way by creating a sender and a receiver process
 * around the filter process; who knows what the OS does if we overflow a
 * pipe.
 * ------------------------------------------------------------------------- */

void act_pipethru(char *command)
{
  int inpipefd[2];		/* File descriptors for input pipe */
  int outpipefd[2];		/* File descriptors for output pipe */
  FILE *out;			/* Output file pointer */
  pid_t pid, xpid, wpid;
  int null;			/* /dev/null */
  int status;			/* Process exit status */

  if ( debug_flag )
    fprintf(stderr, "pipe %s\n", command);

  if ( pipe(inpipefd) || pipe(outpipefd) )
    {
      perror(ERR_NAME);
      exit(1);
    }

  xpid = fork_daemon();
  
  if ( xpid < 0 )
    {
      perror(ERR_NAME);
      exit(1);
    }
  else if ( xpid == 0 )
    {
       close(outpipefd[0]);

       pid = fork();

       if ( pid < 0 )		/* Fork failed */
	 {
	   perror(ERR_NAME);
	   exit(1);
	 }
       else if ( pid == 0 )		/* Child process */
	 {
	   close(inpipefd[1]);
	   dup2(inpipefd[0],fileno(stdin));
	   dup2(outpipefd[1],fileno(stdout)); /* Redirect stdout */
	   run(command);
	 }
       else
	 {
	   /* Writing process */

	   close(inpipefd[0]);
	   close(outpipefd[1]);
	   
	   out = fdopen(inpipefd[1],"wb");
	   cat(out);		/* Send output into pipe */
	   fclose(out);
	   
	   /* Wait for filter to exit */

	   if ( waitpid(pid,&status,0) < 0 )
	     perror("waitpid");
	   
	   /* If status > 0 we should probably do something, but it is
	      difficult to know what we can do at this stage */
	   
	   exit(0);
	 }
     }
  else
    {
      /* Reading process */
      
      close(inpipefd[0]);
      close(outpipefd[1]);
      close(inpipefd[1]);
      
      /* Flush the stdin buffer */
      null = open("/dev/null", O_RDONLY);
      dup2(null, fileno(stdin));
      close(null);
      while ( fread(inblock,1,inblock_size,stdin) > 0 );
      
      if ( dup2(outpipefd[0],fileno(stdin)) < 0 )
	{
	  perror("dup2");
	  exit(1);
	} /* Redirect stdin */
      
      close(outpipefd[0]);
      
      rewind(stdin);
      inblock_len = fread(inblock,1,inblock_size,stdin);
    }
}


/* ------------------------------------------------------------------------- *
 *  act_fpipe(command)
 *
 * Creates a temp file and sends the output through a given command,
 * creating a pipe to fetch the input for re-processing.
 * ------------------------------------------------------------------------- */

void act_fpipe(char *command)
{
  static char tmpbuf[L_tmpnam+6] = "FILE=";
  int outpipefd[2];		/* File descriptors for output pipe */
  FILE *out;			/* Output file pointer */
  int outfd;			/* Output file descriptor */
  pid_t pid, xpid, wpid;
  int null;			/* /dev/null */
  int status;			/* Exit status */

  if ( debug_flag )
    fprintf(stderr, "fpipe %s\n", command);

  if ( !tmpnam(tmpbuf+5) || !(out = fopen(tmpbuf+5,"w+b")) || pipe(outpipefd) )
    {
#if DEBUG > 2
      fprintf(stderr, "TMPBUF: %s\n", tmpbuf);
#endif
      perror(ERR_NAME);
      exit(1);
    }

  cat(out);			/* Write output to file */
  fclose(out);

  xpid = fork_daemon();
  
  if ( xpid < 0 )
    {
      perror(ERR_NAME);
      exit(1);
    }
  else if ( xpid == 0 )
    {
      close(outpipefd[0]);
      
      pid = fork();
      
      if ( pid < 0 )		/* Fork failed */
	{
	  perror(ERR_NAME);
	  exit(1);
	}
      else if ( pid == 0 )		/* Child process */
	{
	  putenv(tmpbuf);		/* Put filename in $FILE */
	  outfd = open(tmpbuf+5, O_RDONLY);
	  dup2(outfd,fileno(stdin)); /* Redirect stdin */
	  dup2(outpipefd[1],fileno(stdout)); /* Redirect stdout */
	  run(command);
	}
      else				/* Parent process */
	{
	  close(outpipefd[1]);
	  
	  /* Wait for filter to finish and then remove temp file */

	  if ( waitpid(pid,&status,0) < 0 )
	    perror("waitpid");
	  
	  /* If status > 0 we should probably do something, but it is
	     difficult to know what we can do at this stage */
	  
	  unlink(tmpbuf+5);
	  exit(0);
	}
    }
  else
    {
      close(outpipefd[1]);
      
      /* Reading process */
      
      /* Flush the stdin buffer */
      null = open("/dev/null", O_RDONLY);
      dup2(null, fileno(stdin));
      close(null);
      while ( fread(inblock,1,inblock_size,stdin) > 0 );
      
      if ( dup2(outpipefd[0],fileno(stdin)) < 0 )
	{
	  perror("dup2");
	  exit(1);
	} /* Redirect stdin */
      
      close(outpipefd[0]);
      
      rewind(stdin);
      inblock_len = fread(inblock,1,inblock_size,stdin);
    }
}


/* ------------------------------------------------------------------------- *
 *  matches(block, pattern, mask, length)
 *
 *  Returns nonzero if block matches pattern.
 * ------------------------------------------------------------------------- */

int matches(char *block, char *pattern, char *mask, int length)
{
  int i;
  char *p, *q, *m;

  q = block;
  p = pattern;
  m = mask;

  for ( i = length ; i ; i-- )
    {
      if ( (*p ^ *q) & *m )
	return 0;
      q++; p++; m++;
    }

#if DEBUG > 3
  fprintf(stderr,"Pattern: ");
  for ( i = 0 ; i < length ; i++ )
    {
      fprintf(stderr,"%03o ", (unsigned char) block[i]);
    }
  fprintf(stderr,"\nMatches: ");
  for ( i = 0 ; i < length ; i++ )
    {
      if ( mask[i] )
	fprintf(stderr,"%03o ", (unsigned char) pattern[i]);
      else
	fprintf(stderr,"??? ");
    }
  fprintf(stderr,"\n");
#endif

  return 1;
}

 
/* ------------------------------------------------------------------------- *
 *  main(argc,argv)
 * 
 * Analyze the input data and choose an appropriate way to handle it.
 * ------------------------------------------------------------------------- */

main(int argc, char *argv[])
{
  int do_cat = 0;		/* True if -c option */
  int i;
  const struct datatype *dtab;	/* Data type table */
  const struct datatype *dt;	/* Data type searched */
  char *user = "unknown";	/* User name */
  char *host = "unknown";	/* Host name */
  char *indent;			/* Indentation (-i option) */
  char *lp_user, *lp_host; 	/* For setenv command */
  char *lp_indent;

  program = argv[0];		/* Make available for error messages */

  if ( argc < 2 )
    {
      fprintf(stderr, "%s: No configuration file specified\n", program);
      exit(1);
    }

  /* Clear these strings from environment
     This actually put it in as null strings, but unsetenv() is
     apparently not portable.  This might be a good use for
     autoconf. */

  putenv("LPUSER=");
  putenv("LPHOST=");
  putenv("LPINDENT=0");		/* Default to zero */
  putenv("FILE=");

  for ( i = 1 ; i < argc ; i++ ) /* Options passed by lpd */
    {
      if ( argv[i][0] == '-' )
	{
	  if ( strcmp(argv[i],"-c") == 0 )
	    do_cat = 1;
	  else if ( strcmp(argv[i],"-n") == 0 && i < argc-1 )
	    {
	      user = argv[++i];
	      if ( (lp_user = malloc(strlen(user)+8)) == NULL )
		{
		  fprintf(stderr, "%s: Out of memory\n", argv[1]);
		  exit(1);
		}
	      sprintf(lp_user, "LPUSER=%s", user);
	      putenv(lp_user);
	    }
	  else if ( strcmp(argv[i],"-h") == 0 && i < argc-1 )
	    {
	      host = argv[++i];
	      if ( (lp_host = malloc(strlen(host)+8)) == NULL )
		{
		  fprintf(stderr, "%s: Out of memory\n", argv[1]);
		  exit(1);
		}
	      sprintf(lp_host, "LPHOST=%s", host);
	      putenv(lp_host);
	    }
	  else if ( strncmp(argv[i],"-i",2) == 0 )
	    {
	      indent = argv[i]+2;
	      if ( (lp_indent = malloc(strlen(indent)+10)) == NULL )
		{
		  fprintf(stderr, "%s: Out of memory\n", argv[1]);
		  exit(1);
		}
	      sprintf(lp_indent, "LPINDENT=%s", indent);
	      putenv(lp_indent);
	    }
	  else if ( strcmp(argv[i], "--debug") == 0 )
	    debug_flag = 1;
	  /* Ignore other options passed by lpd */
	}
      else if ( !config_file )
	config_file = argv[i];
    }

  umask(077);			/* Protect the user's privacy */

  if ( do_cat )			/* Literal flag: skip config file */
    {
      if ( debug_flag )
	fprintf(stderr, "(literal)\n");
      inblock_size = BUFSIZ;
    }
  else
    {
      dtab = load_config(config_file, &i); /* Load configuration file */
      if ( !dtab ) exit(1);	/* load_config does the error message */
      /* Round up to nearest multiple of BUFSIZ, for efficiency */
      inblock_size = ((i+BUFSIZ-1)/BUFSIZ)*BUFSIZ;
    }

  if ( !(inblock = malloc(inblock_size)) )
    {
      fprintf(stderr, "%s: Out of memory\n", ERR_NAME);
      exit(1);
    }
  
  inblock_len = fread(inblock,1,inblock_size,stdin);
      
  if ( do_cat )			/* Literal flag -> always ACT_CAT */
    {
      cat(stdout);
      done = 1;
    }
  else
    {
      while ( !done && inblock_len > 0 )
	{
	  dt = dtab;		/* Head of linked list */
	  while ( dt )
	    {
	      if ( dt->length < 1 ||
		  matches(inblock+dt->offset,dt->magic,dt->mask,dt->length) )
		break;
	      
	      dt = dt->next;
	    }
	  
	  if ( !dt )
	    {
	      fprintf(stderr, "%s: Unknown type data and no default; \
job sent by %s@%s)\n", ERR_NAME, user, host);
	      exit(1);
	    }
	  
	  /* Now dt should point to the proper data type record to use */
	  
	  switch( dt->action )
	    {
	    case ACT_CAT:
	      act_cat(dt->command);
	      break;
	      
	    case ACT_DROP:
	      act_drop();
	      break;
	      
	    case ACT_REJECT:
	      act_reject(dt->command, user, host);
	      break;
	      
	    case ACT_FILTER:
	      act_filter(dt->command);
	      break;
	      
	    case ACT_PIPETHRU:
	      act_pipethru(dt->command);
	      break;
	      
	    case ACT_ADDCR:
	      act_addcr(dt->command);
	      break;

	    case ACT_PS:
	      act_ps();
	      break;

	    case ACT_FFILTER:
	      act_ffilter(dt->command);
	      break;

	    case ACT_FPIPE:
	      act_fpipe(dt->command);
	      break;

	    default:
	      fprintf(stderr, "%s(%s): Internal error - invalid parse\n\
Please mail your configuration file to <Peter.Anvin@linux.org>\n",
		      program, config_file);
	      return 1;
	    }
	}

#ifdef REJECT_EMPTY
      if ( !done )
	{
	  /* Send a message to the user informing that his/her print job
	     ended up empty */

	  act_reject(REJECT_EMPTY, user, host);
	}
#endif
    }

  return 0;			/* Error */
}
