/***************************************************************************
* LPRng - An Extended Print Spooler System
*
* Copyright 1988-1995 Patrick Powell, San Diego State University
*     [email protected]
* See LICENSE for conditions of use.
* From the original PLP Software distribution
*
***************************************************************************
* MODULE: main.c for filter
* PURPOSE:  setup a filter environment
**************************************************************************/

/***************************************************************************
* MODULE: main.c for filters
***************************************************************************
*
* Wed Aug 16 09:35:24 PDT 1995 Patrick Powell
*  Added new filter options -s, etc.
*
* Revision History: Baseline Fri Jul 14 16:16:34 PDT 1995
*
* Patrick Powell Fri Jul 14 16:16:13 PDT 1995
*
***************************************************************************/
#ifndef lint
static char _id[] =
       "main.c,v 1.1 1997/01/05 04:57:02 papowell Exp";
#endif

/***************************************************************************
*  Filter template and frontend.
*
*      A filter is invoked with the following parameters,
*  which can be in any order, and perhaps some missing.
*
*  filtername arguments \   <- from PRINTCAP entry
*      -PPrinter -wwidth -llength -xwidth -ylength [-c] \
*      -kcontrolfilename -Lbnrname \
*      [-iindent] \
*              [-Zoptions] [-Cclass] [-Jjob] [-Raccntname] -nlogin -hHost  \
*      -Fformat -Ddebug [affile]
*
*  1. Parameters can be in different order than the above.
*  2. Optional parameters can be missing
*  3. Values specified for the width, length, etc., are from PRINTCAP
*     or from the overridding user specified options.
*
*  This program provides a common front end for most of the necessary
*  grunt work.  This falls into the following classes:
* 1. Parameter extraction.
* 2. Suspension when used as the "of" filter.
* 3. Termination and accounting
*  The front end will extract parameters,  then call the filter()
*  routine,  which is responsible for carrying out the required filter
*  actions. filter() is invoked with the printer device on fd 1,
*      and error log on fd 2.  The npages variable is used to record the
*  number of pages that were used.
*  The "halt string", which is a sequence of characters that
*  should cause the filter to suspend itself, is passed to filter.
*  When these characters are detected,  the "suspend()" routine should be
*  called.
*
*  On successful termination,  the accounting file will be updated.
*
*  The filter() routine should return 0 (success), 1 (retry) or 2 (abort).
*
* Parameter Extraction
*      The main() routine will extract parameters
*  whose values are placed in the appropriate variables.  This is done
*  by using the ParmTable[], which has entries for each valid letter
*  parmeter, such as the letter flag, the type of variable,
*  and the address of the variable.
*  The following variables are provided as a default set.
*
* VARIABLE  FLAG          TYPE    PURPOSE / PRINTCAP ENTRTY
* name     filterparm     char*    argv[0], program identification
* accntfile -aaccnting    char*    AF, accounting file
* bytecount -bbytecount   int      size of job in bytes
* literal  -c             int      if set, ignore control chars
* ctrldir  -dctrldir      char*    control directory for status information
* dfilename -edfilename   char*    datafile name in spool directory
* filename -ffilename     char*    original file name of data file
* host     -hhost         char*    host name
* indent   -iindent       int      indent amount (depends on device)
* jobnum   -jjobnum       int      job number from cfNNNhost, jobnum = NNN
* cffile   -kcffile       char*    job control file (cfNNNhost) name
* length   -llength       int      PL, length in lines
* login    -nlogin        char*    login name
* statusfile -sstatusfile char*    statusfile
* timestr  -ttimestr      char*    time of printing
* width    -wwidth            int      PW, width in chars
* xwidth    -xwidth        int      PX, width in pixels
* ylength   -ylength       int      PY, length in pixels
* class    -Cclass        char*    classname
* format   -Fformat       char*    format
* job      -Jjob          char*    jobname
* bnrname  -Lbnrname      char*    banner name
* printer  -Printer       char*    printer name
* accntname  -Raccntname  char*    name to be used for accounting
* comment  -Scomment      char*    comment field in printcap
* accntname -Raccntname   char*    account for billing purposes
* zopts    -Zoptions      char*    extra options for printer
* accntfile file          char*    AF, accounting file
*
* Additional Variables - used for the following
* npages    - number of pages for accounting
* debug     - sets debug level
* verbose   - echo to a log file
*
*      The functions fatal(), logerr(), and logerr_die() can be used to report
*      status. The variable errorcode can be set by the user before calling
*      these functions, and will be the exit value of the program. Its default
*      value will be 2 (abort status).
*      fatal() reports a fatal message, and terminates.
*      logerr() reports a message, appends information indicated by errno
*      (see perror(2) for details), and then returns.
*      logerr_die() will call logerr(), and then will exit with errorcode
*      status.
*      Both fatal() and logerr_die() call the cleanup() function before exit.
*
* DEBUGGING:  a simple minded debugging version can be enabled by
* compiling with the -DDEBUG option.
* NB: check error code status with LPRng documentation.
*/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <sys/types.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_FILE_H
# include <sys/file.h>
#endif

