/* lpd.c 4.17 83/03/09 */
* lpd -- remote spooling line-printer daemon
* Recorded into c from assembly. Part of the code is stolen
* from the dat-phone daemon.
* Runs set user-id root in order to connect to remote machines
* NOTE: the lock file is used to pass information to lpq and lprm.
* it does not need to be removed because file locks are dynamic.
* Some utilities used by lpd
#define MAIL "/usr/lib/sendmail"
#define DORETURN 0 /* absorb fork error */
#define DOABORT 1 /* abort lpd if dofork fails */
#define DOUNLOCK 2 /* remove lock file before aborting */
char line
[132]; /* line from control file */
char title
[80]; /* ``pr'' title */
FILE *cfp
; /* control file */
int pfd
; /* printer file descriptor */
int ofd
; /* output filter file descriptor */
int lfd
; /* lock file descriptor */
int pid
; /* id of lpd process */
int prchild
; /* id of pr process */
int child
; /* id of any filters */
int ofilter
; /* id of output filter, if any */
int tof
= 1; /* top of form; init true if open does ff */
int remote
; /* non zero if sending files to remote */
int DU
; /* daeomon user-id */
char *DN
; /* daemon path name */
char *LP
; /* line printer device name */
char *RM
; /* remote machine name */
char *RP
; /* remote printer name */
char *RL
; /* path name of rlpr on remote machine */
char *LO
; /* lock file name */
char *ST
; /* status file name */
char *SD
; /* spool directory */
char *AF
; /* accounting file */
char *LF
; /* log file for error messages */
char *OF
; /* name of ouput filter (one only) */
char *IF
; /* name of input filter (per job) */
char *TF
; /* name of troff filter */
char *VF
; /* name of vplot filter */
char *CF
; /* name of cifplot filter */
char *PF
; /* name of vrast filter */
char *FF
; /* form feed string */
char *TR
; /* trailer string to be output when Q empties */
short SF
; /* suppress FF on each print job */
short SH
; /* suppress header page */
short SB
; /* short banner instead of normal header */
short PW
; /* page width */
short PL
; /* page length */
short BR
; /* baud rate if lp is a tty */
short FC
; /* flags to clear if lp is a tty */
short FS
; /* flags to set if lp is a tty */
extern banner(); /* big character printer */
char logname
[32]; /* user's login name */
char hostname
[32]; /* user's host machine */
char jobname
[32]; /* job or file name */
char class[32]; /* classification field */
char width
[10] = "-w"; /* page width for `pr' */
char length
[10] = "-l"; /* page length for `pr' */
char *name
; /* name of program */
char *printer
; /* name of printer */
register struct queue
*q
, **qp
;
* set-up controlled environment; trap bad signals
signal(SIGQUIT
, SIG_IGN
);
signal(SIGTERM
, cleanup
); /* for use with lprm */
init(); /* set up capabilities */
for (i
= 0; i
< NOFILE
; i
++)
(void) open("/dev/null", 0); /* standard input */
(void) open(LF
, 1); /* standard output */
(void) dup2(1, 2); /* standard error */
(void) lseek(1, 0L, 2); /* append if into a file */
* uses short form file names
log("cannot chdir to %s", SD
);
if ((lfd
= open(LO
, FWRONLY
|FCREATE
|FTRUNCATE
|FEXLOCK
|FNBLOCK
, FILMOD
)) < 0) {
if (errno
== EWOULDBLOCK
) /* active deamon present */
log("cannot create %s", LO
);
if (stat(LO
, &stb
) >= 0 || (lfd
= creat(LO
, 0444)) < 0)
* kill the parent so the user's shell can function
* write process id for others to know
sprintf(line
, "%u\n", pid
);
pidoff
= i
= strlen(line
);
if (write(lfd
, line
, i
) != i
)
log("cannot write daemon pid");
* acquire line printer or remote connection
for (i
= 1; ; i
= i
< 256 ? i
<< 1 : i
) {
log("cannot open %s", LP
);
status("waiting for %s to become ready (offline ?)", printer
);
sp
= getservbyname("shell", "tcp");
log("shell/tcp: unknown service");
(void) sprintf(line
+strlen(line
), " -P%s", RP
);
for (i
= 1; ; i
= i
< 512 ? i
<< 1 : i
) {
pfd
= rcmd(&RM
, sp
->s_port
, "root", "root", line
, 0);
pfd
= rcmd(&RM
, IPPORT_CMDSERVER
, "root", "root", line
, 0);
status("waiting for %s to come up", RM
);
log("no line printer device or remote machine name");
* Start running as daemon instead of root
* Start up an output filter, if needed.
if ((ofilter
= dofork(DOABORT
)) == 0) { /* child */
if ((ofilter
= dofork(DOUNLOCK
)) == 0) { /* child */
dup2(p
[0], 0); /* pipe is std in */
dup2(pfd
, 1); /* printer is std out */
for (i
= 3; i
< NOFILE
; i
++)
if ((cp
= rindex(OF
, '/')) == NULL
)
log("can't execl output filter %s", OF
);
(void) close(p
[0]); /* close input side */
ofd
= p
[1]; /* use pipe for output */
* search the spool directory for work and sort by queue order.
if ((nitems
= getq(&queue
)) < 0) {
log("can't scan spool directory %s", SD
);
if (nitems
== 0) { /* EOF => no work to do */
(void) write(ofd
, FF
, strlen(FF
));
if (TR
!= NULL
) /* output trailer */
(void) write(ofd
, TR
, strlen(TR
));
* we found something to do now do it --
* write the name of the current control file into the lock file
* so the spool queue program can tell what we're working on
status("%s is ready and printing", printer
);
status("sending to %s", RM
);
for (qp
= queue
; nitems
--; ) {
if (stat(q
->q_name
, &stb
) < 0)
sprintf(line
, "%s\n", q
->q_name
);
if (write(lfd
, line
, i
) != i
)
log("can't write (%d) control file name", errno
);
if (i
> 0) { /* restart daemon to reprint job */
log("cannot execl %d", DN
);
char fonts
[4][50]; /* fonts for troff */
static char ifonts
[4][50] = {
* The remaining part is the reading of the control file (cf)
* and performing the various actions.
* Returns 0 if everthing was OK and 1 if we should try to reprint the job.
if ((cfp
= fopen(file
, "r")) == NULL
) {
log("control file (%s) open failure <errno = %d>", file
, errno
);
strcpy(fonts
[i
], ifonts
[i
]);
* read the control file for work to do
* file format -- first character in the line is a command
* rest of the line is the argument.
* J -- "job name" on banner page
* C -- "class name" on banner page
* L -- "literal" user's name to print on banner
* H -- "host name" of machine where lpr was done
* P -- "person" user's login name
* I -- "indent" changes default indents driver
* must have stty/gtty avaialble
* f -- "file name" name of file to print
* 1 -- "R font file" for troff
* 2 -- "I font file" for troff
* 3 -- "B font file" for troff
* 4 -- "S font file" for troff
* N -- "name" of file (used by lpq)
* U -- "unlink" name of file to remove
* (after we print it. (Pass 2 only)).
* M -- "mail" to user when done printing
* getline reads a line and expands tabs to blanks
strcpy(hostname
, line
+1);
else if (class[0] == '\0')
gethostname(class, sizeof (class));
case 'T': /* header title for pr */
case 'L': /* identification line */
case '1': /* troff fonts */
strcpy(fonts
[line
[0]-'1'], line
+1);
case 'W': /* page width */
if ((i
= dump(line
[0], line
+1)) > 0)
* clean-up incase another control file exists
* Set up the chain [ PR [ | IF ] ] or [ {IF | TF | CF | VF} ].
* Return -1 if a non-recoverable error occured, 1 if a recoverable error and
char *av
[15], buf
[BUFSIZ
];
if ((fi
= open(file
, 0)) < 0) {
log("%s: open failure <errno = %d>", file
, errno
);
if (!SF
&& !tof
) /* start on a fresh page */
(void) write(ofd
, FF
, strlen(FF
));
if (IF
== NULL
&& (format
== 'f' || format
== 'l')) {
while ((n
= read(fi
, buf
, BUFSIZ
)) > 0)
if (write(ofd
, buf
, n
) != n
) {
case 'p': /* print file using 'pr' */
if (IF
== NULL
) { /* use output filter */
av
[4] = *title
? title
: " ";
if ((prchild
= dofork(DORETURN
)) == 0) { /* child */
dup2(fi
, 0); /* file is stdin */
dup2(p
[1], 1); /* pipe is stdout */
for (n
= 3; n
< NOFILE
; n
++)
execl(PR
, "pr", width
, length
, "-h", *title
? title
: " ", 0);
log("cannot execl %s", PR
);
(void) close(p
[1]); /* close output side */
fi
= p
[0]; /* use pipe for input */
case 'f': /* print plain text file */
case 'l': /* like 'f' but pass control characters */
case 't': /* print troff output */
(void) unlink(".railmag");
fo
= creat(".railmag", 0666);
for (n
= 0; n
< 4; n
++) {
(void) write(fo
, "/usr/lib/vfont/", 15);
(void) write(fo
, fonts
[n
], strlen (fonts
[n
]));
(void) write(fo
, "\n", 1);
case 'c': /* print cifplot output */
case 'v': /* print vplot output (plain raster file) */
log("illegal format character '%c'", format
);
if ((av
[0] = rindex(prog
, '/')) != NULL
)
if (ofilter
> 0) { /* stop output filter */
while ((pid
= wait3(&status
, WUNTRACED
, 0)) > 0 && pid
!= ofilter
)
if (status
.w_stopval
!= WSTOPPED
) {
log("output filter died (%d)", status
.w_retcode
);
if ((child
= dofork(DORETURN
)) == 0) { /* child */
for (n
= 3; n
< NOFILE
; n
++)
log("cannot execl %s", prog
);
exit(2); /* execl failed or not one of above cases. */
while ((pid
= wait(&status
)) > 0 && pid
!= child
)
if (ofilter
> 0) { /* restart output filter */
if (kill(ofilter
, SIGCONT
) < 0) {
log("cannot continue output filter");
if (!WIFEXITED(status
) || status
.w_retcode
> 1) {
log("Daemon Filter Malfunction");
} else if (status
.w_retcode
== 1)
* Getline reads a line from the control file, removes tabs, converts
* new-line to null and leaves it in line.
* returns 0 at EOF or the number of characters read.
register char *lp
= line
;
while ((c
= getc(cfp
)) != '\n') {
} while ((linel
& 07) != 0);
* Send the daemon control file (cf) and any data files.
if ((cfp
= fopen(file
, "r")) == NULL
) {
log("control file (%s) open failure <errno = %d>", file
, errno
);
* read the control file for work to do
* file format -- first character in the line is a command
* rest of the line is the argument.
* commands of interest are:
* f -- "file name" name of file to print
* U -- "unlink" name of file to remove
* (after we print it. (Pass 2 only)).
if (line
[0] >= 'a' && line
[0] <= 'z') {
while (linelen
= getline())
if (send('\3', last
+1) > 0)
if (send('\2', file
) > 0)
* clean-up incase another control file exists
* Send a data file to the remote machine and spool it.
* Return positive if we should try resending.
if ((f
= open(file
, 0)) < 0 || fstat(f
, &stb
) < 0) {
log("file (%s) open failure <errno = %d>", file
, errno
);
(void) sprintf(buf
, "%c%d %s\n", type
, stb
.st_size
, file
);
if (write(pfd
, buf
, strlen(buf
)) < 0)
for (i
= 0; i
< stb
.st_size
; i
+= BUFSIZ
) {
if (i
+ amt
> stb
.st_size
)
if (sizerr
== 0 && read(f
, buf
, amt
) != amt
)
if (write(pfd
, buf
, amt
) < 0)
log("%s: changed size", file
);
if(write(pfd
, "\1", 1) < 0) /* signal an error occured */
if (write(pfd
, "", 1) < 0)
* Check to make sure there have been no errors and that both programs
* are in sync with eachother.
* Return non-zero if the connection was lost.
if (read(pfd
, &resp
, 1) != 1)
log("fatal error in rlpr");
(void) write(ofd
, FF
, strlen(FF
));
if (SB
) { /* short banner only */
(void) write(ofd
, class, strlen(class));
(void) write(ofd
, ":", 1);
(void) write(ofd
, name1
, strlen(name1
));
(void) write(ofd
, " Job: ", 7);
(void) write(ofd
, name2
, strlen(name2
));
(void) write(ofd
, " Date: ", 8);
(void) write(ofd
, ctime(&tvec
), 24);
(void) write(ofd
, "\n", 1);
} else { /* normal banner */
(void) write(ofd
, "\n\n\n", 3);
scan_out(ofd
, name1
, '\0');
(void) write(ofd
, "\n\n", 2);
scan_out(ofd
, name2
, '\0');
(void) write(ofd
,"\n\n\n",3);
scan_out(ofd
, class, '\0');
(void) write(ofd
, "\n\n\n\n\t\t\t\t\tJob: ", 15);
(void) write(ofd
, name2
, strlen(name2
));
(void) write(ofd
, "\n\t\t\t\t\tDate: ", 12);
(void) write(ofd
, ctime(&tvec
), 24);
(void) write(ofd
, "\n", 1);
(void) write(ofd
, FF
, strlen(FF
));
for (scnwidth
= WIDTH
; --scnwidth
;) {
*p
++ = key
& 0200 ? c
: BACKGND
;
#define TRC(q) (((q)-' ')&0177)
scan_out(scfd
, scsp
, dlm
)
char outbuf
[LINELEN
+1], *sp
, c
, cc
;
extern char scnkey
[][HEIGHT
]; /* in lpdchar.c */
for (scnhgt
= 0; scnhgt
++ < HEIGHT
+DROP
;) {
d
= dropit(c
= TRC(cc
= *sp
++));
if ((!d
&& scnhgt
>HEIGHT
) || (scnhgt
<=DROP
&& d
))
strp
= scnline(scnkey
[c
][scnhgt
-1-d
], strp
, cc
);
if (*sp
==dlm
|| *sp
=='\0' || nchrs
++>=LINELEN
/(WIDTH
+1)-1)
while (*--strp
== BACKGND
&& strp
>= outbuf
)
(void) write(scfd
, outbuf
, strp
-outbuf
);
* tell people about job completion
if ((stat
= dofork(DORETURN
)) == 0) {
for (i
= 3; i
<= NOFILE
; i
++)
if ((cp
= rindex(MAIL
, '/')) != NULL
)
sprintf(buf
, "%s.%s", hostname
, line
+1);
printf("To: %s\n", line
+1);
printf("Subject: printer job\n\n");
printf("Your printer job ");
printf("(%s) ", jobname
);
* dofork - fork with retries on failure
for (i
= 0; i
< 20; i
++) {
log("bad action (%d) to dofork", action
);
signal(SIGTERM
, SIG_IGN
);
kill(child
, SIGKILL
); /* get rid of pr's */
kill(child
, SIGKILL
); /* get rid of filters */
kill(ofilter
, SIGKILL
); /* get rid of output filter */
exit(0); /* lprm removes the lock file */
short console
= isatty(fileno(stderr
));
fprintf(stderr
, console
? "\r\n%s: " : "%s: ", name
);
fprintf(stderr
, message
, a1
, a2
, a3
);
status(message
, a1
, a2
, a3
)
if ((fd
= creat(ST
, FILMOD
)) < 0) {
log("cannot create %s", ST
);
sprintf(buf
, message
, a1
, a2
, a3
);
(void) write(fd
, buf
, strlen(buf
));
static char buf
[BUFSIZ
/2];
if ((status
= pgetent(b
, printer
)) < 0) {
printf("%s: can't open printer description file\n", name
);
} else if (status
== 0) {
printf("%s: unknown printer\n", printer
);
if ((DN
= pgetstr("dn", &bp
)) == NULL
)
if ((LP
= pgetstr("lp", &bp
)) == NULL
)
if ((LO
= pgetstr("lo", &bp
)) == NULL
)
if ((ST
= pgetstr("st", &bp
)) == NULL
)
if ((LF
= pgetstr("lf", &bp
)) == NULL
)
if ((SD
= pgetstr("sd", &bp
)) == NULL
)
if ((FF
= pgetstr("ff", &bp
)) == NULL
)
if ((RL
= pgetstr("rl", &bp
)) == NULL
)
if ((DU
= pgetnum("du")) < 0)
if ((PW
= pgetnum("pw")) < 0)
sprintf(&width
[2], "%d", PW
);
if ((PL
= pgetnum("pl")) < 0)
sprintf(&length
[2], "%d", PL
);
if ((FC
= pgetnum("fc")) < 0)
if ((FS
= pgetnum("fs")) < 0)
register struct bauds
*bp
;
if (ioctl(pfd
, TIOCGETP
, (char *)&ttybuf
) < 0) {
log("cannot get tty parameters");
for (bp
= bauds
; bp
->baud
; bp
++)
log("illegal baud rate %d", BR
);
ttybuf
.sg_ispeed
= ttybuf
.sg_ospeed
= bp
->speed
;
if (ioctl(pfd
, TIOCSETN
, (char *)&ttybuf
) < 0) {
log("cannot set tty parameters");