HEAD: removed mrwise debug crud from uniclientgen.cc.
[wvapps.git] / wvprint / lpd.cc
blob10c7d24ba7edf4735d5aa7c989adb7520bcd417e
1 /*
2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2003 Net Integration Technologies, Inc.
5 * Implementation of the LPD protocol (RFC1179).
7 * We implement the bare minimum to make (most) application produce
8 * the expected output on paper. We break the protocol in many
9 * wonderful ways, in the name of being ultra-simple and safe.
11 * How ultra-simple? We print whatever data we get transferred and the
12 * "interesting" attributes of the last control file we see gets to be
13 * applied to all the data files we got!
15 * "We don't care." -- apenwarr, our fearless leader.
17 * $Id: lpd.cc,v 1.34 2002/07/31 23:08:04 pphaneuf Exp $
20 #include <assert.h>
21 #include <ctype.h>
22 #include "wvlog.h"
23 #include "strutils.h"
24 #include "lpd.h"
26 #define MAX_CONTROL_FILE_SIZE 8192
27 #define DATA_BUFFER_SIZE 32767
29 WvLPDConnection::WvLPDConnection(WvPrint &_wvprint,
30 WvLog &_log,
31 WvTCPConn *_conn,
32 bool _stupid_klpq_hack):
33 WvStreamClone(_conn), wvprint(_wvprint),
34 log(_log), state(INITIAL), queue(0), data(0),
35 left(0), job(0), username("unknown"), filename("unknown"),
36 jobname("unknown"), hostname("unknown"), copies(1),
37 stupid_klpq_hack(_stupid_klpq_hack), seen_job(false)
39 assert(cloned);
40 log(WvLog::Debug1, "LPD connection from %s\n",
41 *static_cast<WvTCPConn*>(cloned)->src());
44 WvLPDConnection::~WvLPDConnection()
46 PrintJobList::Iter i(jobs);
48 /* FIXME: provide some way to match this message with others on the
49 * same connection. */
50 log(WvLog::Debug1, "client disconnected\n");
52 for (i.rewind(); i.next(); )
54 i().username = username;
55 i().jobname = jobname;
56 i().hostname = hostname;
57 i().copies = copies;
58 i().spoolInfo->filename = filename;
59 i().commit();
62 RELEASE(data);
65 void WvLPDConnection::state_initial()
67 char* str;
68 WvString queuename;
70 str = getline(0);
71 if (str)
73 str = trim_string(str);
75 params.split(&str[1]);
77 /* Pick out the queue name from the parameters. */
78 if(params.isempty())
80 log(WvLog::Warning, "no queue specified, disconnecting\n");
81 state = CLOSE;
82 close();
83 return;
85 queuename = *params.first();
86 if (queuename == WVPRINT_MAGIC_PROBE)
88 print(WVPRINT_MAGIC_PROBE"\n");
89 wvprint.advertise_queues(AdvertiseQueueCallback(this, &WvLPDConnection::callback_probe));
90 state = STASIS;
91 return;
94 queue = wvprint.get_queue(queuename);
95 params.unlink_first();
97 switch(*str)
99 case 0x01:
100 state = PRINT_WAITING;
101 break;
102 case 0x02:
103 /* FIXME: We always accept the job if the queue exists. Maybe we
104 shouldn't? */
105 if (queue)
107 write("\0", 1);
108 state = RECEIVE_JOB;
110 else
112 log(WvLog::Warning, "unknown printer \"%s\"\n", queuename);
113 write("\1", 1);
114 state = CLOSE;
115 close();
117 break;
118 case 0x03:
119 state = QUEUE_STATE;
120 break;
121 case 0x04:
122 state = QUEUE_STATE_LONG;
123 break;
124 case 0x05:
125 state = REMOVE_JOB;
126 break;
127 default:
128 log(WvLog::Warning, "incorrect LPD command, disconnecting\n");
129 close();
130 state = CLOSE;
131 return;
135 * We have to poke ourselves because we might not be readable
136 * anymore (we possibly read everything the client had to say)
138 alarm(0);
142 void WvLPDConnection::state_print_waiting()
145 * This LPD command seems to be pretty useless. It's use seems to be
146 * to prod the print server so that it starts printing right now (if
147 * the printer was down and the server didn't notice it was back up
148 * yet for example). Hopefully, WvPrint will be smarter and won't
149 * need something like that.
151 state = CLOSE;
152 close();
155 void WvLPDConnection::state_receive_job()
157 char* str;
159 str = getline(0);
160 if (str)
162 str = trim_string(str);
164 log(WvLog::Debug, "we're in state_receive_job (and got \"%s\")\n", str);
166 params.zap();
167 params.split(&str[1]);
169 switch(*str)
171 case 0x01:
172 log(WvLog::Debug, "got the unsupported \"Abort job\" subcommand\n");
173 write("\1", 1);
174 state = CLOSE;
175 close();
176 break;
177 case 0x02:
178 left = params.first()->num();
179 /* FIXME: arbitrary limit here. */
180 if (left > MAX_CONTROL_FILE_SIZE)
182 write("\1", 1);
183 state = CLOSE;
184 close();
186 else
188 queuemin(left + 1);
189 write("\0", 1);
190 state = RECEIVE_CONTROL;
192 break;
193 case 0x03:
194 left = params.first()->num();
195 job = queue->new_job();
196 if (!job)
198 /* Sorry, try again later. */
199 write("\1", 1);
200 state = CLOSE;
201 close();
202 break;
204 jobs.append(job, false);
206 data = new WvFile(job->get_file(), O_CREAT|O_TRUNC|O_WRONLY, 0600);
207 if (!data->isok())
209 log(WvLog::Error, "could not open spool file (%s)\n", job->get_file());
210 write("\1", 1);
211 close();
212 state = CLOSE;
214 else
216 write("\0", 1);
217 state = RECEIVE_DATA;
219 break;
220 default:
221 /* FIXME: this is not the correct error message. */
222 log(WvLog::Warning, "incorrect LPD command, disconnecting\n");
223 state = CLOSE;
224 close();
225 return;
228 else
230 log(WvLog::Debug, "we're in state_receive_job\n");
234 void WvLPDConnection::state_receive_control()
236 /* We are assuming that "left" is limited to a sensible number. */
237 char controlraw[left + 1];
238 size_t rv;
239 WvStringList lines;
240 WvStringList::Iter i(lines);
242 /* Just being defensive. */
243 queuemin(left + 1);
244 rv = read(controlraw, left + 1);
245 log(WvLog::Debug, "state_receive_control: read %s bytes (%s left)\n", rv, left - rv + 1);
247 if (rv == 0)
249 log(WvLog::Debug, "why were we called if there was nothing to read?\n");
250 return;
253 queuemin(0);
255 if ((rv - 1) == left)
257 /* Check if the client is trying to mess with us. */
258 if (controlraw[left] != 0)
260 log(WvLog::Warning, "control file transfer from the client went bad\n");
261 write("\1", 1);
262 state = CLOSE;
263 close();
266 copies = 0;
268 lines.split(trim_string(controlraw), "\r\n");
269 i.rewind();
270 while (i.next())
272 switch(**i)
274 case 'C':
275 log(WvLog::Debug, "class for banner page: %s\n", *i + 1);
276 break;
277 case 'H':
278 log(WvLog::Debug, "host name: %s\n", *i + 1);
279 hostname = *i + 1;
280 break;
281 case 'I':
282 log(WvLog::Debug, "indenting count: %s\n", *i + 1);
283 break;
284 case 'J':
285 log(WvLog::Debug, "job name for banner page: %s\n", *i + 1);
286 jobname = *i + 1;
287 break;
288 case 'L':
289 log(WvLog::Debug, "print banner page (user: %s)\n", *i + 1);
290 break;
291 case 'M':
292 log(WvLog::Debug, "mail when printed: %s\n", *i + 1);
293 break;
294 case 'N':
295 log(WvLog::Debug, "name of source file: %s\n", *i + 1);
296 filename = *i + 1;
297 break;
298 case 'P':
299 log(WvLog::Debug, "user identification: %s\n", *i + 1);
300 username = *i + 1;
301 break;
302 case 'S':
303 log(WvLog::Debug, "symlink data: %s\n", *i + 1);
304 break;
305 case 'T':
306 log(WvLog::Debug, "title: %s\n", *i + 1);
307 break;
308 case 'U':
309 log(WvLog::Debug, "unlink data file: %s\n", *i + 1);
310 break;
311 case 'W':
312 log(WvLog::Debug, "width of output: %s\n", *i + 1);
313 break;
314 case '1':
315 log(WvLog::Debug, "troff R font: %s\n", *i + 1);
316 break;
317 case '2':
318 log(WvLog::Debug, "troff I font: %s\n", *i + 1);
319 break;
320 case '3':
321 log(WvLog::Debug, "troff B font: %s\n", *i + 1);
322 break;
323 case '4':
324 log(WvLog::Debug, "troff S font: %s\n", *i + 1);
325 break;
326 case 'c':
327 log(WvLog::Debug, "plot CIF file: %s\n", *i + 1);
328 break;
329 case 'd':
330 log(WvLog::Debug, "print DVI file: %s\n", *i + 1);
331 ++copies;
332 break;
333 case 'f':
334 log(WvLog::Debug, "print formatted file: %s\n", *i + 1);
335 ++copies;
336 break;
337 case 'g':
338 log(WvLog::Debug, "plot file: %s\n", *i + 1);
339 ++copies;
340 break;
341 case 'k':
342 log(WvLog::Debug, "Kerberos info: %s\n", *i + 1);
343 break;
344 case 'l':
345 log(WvLog::Debug, "print file leaving control chars: %s\n", *i + 1);
346 ++copies;
347 break;
348 case 'n':
349 log(WvLog::Debug, "print ditroff output file: %s\n", *i + 1);
350 ++copies;
351 break;
352 case 'o':
353 log(WvLog::Debug, "print PostScript file: %s\n", *i + 1);
354 ++copies;
355 break;
356 case 'p':
357 log(WvLog::Debug, "print file with 'pr' format: %s\n", *i + 1);
358 ++copies;
359 break;
360 case 'r':
361 log(WvLog::Debug, "print file with FORTRAN carriage control: %s\n", *i + 1);
362 ++copies;
363 break;
364 case 't':
365 log(WvLog::Debug, "print troff output file: %s\n", *i + 1);
366 ++copies;
367 break;
368 case 'v':
369 log(WvLog::Debug, "print raster file: %s\n", *i + 1);
370 ++copies;
371 break;
372 case 'z':
373 log(WvLog::Debug, "reserved for future use with the Palladium print system\n");
374 break;
375 default:
376 log(WvLog::Debug, "Unknown control file line: %s\n", *i);
380 /* FIXME: should verify the content of the control file before
381 * accepting it. */
382 write("\0", 1);
383 state = RECEIVE_JOB;
385 else
387 log(WvLog::Warning, "could not read the control file to the end?\n");
388 write("\1", 1);
389 state = CLOSE;
390 close();
394 void WvLPDConnection::state_receive_data()
396 char buf[DATA_BUFFER_SIZE];
397 size_t rv;
399 rv = read(buf, left > DATA_BUFFER_SIZE ? DATA_BUFFER_SIZE : left);
400 log(WvLog::Debug, "state_receive_data: read %s bytes (%s left)\n", rv, left - rv);
401 left = left - rv;
403 data->write(buf, rv);
405 if (left == 0)
407 RELEASE(data);
408 state = RECEIVE_DATA_FIN;
412 void WvLPDConnection::state_receive_data_fin()
414 char b;
415 size_t rv;
417 rv = read(&b, 1);
419 if (rv > 0)
421 if (b != 0)
423 log(WvLog::Warning, "data file transfer from the client went bad\n");
424 jobs.unlink(job);
425 queue->cancel(job->get_id());
426 write("\1", 1);
427 state = CLOSE;
428 close();
430 else
432 write("\0", 1);
433 state = RECEIVE_JOB;
438 void WvLPDConnection::state_queue_state()
440 if (!queue)
442 print("unknown printer\n");
443 state = CLOSE;
444 close();
445 return;
448 if (queue->job_count())
450 /* FIXME: here, we should give *accurate* status. */
451 print("%s is ready and printing\n", queue->name);
452 print("Rank Owner Job Files Total Size\n");
454 else
455 print("no entries\n");
457 queue->foreach(PrintJobInfoCallback(this, &WvLPDConnection::send_printjobinfo));
459 /* The rest of the work, including the disconnection, is done in
460 * the callback. So we go to eternal sleep. */
461 state = STASIS;
464 void WvLPDConnection::state_queue_state_long()
466 /* FIXME: for the moment, this is the same as the short status. */
467 state = QUEUE_STATE;
468 alarm(0);
471 void WvLPDConnection::send_printjobinfo(const PrintJobInfo* info)
473 WvStringList::Iter i(params);
474 WvString position;
475 bool found;
477 if (!info)
479 if (queue->lpq_crap && !(stupid_klpq_hack && seen_job))
480 print(queue->lpq_crap);
481 state = CLOSE;
482 close();
483 return;
486 seen_job = true;
488 found = false;
490 i.rewind();
491 while (i.next())
493 if (isdigit(*static_cast<const char*>(*i)))
495 if (static_cast<unsigned int>(atoi(*i)) == info->id)
496 found = true;
498 else
500 if (strcmp(*i, info->username) == 0)
501 found = true;
505 if (params.isempty())
506 found = true;
508 if (found)
510 switch(info->position)
512 case 0:
513 position = "active";
514 break;
515 case 1:
516 position = "1st";
517 break;
518 case 2:
519 position = "2nd";
520 break;
521 case 3:
522 position = "3rd";
523 break;
524 default:
525 position = WvString("%s%s", info->position, "th");
526 break;
529 print("%-6s %-10s %-4s %-37s %s bytes\n", position,
530 info->username, info->id, info->filename, info->size);
534 void WvLPDConnection::state_remove_job()
536 WvString user;
537 WvStringList::Iter i(params);
539 state = CLOSE;
541 if (params.isempty())
543 log(WvLog::Error, "no user name specified to the remove job command, disconnecting\n");
544 state = CLOSE;
545 close();
546 return;
549 user = *params.first();
550 params.unlink_first();
552 if (params.isempty())
554 unsigned int printing = queue->get_active_id();
556 if (printing)
558 if (queue->cancel(printing, user))
559 print("job %s canceled\n", printing);
560 else
561 print("could not cancel job %s\n", printing);
564 close();
565 return;
568 i.rewind();
569 while (i.next())
571 print("param: %s\n", *i);
573 if (isdigit(*static_cast<const char*>(*i)))
575 if (queue->cancel(atoi(*i), user))
576 print("job %s canceled\n", atoi(*i));
577 else
578 print("could not cancel job %s\n", atoi(*i));
580 else
582 print("removal of jobs by user name not implemented yet!\n");
583 username = *i;
584 agentname = user;
585 queue->foreach(PrintJobInfoCallback(this, &WvLPDConnection::cancel_job));
589 /* FIXME: this might be a bug. If the queue->foreach() is not executed
590 * all in one go, then we might still be talking to the other end at
591 * this point. But there is no obvious place to close the
592 * connection... */
593 state = CLOSE;
594 close();
597 void WvLPDConnection::execute()
599 WvStreamClone::execute();
601 switch(state)
603 case INITIAL:
604 state_initial();
605 break;
606 case PRINT_WAITING:
607 state_print_waiting();
608 break;
609 case RECEIVE_JOB:
610 state_receive_job();
611 break;
612 case RECEIVE_CONTROL:
613 state_receive_control();
614 break;
615 case RECEIVE_DATA:
616 state_receive_data();
617 break;
618 case RECEIVE_DATA_FIN:
619 state_receive_data_fin();
620 break;
621 case QUEUE_STATE:
622 state_queue_state();
623 break;
624 case QUEUE_STATE_LONG:
625 state_queue_state_long();
626 break;
627 case REMOVE_JOB:
628 state_remove_job();
629 break;
630 case CLOSE:
631 log(WvLog::Debug, "WvLPDConnection::execute called with state CLOSE\n");
632 close();
633 break;
634 case STASIS:
635 /* FIXME: here, we don't want to do anything, but if by
636 * accident we become readable or something, we'll just get
637 * called as fast as possible. */
638 break;
639 default:
640 log(WvLog::Critical, "bug in WvLPDConnection: invalid state\n");
641 state = CLOSE;
642 close();
646 void WvLPDConnection::callback_probe(WvStringParm queue,
647 WvStringParm printertype,
648 WvStringParm description)
650 if (!queue && !printertype && !description)
652 flush_then_close(60000);
653 return;
656 print(queue);
658 if (printertype)
659 print(":%s", printertype);
660 else
661 print(":");
663 if (description)
664 print(":%s\n", description);
665 else
666 print("\n");
669 void WvLPDConnection::cancel_job(const PrintJobInfo* info)
671 if (info && (info->username == username))
673 if (queue->cancel(info->id, agentname))
674 print("job %s canceled\n", info->id);
675 else
676 print("could not cancel job %s\n", info->id);