xref: /plan9/sys/src/cmd/lp/lpdaemon.c (revision ea9142f784780976853734326525003db057e8e9)
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <signal.h>
7 #include <stdarg.h>
8 #include <time.h>
9 #include <unistd.h>
10 #include <sys/types.h>
11 #include <sys/wait.h>
12 
13 /* for Plan 9 */
14 #ifdef PLAN9
15 #define LP	"/bin/lp"
16 #define TMPDIR "/sys/lib/lp/tmp"
17 #define LPDAEMONLOG	"/sys/lib/lp/log/lpdaemonl"
18 #endif
19 /* for Tenth Edition systems */
20 #ifdef V10
21 #define LP	"/usr/bin/lp"
22 #define TMPDIR "/tmp"
23 #define LPDAEMONLOG	"/tmp/lpdaemonl"
24 #endif
25 /* for System V or BSD systems */
26 #if defined(SYSV) || defined(BSD)
27 #define LP	"/v/bin/lp"
28 #define TMPDIR "/tmp"
29 #define LPDAEMONLOG	"/tmp/lpdaemonl"
30 #endif
31 
32 #define ARGSIZ 4096
33 #define NAMELEN 30
34 
35 unsigned char argvstr[ARGSIZ];		/* arguments after parsing */
36 unsigned char *argvals[ARGSIZ/2+1];	/* pointers to arguments after parsing */
37 int ascnt = 0, argcnt = 0;	/* number of arguments parsed */
38 /* for 'stuff' gleened from lpr cntrl file */
39 struct jobinfo {
40 	char user[NAMELEN+1];
41 	char host[NAMELEN+1];
42 } *getjobinfo();
43 
44 #define MIN(a,b)	((a<b)?a:b)
45 
46 #define	CPYFIELD(src, dst)	{ while (*(src)!=' ' && *(src)!='\t' && *(src)!='\r' && *(src)!='\n' && *(src)!='\0') *(dst)++ = *(src)++; }
47 
48 #define	ACK()	write(1, "", 1)
49 #define NAK()	write(1, "\001", 1)
50 
51 #define LNBFSZ	4096
52 unsigned char lnbuf[LNBFSZ];
53 
54 #define	RDSIZE 512
55 unsigned char jobbuf[RDSIZE];
56 
57 int datafd[400], cntrlfd = -1;
58 
59 int dbgstate = 0;
60 char *dbgstrings[] = {
61 	"",
62 	"sendack1",
63 	"send",
64 	"rcvack",
65 	"sendack2",
66 	"done"
67 };
68 
69 void
error(char * s1,...)70 error(char *s1, ...)
71 {
72 	FILE *fp;
73 	long thetime;
74 	char *chartime;
75 	va_list ap;
76 	char *args[8];
77 	int argno = 0;
78 
79 	if((fp=fopen(LPDAEMONLOG, "a"))==NULL) {
80 		fprintf(stderr, "cannot open %s in append mode\n", LPDAEMONLOG);
81 		return;
82 	}
83 	time(&thetime);
84 	chartime = ctime(&thetime);
85 	fprintf(fp, "%.15s [%5.5d] ", &(chartime[4]), getpid());
86 	va_start(ap, s1);
87 	while((args[argno++] = va_arg(ap, char*)) && argno<8)
88 		;
89 	va_end(ap);
90 	fprintf(fp, s1, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
91 	fclose(fp);
92 }
93 
94 void
forklp(int inputfd)95 forklp(int inputfd)
96 {
97 	int i, cpid;
98 	unsigned char *bp, *cp;
99 	unsigned char logent[LNBFSZ];
100 
101 	/* log this call to lp */
102 	cp = logent;
103 	for (i=1; i<argcnt; i++) {
104 		bp = argvals[i];
105 		if (cp+strlen((const char *)bp)+1 < logent+LNBFSZ-1) {
106 			CPYFIELD(bp, cp);
107 			*cp++ = ' ';
108 		}
109 	}
110 	*--cp = '\n';
111 	*++cp = '\0';
112 	error((const char *)logent);
113 	switch((cpid=fork())){
114 	case -1:
115 		error("fork error\n");
116 		exit(2);
117 	case 0:
118 		if (inputfd != 0)
119 			dup2(inputfd, 0);
120 		dup2(1, 2);
121 		lseek(0, 0L, 0);
122 		execvp(LP, (const char **)argvals);
123 		error("exec failed\n");
124 		exit(3);
125 	default:
126 		while(wait((int *)0) != cpid)
127 			;
128 	}
129 }
130 
131 int
tempfile(void)132 tempfile(void)
133 {
134 	static tindx = 0;
135 	char tmpf[sizeof(TMPDIR)+64];
136 	int crtfd, tmpfd;
137 
138 	sprintf(tmpf, "%s/lp%d.%d", TMPDIR, getpid(), tindx++);
139 	if((crtfd=creat(tmpf, 0666)) < 0) {
140 		error("cannot create temp file %s\n", tmpf);
141 		NAK();
142 		exit(3);
143 	}
144 	if((tmpfd=open(tmpf, 2)) < 0) {
145 		error("cannot open temp file %s\n", tmpf);
146 		NAK();
147 		exit(3);
148 	}
149 	close(crtfd);
150 	unlink(tmpf);	/* comment out for debugging */
151 	return(tmpfd);
152 }
153 
154 int
readfile(int outfd,int bsize)155 readfile(int outfd, int bsize)
156 {
157 	int rv;
158 
159 	dbgstate = 1;
160 	alarm(60);
161 	ACK();
162 	dbgstate = 2;
163 	for(; bsize > 0; bsize -= rv) {
164 		alarm(60);
165 		if((rv=read(0, jobbuf, MIN(bsize,RDSIZE))) < 0) {
166 			error("error reading input, %d unread\n", bsize);
167 			exit(4);
168 		} else if (rv == 0) {
169 			error("connection closed prematurely\n");
170 			exit(4);
171 		} else if((write(outfd, jobbuf, rv)) != rv) {
172 			error("error writing temp file, %d unread\n", bsize);
173 			exit(5);
174 		}
175 	}
176 	dbgstate = 3;
177 	alarm(60);
178 	if (((rv=read(0, jobbuf, 1))==1) && (*jobbuf=='\0')) {
179 		alarm(60);
180 		ACK();
181 		dbgstate = 4;
182 		alarm(0);
183 		return(outfd);
184 	}
185 	alarm(0);
186 	error("received bad status <%d> from sender\n", *jobbuf);
187 	error("rv=%d\n", rv);
188 	NAK();
189 	return(-1);
190 }
191 
192 /* reads a line from the input into lnbuf
193  * if there is no error, it returns
194  *   the number of characters in the buffer
195  * if there is an error and there where characters
196  *   read, it returns the negative value of the
197  *   number of characters read
198  * if there is an error and no characters were read,
199  *   it returns the negative value of 1 greater than
200  *   the size of the line buffer
201  */
202 int
readline(int inpfd)203 readline(int inpfd)
204 {
205 	unsigned char *ap;
206 	int i, rv;
207 
208 	ap = lnbuf;
209 	lnbuf[0] = '\0';
210 	i = 0;
211 	alarm(60);
212 	do {
213 		rv = read(inpfd, ap, 1);
214 	} while (rv==1 && ++i && *ap != '\n' && ap++ && (i < LNBFSZ - 2));
215 	alarm(0);
216 	if (i != 0 && *ap != '\n') {
217 		*++ap = '\n';
218 		i++;
219 	}
220 	*++ap = '\0';
221 	if (rv < 0) {
222 		error("read error; lost connection\n");
223 		if (i==0) i = -(LNBFSZ+1);
224 		else i = -i;
225 	}
226 	return(i);
227 }
228 
229 int
getfiles(void)230 getfiles(void)
231 {
232 	unsigned char *ap;
233 	int filecnt, bsize, rv;
234 
235 	filecnt = 0;
236 	/* get a line, hopefully containing a ctrl char, size, and name */
237 	for(;;) {
238 		ap = lnbuf;
239 		if ((rv=readline(0)) < 0) NAK();
240 		if (rv <= 0) {
241 			return(filecnt);
242 		}
243 		switch(*ap++) {
244 		case '\1':		/* cleanup - data sent was bad (whatever that means) */
245 			break;
246 		case '\2':		/* read control file */
247 			bsize = atoi((const char *)ap);
248 			cntrlfd = tempfile();
249 			if (readfile(cntrlfd, bsize) < 0) {
250 				close(cntrlfd);
251 				NAK();
252 				return(0);
253 			}
254 			break;
255 		case '\3':		/* read data file */
256 			bsize = atoi((const char *)ap);
257 			datafd[filecnt] = tempfile();
258 			if (readfile(datafd[filecnt], bsize) < 0) {
259 				close(datafd[filecnt]);
260 				NAK();
261 				return(0);
262 			}
263 			filecnt++;
264 			break;
265 		default:
266 			error("protocol error <%d>\n", *(ap-1));
267 			NAK();
268 		}
269 	}
270 	return(filecnt);
271 }
272 
273 struct jobinfo *
getjobinfo(int fd)274 getjobinfo(int fd)
275 {
276 	unsigned char *ap;
277 	int rv;
278 	static struct jobinfo info;
279 
280 	if (fd < 0) error("getjobinfo: bad file descriptor\n");
281 	if (lseek(fd, 0L, 0) < 0) {
282 		error("error seeking in temp file\n");
283 		exit(7);
284 	}
285 	/* the following strings should be < NAMELEN or else they will not
286 	 * be null terminated.
287 	 */
288 	strncpy(info.user, "daemon", NAMELEN);
289 	strncpy(info.host, "nowhere", NAMELEN);
290 	/* there may be a space after the name and host.  It will be filtered out
291 	 * by CPYFIELD.
292 	 */
293 	while ((rv=readline(fd)) > 0) {
294 		ap = lnbuf;
295 		ap[rv-1] = '\0';	/* remove newline from string */
296 		switch (*ap) {
297 		case 'H':
298 			if (ap[1] == '\0')
299 				strncpy(info.host, "unknown", NAMELEN);
300 			else
301 				strncpy(info.host, (const char *)&ap[1], NAMELEN);
302 			info.host[NAMELEN] = '\0';
303 			break;
304 		case 'P':
305 			if (ap[1] == '\0')
306 				strncpy(info.user, "unknown", NAMELEN);
307 			else
308 				strncpy(info.user, (const char *)&ap[1], NAMELEN);
309 			info.user[NAMELEN] = '\0';
310 			break;
311 		}
312 	}
313 	return(&info);
314 }
315 
316 void
alarmhandler(int sig)317 alarmhandler(int sig) {
318 	signal(sig, alarmhandler);
319 	error("alarm at %d - %s\n", dbgstate, dbgstrings[dbgstate]);
320 }
321 
322 void
main()323 main()
324 {
325 	unsigned char *ap, *bp, *cp, *savbufpnt;
326 	int i, blen, rv, saveflg, savargcnt;
327 	struct jobinfo *jinfop;
328 
329 	signal(SIGHUP, SIG_IGN);
330 	signal(SIGALRM, alarmhandler);
331 	cp = argvstr;
332 	/* setup argv[0] for exec */
333 	argvals[argcnt++] = cp;
334 	for (bp = (unsigned char *)LP, i = 0; (*bp != '\0') && (i < ARGSIZ-1); *cp++ = *bp++, i++);
335 	*cp++ = '\0';
336 	/* get the first line sent and parse it as arguments for lp */
337 	if ((rv=readline(0)) < 0)
338 		exit(1);
339 	bp = lnbuf;
340 	/* setup the remaining arguments */
341 	/* check for BSD style request */
342 	/* ^A, ^B, ^C, ^D, ^E (for BSD lpr) */
343 	switch (*bp) {
344 	case '\001':
345 	case '\003':
346 	case '\004':
347 		bp++;	/* drop the ctrl character from the input */
348 		argvals[argcnt++] = cp;
349 		*cp++ = '-'; *cp++ = 'q'; *cp++ = '\0';		/* -q */
350 		argvals[argcnt++] = cp;
351 		*cp++ = '-'; *cp++ = 'd'; 			/* -d */
352 		CPYFIELD(bp, cp);				/* printer */
353 		*cp++ = '\0';
354 		break;
355 	case '\002':
356 		bp++;	/* drop the ctrl character from the input */
357 		argvals[argcnt++] = cp;
358 		*cp++ = '-'; *cp++ = 'd'; 			/* -d */
359 		CPYFIELD(bp, cp);				/* printer */
360 		*cp++ = '\0';
361 		ACK();
362 		savargcnt = argcnt;
363 		savbufpnt = cp;
364 		while ((rv=getfiles())) {
365 			jinfop = getjobinfo(cntrlfd);
366 			close(cntrlfd);
367 			argcnt = savargcnt;
368 			cp = savbufpnt;
369 			argvals[argcnt++] = cp;
370 			*cp++ = '-'; *cp++ = 'M'; 			/* -M */
371 			bp = (unsigned char *)jinfop->host;
372 			CPYFIELD(bp, cp);				/* host name */
373 			*cp++ = '\0';
374 			argvals[argcnt++] = cp;
375 			*cp++ = '-'; *cp++ = 'u'; 			/* -u */
376 			bp = (unsigned char *)jinfop->user;
377 			CPYFIELD(bp, cp);				/* user name */
378 			*cp++ = '\0';
379 			for(i=0;i<rv;i++)
380 				forklp(datafd[i]);
381 		}
382 		exit(0);
383 	case '\005':
384 		bp++;	/* drop the ctrl character from the input */
385 		argvals[argcnt++] = cp;
386 		*cp++ = '-'; *cp++ = 'k'; *cp++ = '\0';		/* -k */
387 		argvals[argcnt++] = cp;
388 		*cp++ = '-'; *cp++ = 'd'; 			/* -d */
389 		CPYFIELD(bp, cp);				/* printer */
390 		*cp++ = '\0';
391 		argvals[argcnt++] = cp;
392 		*cp++ = '-'; ap = cp; *cp++ = 'u'; 		/* -u */
393 		CPYFIELD(bp, cp);				/* username */
394 
395 		/* deal with bug in lprng where the username is not supplied
396 		 */
397 		if (ap == (cp-1)) {
398 			ap = (unsigned char *)"none";
399 			CPYFIELD(ap, cp);
400 		}
401 
402 		*cp++ = '\0';
403 		datafd[0] = tempfile();
404 		blen = strlen((const char *)bp);
405 		if (write(datafd[0], bp, blen) != blen) {
406 			error("write error\n");
407 			exit(6);
408 		}
409 		if (write(datafd[0], "\n", 1) != 1) {
410 			error("write error\n");
411 			exit(6);
412 		}
413 		break;
414 	default:
415 		/* otherwise get my lp arguments */
416 		do {
417 			/* move to next non-white space */
418 			while (*bp==' '||*bp=='\t')
419 				++bp;
420 			if (*bp=='\n') continue;
421 			/* only accept arguments beginning with -
422 			 * this is done to prevent the printing of
423 			 * local files from the destination host
424 			 */
425 			if (*bp=='-') {
426 				argvals[argcnt++] = cp;
427 				saveflg = 1;
428 			} else
429 				saveflg = 0;
430 			/* move to next white space copying text to argument buffer */
431 			while (*bp!=' ' && *bp!='\t' && *bp!='\n'
432 			    && *bp!='\0') {
433 				*cp = *bp++;
434 				cp += saveflg;
435 			}
436 			*cp = '\0';
437 			cp += saveflg;
438 		} while (*bp!='\n' && *bp!='\0');
439 		if (readline(0) < 0) exit(7);
440 		datafd[0] = tempfile();
441 		if(readfile(datafd[0], atoi((const char *)lnbuf)) < 0) {
442 			error("readfile failed\n");
443 			exit(8);
444 		}
445 	}
446 	forklp(datafd[0]);
447 	exit(0);
448 }
449