#ifndef HAVE_ERRNO_DECL
extern int errno;
#endif

#ifdef HAVE_SYS_FCNTL_H
# include <sys/fcntl.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif

/* varargs declarations: */

#if defined(HAVE_STDARG_H)
# include <stdarg.h>
# define HAVE_STDARGS    /* let's hope that works everywhere (mj) */
# define VA_LOCAL_DECL   va_list ap;
# define VA_START(f)     va_start(ap, f)
# define VA_SHIFT(v,t)  ;       /* no-op for ANSI */
# define VA_END          va_end(ap)
#else
# if defined(HAVE_VARARGS_H)
#  include <varargs.h>
#  undef HAVE_STDARGS
#  define VA_LOCAL_DECL   va_list ap;
#  define VA_START(f)     va_start(ap)          /* f is ignored! */
#  define VA_SHIFT(v,t) v = va_arg(ap,t)
#  define VA_END                va_end(ap)
# else
XX ** NO VARARGS ** XX
# endif
#endif

/*
* default exit status, causes abort
*/
int errorcode;
char *name;             /* name of filter */

int debug, verbose, width, length, xwidth, ylength, literal, indent;
int bytecount, jobnum;

char *zopts, *class, *job, *login, *accntname, *host, *accntfile, *format;
char *printer, *controlfile, *bnrname, *comment;
char *queuename, *errorfile;
char *ctrldir, *dfilename, *filename, *cffile, *timestr;
int npages;     /* number of pages */
char *statusfile;
char filter_stop[] = "\031\001";        /* sent to cause filter to suspend */

void getargs( int argc, char *argv[], char *envp[] );

#ifdef HAVE_STDARGS
void log( char *msg, ... );
void logerr( char *msg, ... );
void logerr_die( char *msg, ... );
void fatal( char *msg, ... );
#else
void log();
void logerr();
void logerr_die();
void fatal();
#endif
extern void banner( void );
extern void cleanup( void );
extern void doaccnt( void );
extern void filter( char * );

int main( int argc, char *argv[], char *envp[] )
{
       getargs( argc, argv, envp );
       /*
        * Turn off SIGPIPE
        */
       (void)signal( SIGPIPE, SIG_IGN );
       if( format && format[0] == 'o' ){
               filter( filter_stop );
       } else {
               filter( (char *)0 );
       }
       return(0);
}

/****************************************************************************
* Extract the necessary definitions for error message reporting
****************************************************************************/

#if !defined(HAVE_STRERROR)
# if defined(HAVE_SYS_NERR)
#   if !defined(HAVE_SYS_NERR_DEF)
     extern int sys_nerr;
#   endif
#   define num_errors    (sys_nerr)
# else
#       define num_errors    (-1)            /* always use "errno=%d" */
# endif
# if defined(HAVE_SYS_ERRLIST)
#  if !defined(HAVE_SYS_ERRLIST_DEF)
   extern const char *const sys_errlist[];
#  endif
# else
#  undef  num_errors
#  define num_errors   (-1)            /* always use "errno=%d" */
# endif
#endif

const char * Errormsg ( int err )
{
   const char *cp;

#if defined(HAVE_STRERROR)
       cp = strerror(err);
#else
# if defined(HAVE_SYS_ERRLIST)
   if (err >= 0 && err <= num_errors) {
               cp = sys_errlist[err];
   } else
# endif
       {
               static char msgbuf[32];     /* holds "errno=%d". */
               /* SAFE use of sprintf */
               (void) sprintf (msgbuf, "errno=%d", err);
               cp = msgbuf;
   }
#endif
   return (cp);
}

#ifdef HAVE_STDARGS
void log(char *msg, ...)
#else
void log( va_alist ) va_dcl
#endif
{
#ifndef HAVE_STDARGS
       char *msg;
#endif
       VA_LOCAL_DECL
       VA_START(msg);
       VA_SHIFT(msg, char *);
       (void)vfprintf(stderr, msg, ap);
       (void)fprintf(stderr, "\n" );
       VA_END;
       (void)fflush(stderr);
}

