SUMMARY: postscript files sent to line printer

From: Martin Achilli (martin@gea.hsr.it)
Date: Fri Feb 10 1995 - 21:50:07 CST


ORIGINAL QUERY:
I have a plain dot matrix line printer connected to a Sparc 2 with SunOs 4.1.3. The users at my site are mainly doctors and are not very computer & Unix literate. "They" keep sending Postscript files to the line printer, instead of our Laserwriter. Has anyone written a filter or something to stop Ps files before they reach the line printer and perhaps (would be very nice) also automagically flame the user via mail or reroute the print job ? I don't know much about how you write filters & do this kind of st
uff.

SUMMARY: use a program called "smart_filter" by Michael Gordon

        I received 11 reponses, all telling me I needed to define an input filter in /etc/printcap and write it. I had a go at doing this but I kept getting form feeds and other types of mess, I felt like I was wasting more paper myself than those sending Ps files to the printer in the first place. I was quite amused by one of the suggestions, he thought that the postscript file should be sent back to the user as mail... :-)

The solution I adopted was the program by Michael Gordon of Edinburgh called "smart_filter". It is basically a printer filter, but you can configure different options for different printers, you can define which file formats your printer must accept amongst: ASCII-files, Ps-files, executables, PCL and unknown-binary. Sun raster files fall into the "unknown-binary" and are stopped before they reach the line printer. The program was easy to set up and sends mail back to the user, exactly what I need.

The program has not been extensively tested, and is supplied as is. I had no problems, but my set-up is quite simple. It is attached below as a shar shell-archive.

This is (my) sample printcap entry to use the filter. The filter itself must be placed (with the .conf file) in /usr/local/lib/lpfilter. You need a logical link to the smart_filter program for each printcap entry that uses smart_filter. The link must have the name smart_filter_<printername> and must point to smart_filter.

0|lp2|lp|ImageWriter LQ:\
        :lp=/dev/ttyb:sd=/var/spool/lp2d:br#9600:tr=\f:\
        :fs#06020:sh:pw=90:lf=/var/adm/lpd-errs:ms=ixon:mx#120:\
        :if=/usr/local/lib/lpfilter/smart_filter_lp:

#!/bin/sh
# shar: Shell Archiver
# Run the following text with /bin/sh to create:
# Makefile
# smart_filter.c
# smart_filter.conf
# smart_filter.man
# This archive created: Fri Feb 3 10:23:21 1995
cat << \SHAR_EOF > Makefile

PROG=smart_filter
CC=gcc # Either use gcc or add -D__svr4__ to CFLAGS for Solaris 2
CFLAGS=-Wall -O

BINDIR=/usr/local/lib/lpfilter
CONFDIR=/usr/local/lib/lpfilter
NAMES=smart_filter_ps0 smart_filter_lp0 smart_filter_lp1 smart_filter_lp2

###############################################################################

SF_CONFIG_FILE=${CONFDIR}/smart_filter.conf

$(PROG): ${PROG}.c
                ${CC} -o ${PROG} ${CFLAGS} -DSF_CONFIG_FILE=\"${SF_CONFIG_FILE}\" ${PROG}.c

install:
                cp ${PROG} ${BINDIR}
                cd ${BINDIR};for i in ${NAMES};do rm -r $$i;ln -s ${PROG} $$i;done
                #cp smart_filter.conf ${SF_CONFIG_FILE}

clean:
                rm -f ${PROG} *.o a.out core

realclean:
                rm -f ${PROG} *.o a,out core *~
SHAR_EOF
cat << \SHAR_EOF > smart_filter.c

/*
 * SMART_FILTER.C - M. F. GORDON 1994
 *
 */

#include <sys/types.h>
#ifndef __svr4__
#include <sys/exec.h>
#endif
#include <syslog.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

