xref: /plan9/sys/src/cmd/lp/lpdaemon.c (revision ff8c3af2f44d95267f67219afa20ba82ff6cf7e4)
1 #include <sys/types.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5 #include <fcntl.h>
6 #include <stdlib.h>
7 #include <stdio.h>
8 #include <signal.h>
9 #include <errno.h>
10 #include <time.h>
11 #include <string.h>
12 #include <stdarg.h>
13 
14 /* for Plan 9 */
15 #ifdef PLAN9
16 #define LP	"/bin/lp"
17 #define TMPDIR "/sys/lib/lp/tmp"
18 #define LPDAEMONLOG	"/sys/lib/lp/log/lpdaemonl"
19 #endif
20 /* for Tenth Edition systems */
21 #ifdef V10
22 #define LP	"/usr/bin/lp"
23 #define TMPDIR "/tmp"
24 #define LPDAEMONLOG	"/tmp/lpdaemonl"
25 #endif
26 /* for System V or BSD systems */
27 #if defined(SYSV) || defined(BSD)
28 #define LP	"/v/bin/lp"
29 #define TMPDIR "/tmp"
30 #define LPDAEMONLOG	"/tmp/lpdaemonl"
31 #endif
32 
33 #define ARGSIZ 4096
34 #define NAMELEN 30
35 
36 unsigned char argvstr[ARGSIZ];		/* arguments after parsing */
37 unsigned char *argvals[ARGSIZ/2+1];	/* pointers to arguments after parsing */
38 int ascnt = 0, argcnt = 0;	/* number of arguments parsed */
39 /* for 'stuff' gleened from lpr cntrl file */
40 struct jobinfo {
41 	char user[NAMELEN+1];
42 	char host[NAMELEN+1];
43 } *getjobinfo();
44 
45 #define MIN(a,b)	((a<b)?a:b)
46 
47 #define	CPYFIELD(src, dst)	{ while (*(src)!=' ' && *(src)!='\t' && *(src)!='\r' && *(src)!='\n' && *(src)!='\0') *(dst)++ = *(src)++; }
48 
49 #define	ACK()	write(1, "", 1)
50 #define NAK()	write(1, "\001", 1)
51 
52 #define LNBFSZ	4096
53 unsigned char lnbuf[LNBFSZ];
54 
55 #define	RDSIZE 512
56 unsigned char jobbuf[RDSIZE];
57 
58 int datafd[400], cntrlfd = -1;
59 
60 int dbgstate = 0;
61 char *dbgstrings[] = {
62 	"",
63 	"sendack1",
64 	"send",
65 	"rcvack",
66 	"sendack2",
67 	"done"
68 };
69 
70 void
71 error(char *s1, ...)
72 {
73 	FILE *fp;
74 	long thetime;
75 	char *chartime;
76 	va_list ap;
77 	char *args[8];
78 	int argno = 0;
79 
80 	if((fp=fopen(LPDAEMONLOG, "a"))==NULL) {
81 		fprintf(stderr, "cannot open %s in append mode\n", LPDAEMONLOG);
82 		return;
83 	}
84 	time(&thetime);
85 	chartime = ctime(&thetime);
86 	fprintf(fp, "%.15s [%5.5d] ", &(chartime[4]), getpid());
87 	va_start(ap, s1);
88 	while((args[argno++] = va_arg(ap, char*)) && argno<8);
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 	fflush(fp);
92 	fclose(fp);
93 	return;
94 }
95 
96 void
97 forklp(int inputfd)
98 {
99 	int i, cpid;
100 	unsigned char *bp, *cp;
101 	unsigned char logent[LNBFSZ];
102 
103 	/* log this call to lp */
104 	cp = logent;
105 	for (i=1; i<argcnt; i++) {
106 		bp = argvals[i];
107 		if (cp+strlen((const char *)bp)+1 < logent+LNBFSZ-1) {
108 			CPYFIELD(bp, cp);
109 			*cp++ = ' ';
110 		}
111 	}
112 	*--cp = '\n';
113 	*++cp = '\0';
114 	error((const char *)logent);
115 	switch((cpid=fork())){
116 	case -1:
117 		error("fork error\n");
118 		exit(2);
119 	case 0:
120 		if (inputfd != 0)
121 			dup2(inputfd, 0);
122 		dup2(1, 2);
123 		lseek(0, 0L, 0);
124 		execvp(LP, (const char **)argvals);
125 		error("exec failed\n");
126 		exit(3);
127 	default:
128 		while(wait((int *)0) != cpid);
129 	}
130 }
131 
132 int
133 tempfile(void)
134 {
135 	static tindx = 0;
136 	char tmpf[sizeof(TMPDIR)+64];
137 	int crtfd, tmpfd;
138 
139 	sprintf(tmpf, "%s/lp%d.%d", TMPDIR, getpid(), tindx++);
140 	if((crtfd=creat(tmpf, 0666)) < 0) {
141 		error("cannot create temp file %s\n", tmpf);
142 		NAK();
143 		exit(3);
144 	}
145 	if((tmpfd=open(tmpf, 2)) < 0) {
146 		error("cannot open temp file %s\n", tmpf);
147 		NAK();
148 		exit(3);
149 	}
150 	close(crtfd);
151 	unlink(tmpf);	/* comment out for debugging */
152 	return(tmpfd);
153 }
154 
155 int
156 readfile(int outfd, int bsize)
157 {
158 	int rv;
159 
160 	dbgstate = 1;
161 	alarm(60);
162 	ACK();
163 	dbgstate = 2;
164 	for(; bsize > 0; bsize -= rv) {
165 		alarm(60);
166 		if((rv=read(0, jobbuf, MIN(bsize,RDSIZE))) < 0) {
167 			error("error reading input, %d unread\n", bsize);
168 			exit(4);
169 		} else if (rv == 0) {
170 			error("connection closed prematurely\n");
171 			exit(4);
172 		} else if((write(outfd, jobbuf, rv)) != rv) {
173 			error("error writing temp file, %d unread\n", bsize);
174 			exit(5);
175 		}
176 	}
177 	dbgstate = 3;
178 	alarm(60);
179 	if (((rv=read(0, jobbuf, 1))==1) && (*jobbuf=='\0')) {
180 		alarm(60);
181 		ACK();
182 		dbgstate = 4;
183 		alarm(0);
184 		return(outfd);
185 	}
186 	alarm(0);
187 	error("received bad status <%d> from sender\n", *jobbuf);
188 	error("rv=%d\n", rv);
189 	NAK();
190 	return(-1);
191 }
192 
193 /* reads a line from the input into lnbuf
194  * if there is no error, it returns
195  *   the number of characters in the buffer
196  * if there is an error and there where characters
197  *   read, it returns the negative value of the
198  *   number of characters read
199  * if there is an error and no characters were read,
200  *   it returns the negative value of 1 greater than
201  *   the size of the line buffer
202  */
203 int
204 readline(int inpfd)
205 {
206 	unsigned char *ap;
207 	int i, rv;
208 
209 	ap = lnbuf;
210 	lnbuf[0] = '\0';
211 	i = 0;
212 	alarm(60);
213 	do {
214 		rv = read(inpfd, ap, 1);
215 	} while (rv==1 && ++i && *ap != '\n' && ap++ && (i < LNBFSZ - 2));
216 	alarm(0);
217 	if (i != 0 && *ap != '\n') {
218 		*++ap = '\n';
219 		i++;
220 	}
221 	*++ap = '\0';
222 	if (rv < 0) {
223 		error("read error; lost connection\n");
224 		if (i==0) i = -(LNBFSZ+1);
225 		else i = -i;
226 	}
227 	return(i);
228 }
229 
230 int
231 getfiles(void)
232 {
233 	unsigned char *ap;
234 	int filecnt, bsize, rv;
235 
236 	filecnt = 0;
237 	/* get a line, hopefully containing a ctrl char, size, and name */
238 	for(;;) {
239 		ap = lnbuf;
240 		if ((rv=readline(0)) < 0) NAK();
241 		if (rv <= 0) {
242 			return(filecnt);
243 		}
244 		switch(*ap++) {
245 		case '\1':		/* cleanup - data sent was bad (whatever that means) */
246 			break;
247 		case '\2':		/* read control file */
248 			bsize = atoi((const char *)ap);
249 			cntrlfd = tempfile();
250 			if (readfile(cntrlfd, bsize) < 0) {
251 				close(cntrlfd);
252 				NAK();
253 				return(0);
254 			}
255 			break;
256 		case '\3':		/* read data file */
257 			bsize = atoi((const char *)ap);
258 			datafd[filecnt] = tempfile();
259 			if (readfile(datafd[filecnt], bsize) < 0) {
260 				close(datafd[filecnt]);
261 				NAK();
262 				return(0);
263 			}
264 			filecnt++;
265 			break;
266 		default:
267 			error("protocol error <%d>\n", *(ap-1));
268 			NAK();
269 		}
270 	}
271 	return(filecnt);
272 }
273 
274 struct jobinfo *
275 getjobinfo(int fd)
276 {
277 	unsigned char *ap;
278 	int rv;
279 	static struct jobinfo info;
280 
281 	if (fd < 0) error("getjobinfo: bad file descriptor\n");
282 	if (lseek(fd, 0L, 0) < 0) {
283 		error("error seeking in temp file\n");
284 		exit(7);
285 	}
286 	/* the following strings should be < NAMELEN or else they will not
287 	 * be null terminated.
288 	 */
289 	strncpy(info.user, "daemon", NAMELEN);
290 	strncpy(info.host, "nowhere", NAMELEN);
291 	/* there may be a space after the name and host.  It will be filtered out
292 	 * by CPYFIELD.
293 	 */
294 	while ((rv=readline(fd)) > 0) {
295 		ap = lnbuf;
296 		ap[rv-1] = '\0';	/* remove newline from string */
297 		switch (*ap) {
298 		case 'H':
299 			if (ap[1] == '\0')
300 				strncpy(info.host, "unknown", NAMELEN);
301 			else
302 				strncpy(info.host, (const char *)&ap[1], NAMELEN);
303 			info.host[strlen(info.host)] = '\0';
304 			break;
305 		case 'P':
306 			if (ap[1] == '\0')
307 				strncpy(info.user, "unknown", NAMELEN);
308 			else
309 				strncpy(info.user, (const char *)&ap[1], NAMELEN);
310 			info.user[strlen(info.user)] = '\0';
311 			break;
312 		}
313 	}
314 	return(&info);
315 }
316 
317 void
318 alarmhandler(int sig) {
319 	signal(sig, alarmhandler);
320 	error("alarm at %d - %s\n", dbgstate, dbgstrings[dbgstate]);
321 }
322 
323 main()
324 {
325 	unsigned char *ap, *bp, *cp, *savbufpnt;
326 	int i, blen, rv, saveflg, savargcnt;
327 	struct jobinfo *jinfop;
328 
329 	signal(1, SIG_IGN);		/* SIGHUP not in lcc */
330 	signal(14, alarmhandler);	/* SIGALRM not in lcc */
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