#ifdef HAVE_STDARGS
void fatal(char *msg, ...)
#else
void fatal( va_alist ) va_dcl
#endif
{
#ifndef HAVE_STDARGS
       char *msg;
#endif
       VA_LOCAL_DECL
       VA_START(msg);
       VA_SHIFT(msg, char *);
       (void)fprintf(stderr, "%s: ", name);
       (void)vfprintf(stderr, msg, ap);
       (void)fprintf(stderr, "\n" );
       VA_END;
       (void)fflush(stderr);
       cleanup();
       exit(errorcode);
}

#ifdef HAVE_STDARGS
void logerr(char *msg, ...)
#else
void logerr( va_alist ) va_dcl
#endif
{
#ifndef HAVE_STDARGS
       char *msg;
#endif
       int err = errno;
       VA_LOCAL_DECL
       VA_START(msg);
       VA_SHIFT(msg, char *);
       (void)fprintf(stderr, "%s: ", name);
       (void)vfprintf(stderr, msg, ap);
       (void)fprintf(stderr, "- %s\n", Errormsg(err) );
       VA_END;
       (void)fflush(stderr);
}

#ifdef HAVE_STDARGS
void logerr_die(char *msg, ...)
#else
void logerr_die( va_alist ) va_dcl
#endif
{
#ifndef HAVE_STDARGS
       char *msg;
#endif
       int err = errno;
       VA_LOCAL_DECL
       VA_START(msg);
       VA_SHIFT(msg, char *);
       (void)fprintf(stderr, "%s: ", name);
       (void)vfprintf(stderr, msg, ap);
       (void)fprintf(stderr, "- %s\n", Errormsg(err) );
       VA_END;
       (void)fflush(stderr);
       cleanup();
       exit(errorcode);
}

/*
*      doaccnt()
*      writes the accounting information to the accounting file
*  This has the format: user host printer pages format date
*/
void doaccnt()
{
       time_t t, time();
       char *ctime();
       FILE *f;

       t = time((time_t *)0);
       if(accntfile && (f = fopen(accntfile, "a" )) != NULL ) {
               fprintf(f,"%s\t%s\t%s\t%7d\t%s\t%s",
                       login? login: "NULL",
                       host? host: "NULL",
                       printer? printer: "NULL",
                       npages,
                       format? format: "NULL",
                       ctime(&t));
               (void)fclose(f);
       }
}