/* prototypes missing from SunOs 4.1 header files */
#ifndef __svr4__
void syslog(int,char *,...);
int strcasecmp(const char *,const char *);
pid_t wait(int *);
int fgetc(FILE *);
int ungetc(int,FILE *);
int fprintf(FILE *,const char *,...);
int fclose(FILE *);
int pclose(FILE *);
int getopt(int,char **,char *);
#endif

extern int optind;
extern char *optarg;

#ifndef SF_CONFIG_FILE
#define SF_CONFIG_FILE "/usr/local/lib/lpfilter/smart_filter.conf"
#endif

typedef enum {T_STRING,T_OPEN,T_CLOSE,T_EOF} TOKEN;

struct filter_info {
    char *file_type;
    char *filter_name;
};

struct printer_info {
    char *printer_name;
    int num_filters;
    struct filter_info *filters;
};

char *prog_name;

void *
emalloc(size_t sz)
{
    void *p;
    
    if ((p=malloc(sz))==NULL) {
        syslog(LOG_ERR|LOG_LPR,"%s:Out of memory\n",prog_name);
        exit(1);
    }
    return(p);
}

void *
erealloc(void *p,size_t sz)
{
    if ((p=realloc(p,sz))==NULL) {
        syslog(LOG_ERR|LOG_LPR,"%s:Out of memory\n",prog_name);
        exit(1);
    }
    return(p);
}

char *
estrdup(char *s)
{
    if ((s=strdup(s))==NULL) {
        syslog(LOG_ERR|LOG_LPR,"%s:Out of memory\n",prog_name);
        exit(1);
    }
    return(s);
}

TOKEN
get_token(FILE *fp,char *buf,size_t sz)
{
    int c;
    int tlen;

    while (1) {
        while ((isspace(c=fgetc(fp))))
            /* skip */;
        ungetc(c,fp);
        
        if ((c=fgetc(fp))==EOF) {
            return(T_EOF);
        } else if (c=='#') {
            while ((c=fgetc(fp))!='\n' && c!=EOF) {
                /* skip */;
            }
            continue;
        } else if (c=='{') {
            return(T_OPEN);
        } else if (c=='}') {
            return(T_CLOSE);
        } else {
            tlen=0;
            buf[tlen]=c;
            tlen++;
            while (!isspace(c=fgetc(fp)) && tlen<sz-1) {
                buf[tlen]=c;
                tlen++;
            }
            buf[tlen]='\0';
            return(T_STRING);
        }
    }
}

int
read_config_file(struct printer_info **infop)
{
    TOKEN tt;
    char token[80];
    FILE *cf;
    struct printer_info *pi;
    struct filter_info *fi;
    int num_printers,max_printers,num_filters,max_filters;
    
    num_printers=0;max_printers=20;
    pi=emalloc(max_printers*sizeof(struct printer_info));
    
    if ((cf=fopen(SF_CONFIG_FILE,"r"))==NULL) {
        syslog(LOG_ERR|LOG_LPR,"%s:cannot open %s:%m\n",
               prog_name,SF_CONFIG_FILE);
        exit(0);
    }
    
    while ((tt=get_token(cf,token,sizeof(token)))!=T_EOF) {
        if (tt!=T_STRING) {
            syslog(LOG_ERR|LOG_LPR,
                   "%s:syntax error in %s:printer name expected\n",
                   prog_name,SF_CONFIG_FILE);
            exit(0);
        }
        pi[num_printers].printer_name=estrdup(token);
        if ((tt=get_token(cf,token,sizeof(token)))!=T_OPEN) {
            syslog(LOG_ERR|LOG_LPR,
                   "%s:syntax error in %s:{ expected\n",
                   prog_name,SF_CONFIG_FILE);
            exit(0);
        }
        num_filters=0;
        max_filters=20;
        fi=emalloc(max_filters*sizeof(struct filter_info));
        while ((tt=get_token(cf,token,sizeof(token)))==T_STRING) {
            fi[num_filters].file_type=estrdup(token);
            if ((tt=get_token(cf,token,sizeof(token)))!=T_STRING) {
                syslog(LOG_ERR|LOG_LPR,
                       "%s:syntax error in %s:filter name expected",
                       prog_name,SF_CONFIG_FILE);
                exit(0);
            }
            fi[num_filters].filter_name=estrdup(token);
            num_filters++;
            if (num_filters==max_filters) {
                max_filters*=2;
                fi=erealloc(fi,max_filters*sizeof(struct filter_info));
            }
        }
        pi[num_printers].filters=fi;
        pi[num_printers].num_filters=num_filters;
        if (tt!=T_CLOSE) {
            syslog(LOG_ERR|LOG_LPR,
                   "%s:syntax error in %s:} expected\n",
                   prog_name,SF_CONFIG_FILE);
            exit(0);
        }
        num_printers++;
        if (num_printers==max_printers) {
            max_printers*=2;
            pi=erealloc(pi,max_printers*sizeof(struct printer_info));
        }
    }
    
    *infop=pi;
    return(num_printers);
}
          

