1/*
2 * $Id: psf.c,v 1.13 2009-10-16 01:50:50 didg Exp $
3 *
4 * Copyright (c) 1990,1995 Regents of The University of Michigan.
5 * All Rights Reserved. See COPYRIGHT.
6 *
7 * PostScript Filter, psf.
8 *
9 * Handles both PostScript files and text files.  Files with the
10 * '%!' PostScript header are sent directly to the printer,
11 * unmodified.  Text files are first converted to PostScript,
12 * then sent.  Printers may be directly attached or on an AppleTalk
13 * network.  Other media are possible.  Currently, psf invokes
14 * pap to send files to AppleTalk-ed printers.  Replace the pap*
15 * variables to use another program for communication.  psf only
16 * converts plain-text.  If called as "tf" or "df", psf will invoke
17 * a troff or dvi to PostScript converter.
18 */
19
20#ifdef HAVE_CONFIG_H
21#include "config.h"
22#endif /* HAVE_CONFIG_H */
23
24#define FUCKED
25
26#ifdef HAVE_UNISTD_H
27#include <unistd.h>
28#endif /* HAVE_UNISTD_H */
29#include <sys/time.h>
30
31/* POSIX.1 sys/wait.h check */
32#include <sys/types.h>
33#ifdef HAVE_SYS_WAIT_H
34#include <sys/wait.h>
35#endif /* HAVE_SYS_WAIT_H */
36#ifndef WEXITSTATUS
37#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
38#endif /* ! WEXITSTATUS */
39#ifndef WIFEXITED
40#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
41#endif /* ! WIFEXITED */
42
43#include <sys/file.h>
44#include <syslog.h>
45#include <atalk/paths.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <ctype.h>
50#include <signal.h>
51#include <errno.h>
52
53/* Forward Declarations */
54int pexecv(char *path, char *argv[]);
55int copyio();
56int textps();
57
58static char		psapath[] = _PATH_PSA;
59static char		*psaargv[] = { "psa", NULL, NULL, NULL, NULL };
60
61/*
62 * If we're not doing accounting, we just call pap as below.
63 * If we are doing accounting, we call pap twice.  The first time,
64 * we call it with "-E" in arg 2, pagecount.ps in arg 3, and "-" in
65 * arg 4.  The second time, we call it with "-c" in arg 2, pagecount.ps
66 * in arg 3, and 0 in arg 4.
67 */
68static char		pappath[] = _PATH_PAP;
69static char		*papargv[] = { "pap", "-sstatus", NULL, NULL, NULL, NULL, NULL, NULL };
70
71static char		revpath[] = _PATH_PSORDER;
72static char		*revargv[] = { "psorder", "-d", NULL };
73
74static char		*filtargv[] = { NULL, NULL, NULL };
75
76static char		inbuf[ 1024 * 8 ];
77static int		inlen;
78
79static int		literal;
80static int		width = 80, length = 66, indent = 0;
81static char		*prog, *name, *host;
82
83static struct papersize {
84    int		width;
85    int		length;
86   float	win;
87    float	lin;
88} papersizes[] = {
89    { 80, 66, 8.5, 11.0 },			/* US Letter */
90    { 80, 70, 8.27, 11.69 },			/* A4 */
91};
92
93int main( int ac, char **av)
94{
95    int			c, rc, children = 0;
96#ifdef FUCKED
97    int			psafileno = 0, multiconn = 0, waitidle = 0, waitidle2 = 0;
98#endif /* FUCKED */
99    int			status;
100    extern char		*optarg;
101    extern int		optind, opterr;
102
103    opterr = 0;
104    if (( prog = rindex( av[ 0 ], '/' )) == NULL ) {
105	prog = av[ 0 ];
106    } else {
107	prog++;
108    }
109#ifdef ultrix
110    openlog( prog, LOG_PID );
111#else /* ultrix */
112    openlog( prog, LOG_PID, LOG_LPR );
113#endif /* ultrix */
114
115    while (( c = getopt( ac, av, "P:C:D:F:L:J:x:y:n:h:w:l:i:c" )) != EOF ) {
116	switch ( c ) {
117	case 'n' :
118	    name = optarg;
119	    break;
120
121	case 'h' :
122	    host = optarg;
123	    break;
124
125	case 'w' :
126	    width = atoi( optarg );
127#ifdef ZEROWIDTH
128	    /*
129	     * Some version of lpd pass 0 for the page width.
130	     */
131	    if ( width == 0 ) {
132		width = 80;
133	    }
134#endif /* ZEROWIDTH */
135	    break;
136
137	case 'l' :
138	    length = atoi( optarg );
139	    break;
140
141	case 'i' :
142	    indent = atoi( optarg );
143	    break;
144
145	case 'c' :	/* Print control chars */
146	    literal++;
147	    break;
148
149	case 'F' :
150	case 'L' :
151	case 'J' :
152	case 'P' :
153	case 'x' :
154	case 'y' :
155	    break;
156
157#ifdef notdef
158	default :
159	    syslog( LOG_ERR, "bad option: %c", c );
160	    exit( 2 );
161#endif /* notdef */
162	}
163    }
164    if ( ac - optind > 1 ) {
165	syslog( LOG_ERR, "Too many arguments" );
166	exit( 2 );
167    }
168#ifdef FUCKED
169    if ( index( prog, 'w' )) {
170	waitidle++;
171    }
172    if ( index( prog, 'W' )) {
173	waitidle2++;
174    }
175    if ( index( prog, 'm' )) {
176	multiconn++;
177    }
178#endif /* FUCKED */
179
180    syslog( LOG_INFO, "starting for %s", name ? name : "?" );
181
182restart:
183    if (( inlen = read( 0, inbuf, sizeof( inbuf ))) < 0 ) {
184	syslog( LOG_ERR, "read: %s", strerror(errno) );
185	exit( 1 );
186    }
187    if ( inlen == 0 ) {	/* nothing to be done */
188	syslog( LOG_INFO, "done" );
189	exit( 0 );
190    }
191
192    /*
193     * If we've been given an accounting file, start the accounting
194     * process.
195     */
196    if ( optind < ac ) {
197	/* build arguments */
198	psaargv[ 1 ] = av[ optind ];
199	psaargv[ 2 ] = name;
200	psaargv[ 3 ] = host;
201	if (( c = pexecv( psapath, psaargv )) < 0 ) {
202	    syslog( LOG_ERR, "%s: %s", psapath, strerror(errno) );
203	    exit( 2 );
204	}
205	children++;
206	syslog( LOG_INFO, "accounting with psa[%d]", c );
207    }
208
209    /*
210     * Check prog's name to decide what programs to execute.
211     */
212    if ( strstr( prog, "pap" ) != NULL ) {
213	if ( optind < ac ) {	/* accounting */
214#ifdef FUCKED
215	    if ( multiconn ) {
216		psafileno = getdtablesize();
217		psafileno--;
218		dup2( 1, psafileno );
219
220		if ( waitidle2 ) {
221		    papargv[ 2 ] = "-W";
222		    papargv[ 3 ] = "-c";
223		    papargv[ 4 ] = "-E";
224		    papargv[ 5 ] = _PATH_PAGECOUNT;
225		    papargv[ 6 ] = "-";
226		    papargv[ 7 ] = NULL;
227		} else if ( waitidle ) {
228		    papargv[ 2 ] = "-w";
229		    papargv[ 3 ] = "-c";
230		    papargv[ 4 ] = "-E";
231		    papargv[ 5 ] = _PATH_PAGECOUNT;
232		    papargv[ 6 ] = "-";
233		    papargv[ 7 ] = NULL;
234		} else {
235		    papargv[ 2 ] = "-c";
236		    papargv[ 3 ] = "-E";
237		    papargv[ 4 ] = _PATH_PAGECOUNT;
238		    papargv[ 5 ] = "-";
239		    papargv[ 6 ] = NULL;
240		}
241	    } else {
242		/*
243		 * This is how it should be done.
244		 */
245		papargv[ 2 ] = "-c";
246		papargv[ 3 ] = _PATH_PAGECOUNT;
247		papargv[ 4 ] = "-";
248		papargv[ 5 ] = _PATH_PAGECOUNT;
249		papargv[ 6 ] = NULL;
250	    }
251#endif /* FUCKED */
252	} else {
253	    papargv[ 2 ] = "-c";
254	    papargv[ 3 ] = "-E";
255	    papargv[ 4 ] = NULL;
256	}
257
258	if (( c = pexecv( pappath, papargv )) < 0 ) {
259	    syslog( LOG_ERR, "%s: %s", pappath, strerror(errno) );
260	    exit( 2 );
261	}
262	children++;
263	syslog( LOG_INFO, "sending to pap[%d]", c );
264    }
265
266    /*
267     * Might be a good idea to have both a "forw" and a "rev", so that
268     * reversed documents can be reordered for the printing device.
269     */
270    if ( strstr( prog, "rev" ) != NULL ) {
271	if (( c = pexecv( revpath, revargv )) < 0 ) {
272	    syslog( LOG_ERR, "%s: %s", revpath, strerror(errno) );
273	    exit( 2 );
274	}
275	syslog( LOG_INFO, "sending to rev[%d]", c );
276	children++;
277    }
278
279    /*
280     * Invoke an external (script) filter to produce PostScript from
281     * non-text input.
282     */
283    if ( *prog != 'i' && *prog != 'o' && *( prog + 1 ) == 'f' ) {
284	filtargv[ 0 ] = filtargv[ 1 ] = prog;
285	if (( c = pexecv( _PATH_PSFILTER, filtargv )) < 0 ) {
286	    syslog( LOG_ERR, "%s: %s", _PATH_PSFILTER, strerror(errno) );
287	    exit( 2 );
288	}
289	syslog( LOG_INFO, "external filter[%d]", c );
290	children++;
291	rc = copyio();		/* external filter */
292    } else {
293	if ( inlen >= 2 && inbuf[ 0 ] == '%' && inbuf[ 1 ] == '!' ) {
294	    syslog( LOG_INFO, "PostScript" );
295	    rc = copyio();	/* PostScript */
296	} else if ( inlen >= 2 && inbuf[ 0 ] == '\033' && inbuf[ 1 ] == '%' ) {
297	    syslog( LOG_INFO, "PostScript w/PJL" );
298	    rc = copyio();      /* PostScript */
299	} else {
300	    syslog( LOG_INFO, "straight text" );
301	    rc = textps();	/* straight text */
302	}
303    }
304
305#ifdef FUCKED
306    if ( strstr( prog, "pap" ) != NULL && optind < ac && multiconn ) {
307	dup2( psafileno, 1 );
308	close( psafileno );
309	papargv[ 2 ] = "-c";
310	if ( waitidle2 ) {
311	    papargv[ 3 ] = "-W";
312	    papargv[ 4 ] = _PATH_PAGECOUNT;
313	    papargv[ 5 ] = NULL;
314	} else if ( waitidle ) {
315	    papargv[ 3 ] = "-w";
316	    papargv[ 4 ] = _PATH_PAGECOUNT;
317	    papargv[ 5 ] = NULL;
318	} else {
319	    papargv[ 3 ] = _PATH_PAGECOUNT;
320	    papargv[ 4 ] = NULL;
321	}
322
323	if (( c = pexecv( pappath, papargv )) < 0 ) {
324	    syslog( LOG_ERR, "%s: %s", pappath, strerror(errno) );
325	    exit( 2 );
326	}
327	children++;
328	syslog( LOG_INFO, "pagecount with pap[%d]", c );
329    }
330#endif /* FUCKED */
331
332    if ( children ) {
333	close( 1 );
334    }
335    while ( children ) {
336	if (( c = wait3( &status, 0, NULL )) < 0 ) {
337	    syslog( LOG_ERR, "wait3: %s", strerror(errno) );
338	    exit( 1 );
339	}
340	if ( WIFEXITED( status )) {
341#ifndef WEXITSTATUS
342#define WEXITSTATUS(x)	((x).w_status)
343#endif /* WEXITSTATUS */
344	    if ( WEXITSTATUS( status ) != 0 ) {
345		syslog( LOG_ERR, "%d died with %d", c, WEXITSTATUS( status ));
346		exit( WEXITSTATUS( status ));
347	    } else {
348		syslog( LOG_INFO, "%d done", c );
349		children--;
350	    }
351	} else {
352	    syslog( LOG_ERR, "%d died badly", c );
353	    exit( 1 );
354	}
355    }
356
357    if ( rc == 3 ) {
358	syslog( LOG_INFO, "pausing" );
359	kill( getpid(), SIGSTOP );
360	syslog( LOG_INFO, "restarting" );
361	goto restart;
362    }
363
364    syslog( LOG_INFO, "done" );
365    exit( rc );
366}
367
368int copyio(void)
369{
370    /* implement the FSM needed to do the suspend. Note that
371     * the last characters will be \031\001 so don't worry
372     * Fun things: 1. \031\001 should not be written to output device
373     *  2. The \031 can be last char of one read, \001 first of next
374     *      - we need to write \031 if not followed by \001
375     */
376    struct timeval	tv;
377    fd_set		fdset;
378    int			ctl = 0;
379
380notdone:
381    do {
382	/*
383	 * First, \031 and \001 *must* be the last things in the buffer
384	 * (\001 can be the first thing in the next buffer).  There's no
385	 * need to scan any of the intervening bytes.  Second, if there's
386	 * more input, the escape sequence was bogus, and we should keep
387	 * reading.
388	 */
389	if ( inlen == 1 ) {
390	    if ( ctl == 1 ) {
391		if ( inbuf[ 0 ] == '\001' ) {
392		    ctl = 2;
393		    break;
394		}
395		if ( write( 1, "\031", 1 ) != 1 ) {
396		    syslog( LOG_ERR, "write: %s", strerror(errno) );
397		    return( 1 );
398		}
399		ctl = 0;
400	    }
401
402	    if ( inbuf[ 0 ] == '\031' ) {
403		ctl = 1;
404	    }
405
406	} else {
407	    if ( ctl == 1 ) {
408		if ( write( 1, "\031", 1 ) != 1 ) {
409		    syslog( LOG_ERR, "write: %s", strerror(errno) );
410		    return( 1 );
411		}
412	    }
413	    ctl = 0;
414	    if ( inbuf[ inlen - 2 ] == '\031' &&
415		    inbuf[ inlen - 1 ] == '\001' ) {
416		ctl = 2;
417	    } else if ( inbuf[ inlen - 1 ] == '\031' ) {
418		ctl = 1;
419	    }
420	}
421
422	inlen -= ctl;
423	if (( inlen > 0 ) && ( write( 1, inbuf, inlen ) != inlen )) {
424	    syslog( LOG_ERR, "write: %s", strerror(errno) );
425	    return( 1 );
426	}
427	if ( ctl == 2 ) {
428	    break;
429	}
430    } while (( inlen = read( 0, inbuf, sizeof( inbuf ))) > 0 );
431
432    if ( ctl == 2 ) {
433	tv.tv_sec = 2;
434	tv.tv_usec = 0;
435	FD_ZERO( &fdset );
436	FD_SET( 0, &fdset );
437	if ( select( 1, &fdset, NULL, NULL, &tv ) != 0 ) {
438	    if (( inlen = read( 0, inbuf, sizeof( inbuf ))) > 0 ) {
439		goto notdone;
440	    }
441	}
442    }
443
444    if ( inlen < 0 ) {
445	syslog( LOG_ERR, "read: %s", strerror(errno) );
446	return( 1 );
447    }
448
449    if ( ctl == 1 ) {
450	if ( write( 1, "\031", 1 ) != 1 ) {
451	    syslog( LOG_ERR, "write: %s", strerror(errno) );
452	    return( 1 );
453	}
454    } else if ( ctl == 2 ) {
455	return( 3 );
456    }
457    return( 0 );
458}
459
460static char		*font = "Courier";
461static int		point = 11;
462
463static char		pspro[] = "\
464/GSV save def						% global VM\n\
465/SP {\n\
466	/SV save def					% save vmstate\n\
467	dup /H exch def					% save font height\n\
468	exch findfont exch scalefont setfont		% select font\n\
469	( ) stringwidth pop /W exch def			% save font width\n\
470	0.5 sub 72 mul /CY exch def			% save start Y\n\
471	pop 0.5 add 72 mul /CX exch def			% save start X\n\
472	CX CY moveto					% make current point\n\
473} bind def\n\
474/S /show load def\n\
475/NL { CX CY H sub dup /CY exch def moveto } bind def\n\
476/CR { CX CY moveto } bind def\n\
477/B { W neg 0 rmoveto}bind def\n\
478/T { W mul 0 rmoveto}bind def\n\
479/EP { SV restore showpage } bind def\n\
480%%EndProlog\n";
481
482int textps(void)
483{
484    struct papersize	papersize;
485    int			state = 0, line = 0, col = 0, npages = 0, rc;
486    unsigned int	i;
487    char		*p, *end;
488
489#define elements(x)	(sizeof(x)/sizeof((x)[0]))
490    for ( i = 0; i < elements( papersizes ); i++ ) {
491	if ( width == papersizes[ 0 ].width &&
492		length == papersizes[ 0 ].length ) {
493	    papersize = papersizes[ i ];
494	    break;
495	}
496    }
497    if ( i >= elements( papersizes )) {
498	papersize = papersizes[ 0 ];		/* default */
499    }
500
501#define ST_AVAIL		(1<<0)
502#define ST_CONTROL		(1<<1)
503#define ST_PAGE			(1<<2)
504    /*
505     * convert text lines to postscript.
506     * A grungy little state machine. If I was more creative, I could
507     * probably think of a better way of doing this...
508     */
509    do {
510	p = inbuf;
511	end = inbuf + inlen;
512	while ( p < end ) {
513	    if (( state & ST_PAGE ) == 0 && *p != '\031' && *p != '\001' ) {
514		if ( npages == 0 ) {
515		    printf( "%%!PS-Adobe-2.0\n%%%%Pages: (atend)\n" );
516		    printf( "%%%%DocumentFonts: %s\n", font );
517		    fflush( stdout );
518
519		    /* output postscript prologue: */
520		    if ( write( 1, pspro, sizeof( pspro ) - 1 ) !=
521			    sizeof( pspro ) - 1 ) {
522			syslog( LOG_ERR, "write prologue: %s", strerror(errno) );
523			return( 1 );
524		    }
525		    if ( name && host ) {
526			printf( "statusdict /jobname (%s@%s) put\n", name,
527				host );
528		    }
529		}
530
531		printf( "%%%%Page: ? %d\n", ++npages );
532		printf( "%d %f %f /%s %d SP\n", indent,
533			papersize.win, papersize.lin, font, point );
534		state |= ST_PAGE;
535	    }
536	    if ( state & ST_CONTROL && *p != '\001' ) {
537		/* It is a very bad thing to toss a job because it contains
538		 * unprintable characters.  Instead, we will convert them to
539		 * question marks.  This is adapted from a solution described
540		 * by Werner Eugster <eugster@giub.unibe.ch> on his ApplePrint
541		 * webpage (http://www.giub.unibe.ch/~eugster/appleprint.html).
542		 *
543		 * Note that this is rather ugly code.  The same change is
544		 * applied identically at two different locations in this file.
545		 * It would be better someday to combine the two.
546                */
547		if ( !literal ) {
548			fprintf( stderr,
549				"unprintable character (0x%x) converted to ?!\n",
550				(unsigned char)*p );
551			putchar( '?' ); /* Replace unprintable char with a question mark. */
552		} else {
553			printf( "\\%o", (unsigned char)031 );
554		}
555		state &= ~ST_CONTROL;
556		col++;
557	    }
558
559	    switch ( *p ) {
560	    case '\n' :		/* end of line */
561		if ( state & ST_AVAIL ) {
562		    printf( ")S\n" );
563		    state &= ~ST_AVAIL;
564		}
565		printf( "NL\n" );
566		line++;
567		col = 0;
568		if ( line >= length ) {
569		    printf( "EP\n" );
570		    state &= ~ST_PAGE;
571		    line = 0;
572		}
573		break;
574
575	    case '\r' :		/* carriage return (for overtyping) */
576		if ( state & ST_AVAIL ) {
577		    printf( ")S CR\n" );
578		    state &= ~ST_AVAIL;
579		}
580		col = 0;
581		break;
582
583	    case '\f' :		/* form feed */
584		if ( state & ST_AVAIL ) {
585		    printf( ")S\n" );
586		    state &= ~ST_AVAIL;
587		}
588		printf( "EP\n" );
589		state &= ~ST_PAGE;
590		line = 0;
591		col = 0;
592		break;
593
594	    case '\b' :		/* backspace */
595		/* show line, back up one character */
596		if ( state & ST_AVAIL ) {
597		    printf( ")S\n" );
598		    state &= ~ST_AVAIL;
599		}
600		printf( "B\n" );
601		col--;
602		break;
603
604	    case '\t' :		/* tab */
605		if ( state & ST_AVAIL ) {
606		    printf( ")S\n" );
607		    state &= ~ST_AVAIL;
608		}
609		printf( "%d T\n", 8 - ( col % 8 ));
610		col += 8 - ( col % 8 );
611		break;
612
613	    /*
614	     * beginning of lpr control sequence
615	     */
616	    case '\031' :
617		state |= ST_CONTROL;
618		break;
619
620	    case '\001' :	/* lpr control sequence */
621		if ( state & ST_CONTROL ) {
622		    rc = 3;
623		    goto out;
624		}
625		/* FALLTHROUGH */
626
627	    case '\\' :
628	    case ')' :
629	    case '(' :
630		if (( state & ST_AVAIL ) == 0 ) {
631		    printf( "(" );
632		    state |= ST_AVAIL;
633		}
634		putchar( '\\' );
635		/* FALLTHROUGH */
636
637	    default :
638		if (( state & ST_AVAIL ) == 0 ) {
639		    printf( "(" );
640		    state |= ST_AVAIL;
641		}
642		if ( !isascii( *p ) || !isprint( *p )) {
643		    if ( !literal ) {
644                        fprintf( stderr,
645			    "unprintable character (0x%x) converted to ?!\n",
646			    (unsigned char)*p );
647			putchar( '?' ); /* Replace unprintable char with a question mark. */
648		    } else {
649		        printf( "\\%o", (unsigned char)*p );
650		    }
651		} else {
652		    putchar( *p );
653		}
654		col++;
655		break;
656	    }
657	p++;
658	}
659    } while (( inlen = read( 0, inbuf, sizeof( inbuf ))) > 0 );
660    if ( inlen < 0 ) {
661	syslog( LOG_ERR, "read: %s", strerror(errno) );
662	return( 1 );
663    }
664    rc = 0;
665
666out:
667    if ( state & ST_AVAIL ) {
668	printf( ")S\n" );
669	state &= ~ST_AVAIL;
670    }
671
672    if ( state & ST_PAGE ) {
673	printf( "EP\n" );
674	state &= ~ST_PAGE;
675    }
676
677    if ( npages > 0 ) {
678	printf( "%%%%Trailer\nGSV restore\n%%%%Pages: %d\n%%%%EOF\n", npages );
679	fflush( stdout );
680    }
681
682    return( rc );
683}
684
685/*
686 * Interface to pipe and exec, for starting children in pipelines.
687 *
688 * Manipulates file descriptors 0, 1, and 2, such that the new child
689 * is reading from the parent's output.
690 */
691int pexecv( char *path, char *argv[])
692{
693    int		fd[ 2 ], c;
694
695    if ( pipe( fd ) < 0 ) {
696	return( -1 );
697    }
698
699    switch ( c = fork()) {
700    case -1 :
701	return( -1 );
702	/* NOTREACHED */
703
704    case 0 :
705	if ( close( fd[ 1 ] ) < 0 ) {
706	    return( -1 );
707	}
708	if ( dup2( fd[ 0 ], 0 ) < 0 ) {
709	    return( -1 );
710	}
711	if ( close( fd[ 0 ] ) < 0 ) {
712	    return( -1 );
713	}
714	execv( path, argv );
715	return( -1 );
716	/* NOTREACHED */
717
718    default :
719	if ( close( fd[ 0 ] ) < 0 ) {
720	    return( -1 );
721	}
722	if ( dup2( fd[ 1 ], 1 ) < 0 ) {
723	    return( -1 );
724	}
725	if ( close( fd[ 1 ] ) < 0 ) {
726	    return( -1 );
727	}
728	return( c );
729    }
730}
731