void getargs( int argc, char *argv[], char *envp[] )
{
       int i, c;               /* argument index */
       char *arg, *optarg;     /* argument */

       if( (name = argv[0]) == 0 ) name = "FILTER";
       for( i = 1; i < argc && (arg = argv[i])[0] == '-'; ++i ){
               if( (c = arg[1]) == 0 ){
                       fprintf( stderr, "missing option flag");
                       i = argc;
                       break;
               }
               if( c == 'c' ){
                       literal = 1;
                       continue;
               }
               if( arg[2] == 0 ){
                       optarg = argv[i++];
                       if( optarg == 0 || optarg[0] == '-' ){
                               fprintf( stderr, "missing option '%c' value", c );
                               i = argc;
                               break;
                       }
               }
               optarg = &arg[2];
               switch(c){
                       case 'C': class = optarg; break;
                       case 'E': errorfile = optarg; break;
                       case 'D': debug = atoi( optarg ); break;
                       case 'F': format = optarg; break;
                       case 'J': job = optarg; break;
                       case 'K': controlfile = optarg; break;
                       case 'L': bnrname = optarg; break;
                       case 'P': printer = optarg; break;
                       case 'Q': queuename = optarg; break;
                       case 'R': accntname = optarg; break;
                       case 'S': comment = optarg; break;
                       case 'Z': zopts = optarg; break;
                       case 'a': accntfile = optarg; break;
                       case 'b': bytecount = atoi(optarg); break;
                       case 'd': ctrldir = optarg; break;
                       case 'e': dfilename = optarg; break;
                       case 'f': filename = optarg; break;
                       case 'h': host = optarg; break;
                       case 'i': indent = atoi( optarg ); break;
                       case 'j': jobnum = atoi( optarg ); break;
                       case 'k': cffile = optarg; break;
                       case 'l': length = atoi( optarg ); break;
                       case 'n': login = optarg; break;
                       case 's': statusfile = optarg; break;
                       case 't': timestr = optarg; break;
                       case 'v': verbose = atoi( optarg ); break;
                       case 'w': width = atoi( optarg ); break;
                       case 'x': xwidth = atoi( optarg ); break;
                       case 'y': ylength = atoi( optarg ); break;
                       default: break;
               }
       }
       if( i < argc ){
               accntfile = argv[i];
       }
       if( errorfile ){
               int fd;
               fd = open( errorfile, O_APPEND | O_WRONLY );
               if( fd < 0 ){
                       fprintf( stderr, "cannot open error log file '%s'", errorfile );
               } else {
                       fprintf( stderr, "using error log file '%s'", errorfile );
                       if( fd != 2 ){
                               dup2(fd, 2 );
                               close(fd);
                       }
               }
       }
       if( verbose || debug ){
               fprintf(stderr, "%s command: ", name );
               for( i = 0; i < argc; ++i ){
                       fprintf(stderr, "%s ", argv[i] );
               }
               fprintf( stderr, "\n" );
               fflush(stderr);
       }
       if( debug ){
               fprintf(stderr, "FILTER decoded options: " );
               fprintf(stderr,"login '%s'\n", login? login : "null" );
               fprintf(stderr,"host '%s'\n", host? host : "null" );
               fprintf(stderr,"class '%s'\n", class? class : "null" );
               fprintf(stderr,"format '%s'\n", format? format : "null" );
               fprintf(stderr,"job '%s'\n", job? job : "null" );
               fprintf(stderr,"printer '%s'\n", printer? printer : "null" );
               fprintf(stderr,"queuename '%s'\n", queuename? queuename : "null" );
               fprintf(stderr,"accntname '%s'\n", accntname? accntname : "null" );
               fprintf(stderr,"zopts '%s'\n", zopts? zopts : "null" );
               fprintf(stderr,"literal, %d\n", literal);
               fprintf(stderr,"indent, %d\n", indent);
               fprintf(stderr,"length, %d\n", length);
               fprintf(stderr,"width, %d\n", width);
               fprintf(stderr,"xwidth, %d\n", xwidth);
               fprintf(stderr,"ylength, %d\n", ylength);
               fprintf(stderr,"accntfile '%s'\n", accntfile? accntfile : "null" );
               fprintf(stderr,"errorfile '%s'\n", errorfile? errorfile : "null" );
               fprintf(stderr,"statusfile '%s'\n", statusfile? statusfile : "null" );
               fprintf(stderr, "FILTER environment: " );
               for( i = 0; (arg = envp[i]); ++i ){
                       fprintf(stderr,"%s\n", arg );
               }
               fprintf(stderr, "RUID: %d, EUID: %d\n", (int)getuid(), (int)geteuid() );
               fflush(stderr);
       }
}

/*
* suspend():  suspends the output filter, waits for a signal
*/
void suspend()
{
       if(debug)fprintf(stderr,"FILTER suspending\n");
       fflush(stderr);
       kill(getpid(), SIGSTOP);
       if(debug)fprintf(stderr,"FILTER awake\n");
       fflush(stderr);
}
/******************************************
* prototype filter() and cleanup() functions;
* filter will scan the input looking for the suspend string
* if any.
******************************************/
void cleanup() {}

#ifdef DEBUG
void filter(char *stop)
{
       int c;
       int state, i;
       int lines = 0;

       /*
        * do whatever initializations are needed
        */
       /* fprintf(stderr, "filter ('%s')\n", stop ? stop : "NULL" ); */
       /*
        * now scan the input string, looking for the stop string
        */
       state = 0;
       npages = 1;

       while( (c = getchar()) != EOF ){
               if( c == '\n' ){
                       ++lines;
                       if( lines > length ){
                               lines -= length;
                               ++npages;
                       }
               }
               if( (stop || state) && c == stop[state] ){
                       ++state;
                       if( stop[state] == 0 ){
                               state = 0;
                               if( fflush(stdout) ){
                                       logerr( "fflush returned error" );
                                       break;
                               }
                               suspend();
                       }
               } else {
                       for( i = 0; i < state; ++i ){
                               putchar( stop[i] );
                       }
                       state = 0;
                       putchar( c );
               }
       }
       if( ferror( stdin ) ){
               logerr( "read error on stdin");
       }
       for( i = 0; i < state; ++i ){
               putchar( stop[i] );
       }
       if( lines > 0 ){
               ++npages;
       }
       if( fflush(stdout) ){
               logerr( "fflush returned error" );
       }
       doaccnt();
}

#endif /* DEBUG */