char *
find_file_type(char *buf,size_t sz,int *nrp)
{
    int i,binary_found;
#ifndef __svr4__
    struct exec *execp;
#endif
    
    *nrp=read(0,buf,sz);
#ifndef __svr4__
    execp=(struct exec *)buf;
#endif
    
    if (strncmp(buf,"%!",2)==0 || strncmp(buf,"\004%!",3)==0) {
        return("PostScript");
    } else if (strstr(buf,"\033E\033")!=NULL ||
               strstr(buf,"\033&")!=NULL ||
               strstr(buf,"\033Z")!=NULL) {
        return("PCL");
    } else if (buf[0]==0177 && strncmp(buf+1,"ELF",3)==0) {
        return("Executable");
#ifndef __svr4__
    } else if (execp->a_magic==ZMAGIC ||
               execp->a_magic==NMAGIC ||
               execp->a_magic==OMAGIC) {
        return("Executable");
#endif
    } else {
        binary_found=0;
        for (i=0;i<*nrp;i++) {
            if (!isascii(buf[i])) {
                binary_found++;
                break;
            }
        }
        if (binary_found) {
            return("Unknown-binary");
        } else {
            return("ASCII");
        }
    }
}

char *
find_filter(struct printer_info *pi,int num_printers,
            char *printer_name,char *file_type)
{
    struct filter_info *fi;
    int i,j;
    
    for (i=0;i<num_printers;i++) {
        if (strcasecmp(pi[i].printer_name,printer_name)==0) {
            fi=pi[i].filters;
            for (j=0;j<pi[i].num_filters;j++) {
                if (strcasecmp(fi[j].file_type,file_type)==0 ||
                    strcmp(fi[j].file_type,"*")==0) {
                    return(fi[j].filter_name);
                }
            }
        }
    }
    
    return(NULL);
}

void
run_filter(char *filter_name,char **argv,char *ident,int ilen)
{
    int pipe_fds[2];
    pid_t pid;
    char buf[BUFSIZ];
    int nr;
    
    argv[0]=filter_name;
    
    if (pipe(pipe_fds)==-1) {
        syslog(LOG_ERR|LOG_LPR,"%s:Can't make pipe:%m\n",prog_name);
        exit(0);
    }
    
    switch (pid=fork()) {
      case -1:
        syslog(LOG_ERR|LOG_LPR,"%s:Can't fork:%m\n",prog_name);
        exit(0);
        
      case 0:
        dup2(pipe_fds[0],0);
        close(pipe_fds[0]);
        close(pipe_fds[1]);
        execv(filter_name,argv);
        syslog(LOG_ERR|LOG_LPR,"%s:Can't exec %s:%m\n",prog_name,filter_name);
        exit(0);
        
      default:
        close(pipe_fds[0]);
        write(pipe_fds[1],ident,ilen);
        while ((nr=read(0,buf,sizeof(buf)))>0) {
            write(pipe_fds[1],buf,nr);
        }
        close(pipe_fds[1]);
        wait(NULL);
        break;
    }
}

void
make_accounting_file_entry(char *acct,char *user,char *host,int pages,
                           char *file_type,int file_size)
{
    FILE *fp;
    
    if ((fp=fopen(acct,"a"))==NULL) {
        syslog(LOG_ERR|LOG_LPR,"%s:Can't open %s:%m\n",prog_name,acct);
        exit(0);
    }
    
    fprintf(fp,"\t%d.00\t%s:%s",pages,host,user);
    if (pages==0) {
        fprintf(fp,"\t%s\t%d",file_type,file_size);
    }
    fprintf(fp,"\n");
    fclose(fp);
}

void
send_mail_to_user(char *user,char *file_type,char *printer_name)
{
    FILE *fp;
    char cmd[80];
    
    sprintf(cmd,"/bin/mail %s",user);
    if ((fp=popen(cmd,"w"))==NULL) {
        syslog(LOG_ERR|LOG_LPR,"%s:Can't open pipe to mail:%m\n",prog_name);
        exit(0);
    }
    
    fprintf(fp,"Subject: Printer couldn't handle your file\n\n");
    fprintf(fp,"You sent a %s file to the printer %s.\n",file_type,printer_name);
    if (strcmp(file_type,"Executable")==0) {
        fprintf(fp,"Maybe you meant to send the source.\n\n");
    } else {
        fprintf(fp,"This printer can't cope with this type of file.\n\n");
        fprintf(fp,"Please convert it to something the printer can\n");
        fprintf(fp,"understand, or send it to a different printer\n\n");
    }
    fprintf(fp,"The printer filter\n");
    
    pclose(fp);
}

int
main(int argc,char **argv)
{
    struct printer_info *pi;
    int num_printers;
    char *printer_name;
    char *file_type,*filter_name;
    char ident[80];
    int ilen;
    char buf[BUFSIZ];
    int file_size,nr;
    char *user,*host,*acct;
    int c;
    
    if ((prog_name=strrchr(argv[0],'/'))==NULL) {
        prog_name=argv[0];
    } else {
        prog_name++;
    }
    if ((printer_name=strrchr(prog_name,'_'))==NULL) {
        syslog(LOG_ERR|LOG_LPR,"%s:Can't work out which printer I'm using - check name I'm run with\n",prog_name);
        printer_name="unknown";
    } else {
        printer_name++;
    }
    
    user=host="UNKNOWN";
    acct=NULL;
    while ((c=getopt(argc,argv,"ch:i:l:n:w:"))!=-1) {
        switch (c) {
          case 'h':
            host=optarg;
            break;
            
          case 'n':
            user=optarg;
            
          case 'c':
          case 'i':
          case 'l':
          case 'w':
            break;
            
          default:
            syslog(LOG_ERR|LOG_LPR,"%s: called with unknown argument '%c'\n",
                   prog_name,c);
            exit(1);
        }
    }
    
    acct=argv[optind];
    
    num_printers=read_config_file(&pi);
    
    file_type=find_file_type(ident,sizeof(ident),&ilen);
    filter_name=find_filter(pi,num_printers,printer_name,file_type);
    
    if (filter_name==NULL || strcasecmp(filter_name,"DISCARD")==0) {
        syslog(LOG_ERR|LOG_LPR,"%s:%s file sent to %s by %s on %s\n",
               prog_name,file_type,printer_name,user,host);
        send_mail_to_user(user,file_type,printer_name);
    } else if (strcasecmp(filter_name,"NONE")==0) {
        file_size=ilen;
        write(1,ident,ilen);
        while ((nr=read(0,buf,sizeof(buf)))>0) {
            file_size+=nr;
            write(1,buf,nr);
        }
        if (acct!=NULL) {
            make_accounting_file_entry(acct,user,host,0,file_type,file_size);
        }
    } else {
        run_filter(filter_name,argv,ident,ilen);
    }

    return(0);
}

            
SHAR_EOF
cat << \SHAR_EOF > smart_filter.conf
#
# This is the configuration file for smart_filter. It consists of records
# of the form
#
# printer_name {
# file_type filter_name
# ...
# ...
# }
#
# If we're printing a file of type file_type to printer printer_name
# it is fed to the filter filter_name. The file_type * matches any
# file type. There are two special filter names, DISCARD (don't print
# anything and mail the user a message) and NONE (copy the file straight
# to stdout). If no matching file_type is found for a printer, or there
# is no record for the printer, the file is discarded.
#
# File types currently recognised are ASCII, Executable, PCL, PostScript
# and Unknown-binary. Comparison of printer names and file types is
# not case-sensitive.
#
# White space is ignored, so the layout of the records is unimportant
# (as long as there is space between the printer_name, the opening and
# closing braces, the file_type and the filter_name). Lines starting
# with # are comments.

ps0 {
        PostScript /usr/local/lib/lpfilter/psfilt
        PCL NONE
        * DISCARD
}

ps1 {
        PostScript /usr/local/lib/lpfilter/psfilt
        * DISCARD
}

psc8 {
        PostScript /usr/local/lib/lpfilter/psfilt
        * DISCARD
}

ps12 {
        PostScript /usr/local/lib/lpfilter/psfilt
        PCL NONE
        * DISCARD
}

ps13 {
        PostScript /usr/local/lib/lpfilter/psfilt
        PCL NONE
        * DISCARD
}

ps14 {
        PostScript /usr/local/lib/lpfilter/psfilt
        PCL NONE
        * DISCARD
}

lj4 {
        PCL NONE
        ASCII /usr/local/lib/lpfilter/dlj+
}

lp0 {
        ASCII /usr/local/lib/lpfilter/lpf_la210
        * DISCARD
}

lp1 {
        ASCII /usr/local/lib/lpfilter/lpf_la210
        * DISCARD
}

lp2 {
        ASCII /usr/local/lib/lpfilter/lpf_la210
        * DISCARD
}
SHAR_EOF
cat << \SHAR_EOF > smart_filter.man
.TH "SMART_FILTER" "8L" "9th November 1994" "ee.ed.ac.uk" "LOCAL commands"
.SH NAME
smart_filter \- auto-detecting printer filter
.SH DESCRIPTION
.B Smart_filter
is a printer filter which identifies the type of file being printed
and starts an appropriate filter to handle it.
If no filter is available for a given file type the file is discarded
and a message mailed to the user who submitted the file.
.LP
The filter to use for a given file type is read from a configuration
file with an entry for each printer.
The format of entries is
.sp 1
.RS
.I printer_name
{
.RS
.I "file_type filter_name"
.br
.I "file_type filter_name"
.br
\&...
.RE
}
.RE
.LP
The currently recognised file types are
.I "PostScript, PCL, ASCII, Executable"
and
.I Unknown-binary.
If the file type is * it matches any input file.
.LP
There are two special filter names,
.I NONE
and
.I DISCARD.
The
.I NONE
filter simply copies the input file to stdout.
The
.I DISCARD
filter discards the file and sends the user who submitted the file a
mail message explaining why.
.I DISCARD
is the default action if no entry can be found for a given file type.
.SH NOTES
.B Smart_filter
uses the name it was invoked under to work out the printer name.
Everything after the last undersore in the name is considered to be
the name of the printer.
.SH FILES
.I /usr/local/lib/lpfilter/smart_filter.conf
\- configuration file
.SH AUTHOR
support@ee.ed.ac.uk : MFG
SHAR_EOF
# End of shell archive
exit 0



This archive was generated by hypermail 2.1.2 : Fri Sep 28 2001 - 23:10:16 CDT