]> git.ipfire.org Git - thirdparty/cups.git/blame - scheduler/client.c
Multiple IPP processing fixes.
[thirdparty/cups.git] / scheduler / client.c
CommitLineData
a74b005d 1/*
b14d90ba 2 * "$Id: client.c,v 1.4 1999/02/19 22:07:04 mike Exp $"
a74b005d 3 *
4 * Client routines for the Common UNIX Printing System (CUPS) scheduler.
5 *
6 * Copyright 1997-1999 by Easy Software Products, all rights reserved.
7 *
8 * These coded instructions, statements, and computer programs are the
9 * property of Easy Software Products and are protected by Federal
10 * copyright law. Distribution and use rights are outlined in the file
11 * "LICENSE.txt" which should have been included with this file. If this
12 * file is missing or damaged please contact Easy Software Products
13 * at:
14 *
15 * Attn: CUPS Licensing Information
16 * Easy Software Products
17 * 44145 Airport View Drive, Suite 204
18 * Hollywood, Maryland 20636-3111 USA
19 *
20 * Voice: (301) 373-9603
21 * EMail: cups-info@cups.org
22 * WWW: http://www.cups.org
23 *
24 * Contents:
25 *
26 * AcceptClient() - Accept a new client.
27 * CloseAllClients() - Close all remote clients immediately.
28 * CloseClient() - Close a remote client.
29 * ReadClient() - Read data from a client.
30 * SendCommand() - Send output from a command via HTTP.
31 * SendError() - Send an error message via HTTP.
32 * SendFile() - Send a file via HTTP.
33 * SendHeader() - Send an HTTP header.
34 * StartListening() - Create all listening sockets...
35 * StopListening() - Close all listening sockets...
36 * WriteClient() - Write data to a client as needed.
37 * decode_basic_auth() - Decode a Basic authorization string.
38 * get_datetime() - Get a data/time string for the given time.
39 * get_extension() - Get the extension for a filename.
40 * get_file() - Get a filename and state info.
41 * get_line() - Get a request line terminated with a CR and LF.
42 * get_message() - Get a message string for the given HTTP code.
43 * get_type() - Get MIME type from the given extension.
44 * sigpipe_handler() - Handle 'broken pipe' signals from lost network
45 * clients.
46 */
47
48/*
49 * Include necessary headers...
50 */
51
52#include "cupsd.h"
53
54
55/*
56 * Local functions...
57 */
58
59static void decode_basic_auth(client_t *con);
60static int check_if_modified(client_t *con, struct stat *filestats);
61static char *get_extension(char *filename);
62static char *get_type(char *extension);
63static char *get_file(client_t *con, struct stat *filestats);
b14d90ba 64static int pipe_command(client_t *con, int infile, int *outfile, char *command, char *options);
a74b005d 65static void sigpipe_handler(int sig);
66
67
68/*
69 * 'AcceptClient()' - Accept a new client.
70 */
71
72void
73AcceptClient(listener_t *lis) /* I - Listener socket */
74{
75 int i; /* Looping var */
76 int val; /* Parameter value */
77 client_t *con; /* New client pointer */
78 unsigned address;/* Address of client */
79 struct hostent *host; /* Host entry for address */
80
81
82 /*
83 * Get a pointer to the next available client...
84 */
85
86 con = Clients + NumClients;
87
88 memset(con, 0, sizeof(client_t));
89 con->http.activity = time(NULL);
90
91 /*
92 * Accept the client and get the remote address...
93 */
94
95 val = sizeof(struct sockaddr_in);
96
97 if ((con->http.fd = accept(lis->fd, (struct sockaddr *)&(con->http.hostaddr), &val)) < 0)
98 {
99 LogMessage(LOG_ERROR, "accept() failed - %s.", strerror(errno));
100 return;
101 }
102
103 /*
104 * Get the hostname or format the IP address as needed...
105 */
106
107 address = ntohl(con->http.hostaddr.sin_addr.s_addr);
108
109 if (HostNameLookups)
110 host = gethostbyaddr(&address, sizeof(address), AF_INET);
111 else
112 host = NULL;
113
114 if (host == NULL)
115 sprintf(con->http.hostname, "%d.%d.%d.%d", (address >> 24) & 255,
116 (address >> 16) & 255, (address >> 8) & 255, address & 255);
117 else
118 strncpy(con->http.hostname, host->h_name, sizeof(con->http.hostname) - 1);
119
120 LogMessage(LOG_INFO, "accept() %d from %s.", con->http.fd, con->http.hostname);
121
122 /*
123 * Add the socket to the select() input mask.
124 */
125
126 fcntl(con->http.fd, F_SETFD, fcntl(con->http.fd, F_GETFD) | FD_CLOEXEC);
127
128 FD_SET(con->http.fd, &InputSet);
129
130 NumClients ++;
131
132 /*
133 * Temporarily suspend accept()'s until we lose a client...
134 */
135
136 if (NumClients == MAX_CLIENTS)
137 for (i = 0; i < NumListeners; i ++)
138 FD_CLR(Listeners[i].fd, &InputSet);
139}
140
141
142/*
143 * 'CloseAllClients()' - Close all remote clients immediately.
144 */
145
146void
147CloseAllClients(void)
148{
149 while (NumClients > 0)
150 CloseClient(Clients);
151}
152
153
154/*
155 * 'CloseClient()' - Close a remote client.
156 */
157
158void
159CloseClient(client_t *con) /* I - Client to close */
160{
161 int i; /* Looping var */
162 int status; /* Exit status of pipe command */
163
164
165 LogMessage(LOG_INFO, "CloseClient() %d", con->http.fd);
166
167 /*
168 * Close the socket and clear the file from the input set for select()...
169 */
170
171#if defined(WIN32) || defined(__EMX__)
172 /*
173 * Microsoft Windows and IBM OS/2 don't have a unified IO system...
174 */
175
176 closesocket(con->http.fd);
177#else
178 /*
179 * UNIX *does*....
180 */
181
182 close(con->http.fd);
183#endif /* WIN32 || __EMX__ */
184
185 for (i = 0; i < NumListeners; i ++)
186 FD_SET(Listeners[i].fd, &InputSet);
187
188 FD_CLR(con->http.fd, &InputSet);
189 if (con->pipe_pid != 0)
190 FD_CLR(con->file, &InputSet);
191 FD_CLR(con->http.fd, &OutputSet);
192
193 /*
194 * If we have a data file open, close it...
195 */
196
197 if (con->file > 0)
198 {
199 if (con->pipe_pid)
200 {
201 kill(con->pipe_pid, SIGKILL);
202 waitpid(con->pipe_pid, &status, WNOHANG);
203 }
204
205 close(con->file);
206 }
207
208 /*
209 * Compact the list of clients as necessary...
210 */
211
212 NumClients --;
213
214 if (con < (Clients + NumClients))
215 memcpy(con, con + 1, (Clients + NumClients - con) * sizeof(client_t));
216}
217
218
219/*
220 * 'ReadClient()' - Read data from a client.
221 */
222
223int /* O - 1 on success, 0 on error */
224ReadClient(client_t *con) /* I - Client to read from */
225{
226 char line[8192], /* Line from client... */
227 operation[64], /* Operation code from socket */
228 version[64]; /* HTTP version number string */
229 int major, minor; /* HTTP version numbers */
230 http_status_t status; /* Transfer status */
231 int bytes; /* Number of bytes to POST */
232 char *filename, /* Name of file for GET/HEAD */
233 *extension, /* Extension of file */
234 *type; /* MIME type */
235 struct stat filestats; /* File information */
b14d90ba 236 char command[1024], /* Command to run */
237 *options; /* Options/CGI data */
a74b005d 238
239
240 status = HTTP_CONTINUE;
241
242 switch (con->http.state)
243 {
244 case HTTP_WAITING :
245 /*
246 * See if we've received a request line...
247 */
248
249 if (httpGets(line, sizeof(line) - 1, HTTP(con)) == NULL)
250 break;
251
252 /*
253 * Ignore blank request lines...
254 */
255
256 if (line[0] == '\0')
257 break;
258
259 /*
260 * Clear other state variables...
261 */
262
263 httpClearFields(HTTP(con));
264
265 con->http.activity = time(NULL);
266 con->http.version = HTTP_1_0;
267 con->http.keep_alive = HTTP_KEEPALIVE_OFF;
268 con->http.data_encoding = HTTP_ENCODE_LENGTH;
269 con->http.data_remaining = 0;
6a0c519d 270 con->operation = HTTP_WAITING;
271 con->bytes = 0;
a74b005d 272 con->file = 0;
273 con->pipe_pid = 0;
274 con->username[0] = '\0';
275 con->password[0] = '\0';
276 con->uri[0] = '\0';
277
278 if (con->language != NULL)
279 {
280 cupsLangFree(con->language);
281 con->language = NULL;
282 }
283
284 /*
285 * Grab the request line...
286 */
287
288 switch (sscanf(line, "%63s%1023s%s", operation, con->uri, version))
289 {
290 case 1 :
291 SendError(con, HTTP_BAD_REQUEST);
292 CloseClient(con);
293 return (0);
294 case 2 :
295 con->http.version = HTTP_0_9;
296 break;
297 case 3 :
298 if (sscanf(version, "HTTP/%d.%d", &major, &minor) != 2)
299 {
300 SendError(con, HTTP_BAD_REQUEST);
301 CloseClient(con);
302 return (0);
303 }
304
305 if (major < 2)
306 {
307 con->http.version = (http_version_t)(major * 100 + minor);
308 if (con->http.version == HTTP_1_1)
309 con->http.keep_alive = HTTP_KEEPALIVE_ON;
310 else
311 con->http.keep_alive = HTTP_KEEPALIVE_OFF;
312 }
313 else
314 {
315 SendError(con, HTTP_NOT_SUPPORTED);
316 CloseClient(con);
317 return (0);
318 }
319 break;
320 }
321
322 /*
323 * Process the request...
324 */
325
326 if (strcmp(operation, "GET") == 0)
327 con->http.state = HTTP_GET;
328 else if (strcmp(operation, "PUT") == 0)
329 con->http.state = HTTP_PUT;
330 else if (strcmp(operation, "POST") == 0)
331 con->http.state = HTTP_POST;
332 else if (strcmp(operation, "DELETE") == 0)
333 con->http.state = HTTP_DELETE;
334 else if (strcmp(operation, "TRACE") == 0)
335 con->http.state = HTTP_TRACE;
336 else if (strcmp(operation, "CLOSE") == 0)
337 con->http.state = HTTP_CLOSE;
338 else if (strcmp(operation, "OPTIONS") == 0)
339 con->http.state = HTTP_OPTIONS;
340 else if (strcmp(operation, "HEAD") == 0)
341 con->http.state = HTTP_HEAD;
342 else
343 {
344 SendError(con, HTTP_BAD_REQUEST);
345 CloseClient(con);
346 return (0);
347 }
348
6a0c519d 349 con->start = time(NULL);
350 con->operation = con->http.state;
351
4a64fdb7 352 LogMessage(LOG_INFO, "ReadClient() %d %s %s HTTP/%d.%d", con->http.fd,
353 operation, con->uri,
354 con->http.version / 100, con->http.version % 100);
a74b005d 355
356 con->http.status = HTTP_OK;
357 break;
358
359 case HTTP_CLOSE :
360 case HTTP_DELETE :
361 case HTTP_GET :
362 case HTTP_HEAD :
363 case HTTP_POST :
364 case HTTP_PUT :
365 case HTTP_TRACE :
366 /*
367 * Parse incoming parameters until the status changes...
368 */
369
370 status = httpUpdate(HTTP(con));
371
372 if (status != HTTP_OK && status != HTTP_CONTINUE)
373 {
374 SendError(con, HTTP_BAD_REQUEST);
375 CloseClient(con);
376 return (0);
377 }
378 break;
379 }
380
381 /*
382 * Handle new transfers...
383 */
384
385 if (status == HTTP_OK)
386 {
387 con->language = cupsLangGet(con->http.fields[HTTP_FIELD_ACCEPT_LANGUAGE]);
388
389 decode_basic_auth(con);
390
391 if (con->http.fields[HTTP_FIELD_HOST][0] == '\0' &&
392 con->http.version >= HTTP_1_0)
393 {
394 if (!SendError(con, HTTP_BAD_REQUEST))
395 {
396 CloseClient(con);
397 return (0);
398 }
399 }
400 else if (strncmp(con->uri, "..", 2) == 0)
401 {
402 /*
403 * Protect against malicious users!
404 */
405
406 if (!SendError(con, HTTP_FORBIDDEN))
407 {
408 CloseClient(con);
409 return (0);
410 }
411 }
412 else if (con->uri[0] != '/')
413 {
414 /*
415 * Don't allow proxying (yet)...
416 */
417
418 if (!SendError(con, HTTP_METHOD_NOT_ALLOWED))
419 {
420 CloseClient(con);
421 return (0);
422 }
423 }
424 else if ((status = IsAuthorized(con)) != HTTP_OK)
425 {
426 if (!SendError(con, status))
427 {
428 CloseClient(con);
429 return (0);
430 }
431 }
432 else switch (con->http.state)
433 {
434 case HTTP_GET_SEND :
b14d90ba 435 if (strncmp(con->uri, "/printers", 9) == 0 ||
436 strncmp(con->uri, "/classes", 8) == 0 ||
437 strncmp(con->uri, "/jobs", 5) == 0)
a74b005d 438 {
439 /*
b14d90ba 440 * Send CGI output...
a74b005d 441 */
442
b14d90ba 443 if (strncmp(con->uri, "/printers", 9) == 0)
444 {
445 sprintf(command, "%s/cgi-bin/printers", ServerRoot);
446 options = con->uri + 9;
447 }
448 else if (strncmp(con->uri, "/classes", 8) == 0)
449 {
450 sprintf(command, "%s/cgi-bin/classes", ServerRoot);
451 options = con->uri + 8;
452 }
453 else
454 {
455 sprintf(command, "%s/cgi-bin/jobs", ServerRoot);
456 options = con->uri + 5;
457 }
458
459 if (*options == '/')
460 options ++;
461
462 if (!SendCommand(con, command, options))
a74b005d 463 {
464 if (!SendError(con, HTTP_NOT_FOUND))
465 {
466 CloseClient(con);
467 return (0);
468 }
469 }
6a0c519d 470 else
471 LogRequest(con, HTTP_OK);
a74b005d 472
b14d90ba 473 if (con->http.version <= HTTP_1_0)
a74b005d 474 con->http.keep_alive = HTTP_KEEPALIVE_OFF;
475 }
476 else
477 {
478 /*
479 * Serve a file...
480 */
481
482 if ((filename = get_file(con, &filestats)) == NULL)
483 {
484 if (!SendError(con, HTTP_NOT_FOUND))
485 {
486 CloseClient(con);
487 return (0);
488 }
489 }
490 else if (!check_if_modified(con, &filestats))
491 {
492 if (!SendError(con, HTTP_NOT_MODIFIED))
493 {
494 CloseClient(con);
495 return (0);
496 }
497 }
498 else
499 {
500 extension = get_extension(filename);
501 type = get_type(extension);
502
503 if (!SendFile(con, HTTP_OK, filename, type, &filestats))
504 {
505 CloseClient(con);
506 return (0);
507 }
508 }
509 }
510 break;
511
512 case HTTP_POST_RECV :
b14d90ba 513 /*
514 * See what kind of POST request this is; for IPP requests the
515 * content-type field will be "application/ipp"...
516 */
517
518 if (strcmp(con->http.fields[HTTP_FIELD_CONTENT_TYPE], "application/ipp") == 0)
519 {
520#if 0
521 sprintf(con->filename, "%s/requests/XXXXXX", ServerRoot);
522 con->file = mkstemp(con->filename);
a74b005d 523
b14d90ba 524 LogMessage(LOG_INFO, "ReadClient() %d REQUEST %s", con->http.fd,
525 con->filename);
a74b005d 526
b14d90ba 527 if (con->file < 0)
528 {
529 if (!SendError(con, HTTP_REQUEST_TOO_LARGE))
530 {
531 CloseClient(con);
532 return (0);
533 }
534 }
535#else
536 con->request = ippNew();
537#endif /* 0 */
538 }
539 else if (strcmp(con->http.fields[HTTP_FIELD_CONTENT_TYPE], "application/ipp") == 0 &&
540 (strncmp(con->uri, "/printers", 9) == 0 ||
541 strncmp(con->uri, "/classes", 8) == 0 ||
542 strncmp(con->uri, "/jobs", 5) == 0))
a74b005d 543 {
b14d90ba 544 /*
545 * CGI request...
546 */
547
548 if (strncmp(con->uri, "/printers", 9) == 0)
a74b005d 549 {
b14d90ba 550 sprintf(command, "%s/cgi-bin/printers", ServerRoot);
551 options = con->uri + 9;
552 }
553 else if (strncmp(con->uri, "/classes", 8) == 0)
554 {
555 sprintf(command, "%s/cgi-bin/classes", ServerRoot);
556 options = con->uri + 8;
557 }
558 else
559 {
560 sprintf(command, "%s/cgi-bin/jobs", ServerRoot);
561 options = con->uri + 5;
a74b005d 562 }
b14d90ba 563
564 if (*options == '/')
565 options ++;
566
567 if (!SendCommand(con, command, options))
568 {
569 if (!SendError(con, HTTP_NOT_FOUND))
570 {
571 CloseClient(con);
572 return (0);
573 }
574 }
575 else
576 LogRequest(con, HTTP_OK);
577
578 if (con->http.version <= HTTP_1_0)
579 con->http.keep_alive = HTTP_KEEPALIVE_OFF;
580 }
581 else if (!SendError(con, HTTP_UNAUTHORIZED))
582 {
583 CloseClient(con);
584 return (0);
a74b005d 585 }
586 break;
587
588 case HTTP_PUT_RECV :
589 case HTTP_DELETE :
590 case HTTP_TRACE :
591 SendError(con, HTTP_NOT_IMPLEMENTED);
592
593 case HTTP_CLOSE :
594 CloseClient(con);
595 return (0);
596
597 case HTTP_HEAD :
b14d90ba 598 if (strncmp(con->uri, "/printers/", 10) == 0 ||
599 strncmp(con->uri, "/classes/", 9) == 0 ||
600 strncmp(con->uri, "/jobs/", 6) == 0)
a74b005d 601 {
602 /*
b14d90ba 603 * CGI output...
a74b005d 604 */
605
606 if (!SendHeader(con, HTTP_OK, "text/html"))
607 {
608 CloseClient(con);
609 return (0);
610 }
611
612 if (httpPrintf(HTTP(con), "\r\n") < 0)
613 {
614 CloseClient(con);
615 return (0);
616 }
6a0c519d 617
618 LogRequest(con, HTTP_OK);
a74b005d 619 }
620 else if ((filename = get_file(con, &filestats)) == NULL)
621 {
622 if (!SendHeader(con, HTTP_NOT_FOUND, "text/html"))
623 {
624 CloseClient(con);
625 return (0);
626 }
6a0c519d 627
628 LogRequest(con, HTTP_NOT_FOUND);
a74b005d 629 }
630 else if (!check_if_modified(con, &filestats))
631 {
632 if (!SendError(con, HTTP_NOT_MODIFIED))
633 {
634 CloseClient(con);
635 return (0);
636 }
6a0c519d 637
638 LogRequest(con, HTTP_NOT_MODIFIED);
a74b005d 639 }
640 else
641 {
642 /*
643 * Serve a file...
644 */
645
646 extension = get_extension(filename);
647 type = get_type(extension);
648
649 if (!SendHeader(con, HTTP_OK, type))
650 {
651 CloseClient(con);
652 return (0);
653 }
654
655 if (httpPrintf(HTTP(con), "Last-Modified: %s\r\n",
656 httpGetDateString(filestats.st_mtime)) < 0)
657 {
658 CloseClient(con);
659 return (0);
660 }
661
662 if (httpPrintf(HTTP(con), "Content-Length: %d\r\n",
663 filestats.st_size) < 0)
664 {
665 CloseClient(con);
666 return (0);
667 }
6a0c519d 668
669 LogRequest(con, HTTP_OK);
a74b005d 670 }
671
672 if (send(con->http.fd, "\r\n", 2, 0) < 0)
673 {
674 CloseClient(con);
675 return (0);
676 }
677
678 con->http.state = HTTP_WAITING;
679 break;
680 }
681 }
682
683 /*
684 * Handle any incoming data...
685 */
686
687 switch (con->http.state)
688 {
689 case HTTP_PUT_RECV :
690 break;
691
692 case HTTP_POST_RECV :
693 LogMessage(LOG_DEBUG, "ReadClient() %d con->data_encoding = %s con->data_remaining = %d\n",
694 con->http.data_encoding == HTTP_ENCODE_CHUNKED ? "chunked" : "length",
695 con->http.data_remaining);
696
b14d90ba 697 if (con->request != NULL)
698 {
699 /*
700 * Grab any request data from the connection...
701 */
702
703 if (ippRead(&(con->http), con->request) != IPP_DATA)
704 break;
705
706 /*
707 * Then create a file as needed for the request data...
708 */
709
710 if (con->file == 0 &&
711 (con->http.data_remaining > 0 ||
712 con->http.data_encoding == HTTP_ENCODE_CHUNKED))
713 {
714 sprintf(con->filename, "%s/requests/XXXXXX", ServerRoot);
715 con->file = mkstemp(con->filename);
716
717 LogMessage(LOG_INFO, "ReadClient() %d REQUEST %s", con->http.fd,
718 con->filename);
719
720 if (con->file < 0)
721 {
722 if (!SendError(con, HTTP_REQUEST_TOO_LARGE))
723 {
724 CloseClient(con);
725 return (0);
726 }
727 }
728 }
729 }
730
a74b005d 731 if ((bytes = httpRead(HTTP(con), line, sizeof(line))) < 0)
732 {
733 CloseClient(con);
734 return (0);
735 }
736 else if (con->http.state == HTTP_POST_SEND)
737 {
738 close(con->file);
739
b14d90ba 740 if (con->request)
a74b005d 741 {
b14d90ba 742 /*
743 * Add IPP processing/response stuff...
744 */
a74b005d 745
b14d90ba 746 if (!SendError(con, HTTP_ACCEPTED))
747 {
748 CloseClient(con);
749 return (0);
750 }
751 }
a74b005d 752 }
753 else
754 {
6a0c519d 755 con->bytes += bytes;
756
a74b005d 757 LogMessage(LOG_DEBUG, "ReadClient() %d writing %d bytes", bytes);
758
759 if (write(con->file, line, bytes) < bytes)
760 {
761 close(con->file);
762 unlink(con->filename);
763
764 if (!SendError(con, HTTP_REQUEST_TOO_LARGE))
765 {
766 CloseClient(con);
767 return (0);
768 }
769 }
770 }
771 break;
772 }
773
774 if (!con->http.keep_alive && con->http.state == HTTP_WAITING)
775 {
776 CloseClient(con);
777 return (0);
778 }
779 else
780 return (1);
781}
782
783
a74b005d 784/*
785 * 'SendCommand()' - Send output from a command via HTTP.
786 */
787
788int
789SendCommand(client_t *con,
a74b005d 790 char *command,
b14d90ba 791 char *options)
a74b005d 792{
b14d90ba 793 con->pipe_pid = pipe_command(con, 0, &(con->file), command, options);
a74b005d 794
795 LogMessage(LOG_DEBUG, "SendCommand() %d command=\"%s\" file=%d pipe_pid=%d",
796 con->http.fd, command, con->file, con->pipe_pid);
797
798 if (con->pipe_pid == 0)
799 return (0);
800
801 fcntl(con->file, F_SETFD, fcntl(con->file, F_GETFD) | FD_CLOEXEC);
802
803 FD_SET(con->file, &InputSet);
804 FD_SET(con->http.fd, &OutputSet);
805
b14d90ba 806 if (!SendHeader(con, HTTP_OK, "text/html"))
a74b005d 807 return (0);
808
809 if (con->http.version == HTTP_1_1)
810 {
811 con->http.data_encoding = HTTP_ENCODE_CHUNKED;
812
813 if (httpPrintf(HTTP(con), "Transfer-Encoding: chunked\r\n") < 0)
814 return (0);
815 }
816
817 if (httpPrintf(HTTP(con), "\r\n") < 0)
818 return (0);
819
820 return (1);
821}
822
823
824/*
825 * 'SendError()' - Send an error message via HTTP.
826 */
827
828int /* O - 1 if successful, 0 otherwise */
829SendError(client_t *con, /* I - Connection */
830 http_status_t code) /* I - Error code */
831{
832 char message[1024]; /* Text version of error code */
833
834
6a0c519d 835 /*
836 * Put the request in the access_log file...
837 */
838
839 LogRequest(con, code);
840
a74b005d 841 /*
842 * To work around bugs in some proxies, don't use Keep-Alive for some
843 * error messages...
844 */
845
846 if (code >= HTTP_BAD_REQUEST)
847 con->http.keep_alive = HTTP_KEEPALIVE_OFF;
848
849 /*
850 * Send an error message back to the client. If the error code is a
851 * 400 or 500 series, make sure the message contains some text, too!
852 */
853
854 if (!SendHeader(con, code, NULL))
855 return (0);
856
857 if (code == HTTP_UNAUTHORIZED)
858 {
859 if (httpPrintf(HTTP(con), "WWW-Authenticate: Basic realm=\"CUPS\"\r\n") < 0)
860 return (0);
861 }
862
863 if (con->http.version >= HTTP_1_1 && !con->http.keep_alive)
864 {
865 if (httpPrintf(HTTP(con), "Connection: close\r\n") < 0)
866 return (0);
867 }
868
869 if (code >= HTTP_BAD_REQUEST)
870 {
871 /*
872 * Send a human-readable error message.
873 */
874
875 sprintf(message, "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>"
876 "<BODY><H1>%s</H1>%s</BODY></HTML>\n",
877 code, httpStatus(code), httpStatus(code),
b14d90ba 878 con->language ? (char *)con->language->messages[code] : httpStatus(code));
a74b005d 879
880 if (httpPrintf(HTTP(con), "Content-Type: text/html\r\n") < 0)
881 return (0);
882 if (httpPrintf(HTTP(con), "Content-Length: %d\r\n", strlen(message)) < 0)
883 return (0);
884 if (httpPrintf(HTTP(con), "\r\n") < 0)
885 return (0);
886 if (send(con->http.fd, message, strlen(message), 0) < 0)
887 return (0);
888 }
889 else if (httpPrintf(HTTP(con), "\r\n") < 0)
890 return (0);
891
892 con->http.state = HTTP_WAITING;
893
894 return (1);
895}
896
897
898/*
899 * 'SendFile()' - Send a file via HTTP.
900 */
901
902int
903SendFile(client_t *con,
904 http_status_t code,
905 char *filename,
906 char *type,
907 struct stat *filestats)
908{
909 con->file = open(filename, O_RDONLY);
910
911 LogMessage(LOG_DEBUG, "SendFile() %d file=%d", con->http.fd, con->file);
912
913 if (con->file < 0)
914 return (0);
915
916 fcntl(con->file, F_SETFD, fcntl(con->file, F_GETFD) | FD_CLOEXEC);
917
918 con->pipe_pid = 0;
919
920 if (!SendHeader(con, code, type))
921 return (0);
922
923 if (httpPrintf(HTTP(con), "Last-Modified: %s\r\n", httpGetDateString(filestats->st_mtime)) < 0)
924 return (0);
925 if (httpPrintf(HTTP(con), "Content-Length: %d\r\n", filestats->st_size) < 0)
926 return (0);
927 if (httpPrintf(HTTP(con), "\r\n") < 0)
928 return (0);
929
930 FD_SET(con->http.fd, &OutputSet);
931
932 return (1);
933}
934
935
936/*
937 * 'SendHeader()' - Send an HTTP header.
938 */
939
940int /* O - 1 on success, 0 on failure */
941SendHeader(client_t *con, /* I - Client to send to */
942 http_status_t code, /* I - HTTP status code */
943 char *type) /* I - MIME type of document */
944{
945 if (httpPrintf(HTTP(con), "HTTP/%d.%d %d %s\r\n", con->http.version / 100,
946 con->http.version % 100, code, httpStatus(code)) < 0)
947 return (0);
948 if (httpPrintf(HTTP(con), "Date: %s\r\n", httpGetDateString(time(NULL))) < 0)
949 return (0);
950 if (httpPrintf(HTTP(con), "Server: CUPS/1.0\r\n") < 0)
951 return (0);
952 if (con->http.keep_alive && con->http.version >= HTTP_1_0)
953 {
954 if (httpPrintf(HTTP(con), "Connection: Keep-Alive\r\n") < 0)
955 return (0);
956 if (httpPrintf(HTTP(con), "Keep-Alive: timeout=%d\r\n", KeepAliveTimeout) < 0)
957 return (0);
958 }
959 if (con->language != NULL)
960 {
961 if (httpPrintf(HTTP(con), "Content-Language: %s\r\n",
962 con->language->language) < 0)
963 return (0);
964
965 if (type != NULL)
966 if (httpPrintf(HTTP(con), "Content-Type: %s; charset=%s\r\n", type,
967 cupsLangEncoding(con->language)) < 0)
968 return (0);
969 }
970 else if (type != NULL)
971 if (httpPrintf(HTTP(con), "Content-Type: %s\r\n", type) < 0)
972 return (0);
973
974 return (1);
975}
976
977
978/*
979 * 'StartListening()' - Create all listening sockets...
980 */
981
982void
983StartListening(void)
984{
985 int i, /* Looping var */
986 val; /* Parameter value */
987 listener_t *lis; /* Current listening socket */
988
989
990#if defined(WIN32) || defined(__EMX__)
991 /*
992 * Initialize Windows sockets... This handles loading the DLL, etc. since
993 * Windows doesn't have built-in support for sockets like UNIX...
994 */
995
996 WSADATA wsadata; /* Socket data for application from DLL */
997 static int initialized = 0;/* Whether or not we've initialized things */
998
999
1000 if (!initialized)
1001 {
1002 initialized = 1;
1003
1004 if (WSAStartup(MAKEWORD(1,1), &wsadata) != 0)
1005 exit(1);
1006 }
1007#else
1008 /*
1009 * Setup a 'broken pipe' signal handler for lost clients.
1010 */
1011
1012 sigset(SIGPIPE, sigpipe_handler);
1013#endif /* !WIN32 */
1014
1015 /*
1016 * Setup socket listeners...
1017 */
1018
1019 for (i = NumListeners, lis = Listeners; i > 0; i --, lis ++)
1020 {
1021 LogMessage(LOG_INFO, "StartListening() address=%08x port=%d",
1022 ntohl(lis->address.sin_addr.s_addr),
1023 ntohs(lis->address.sin_port));
1024
1025 if ((lis->fd = socket(AF_INET, SOCK_STREAM, PF_UNSPEC)) == -1)
1026 {
1027 LogMessage(LOG_ERROR, "StartListening() Unable to open listen socket - %s.",
1028 strerror(errno));
1029 exit(errno);
1030 }
1031
1032 fcntl(lis->fd, F_SETFD, fcntl(lis->fd, F_GETFD) | FD_CLOEXEC);
1033
1034 /*
1035 * Set things up to reuse the local address for this port.
1036 */
1037
1038 val = 1;
1039 setsockopt(lis->fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
1040
1041 /*
1042 * Bind to the port we found...
1043 */
1044
1045 if (bind(lis->fd, (struct sockaddr *)&(lis->address), sizeof(lis->address)) < 0)
1046 {
1047 LogMessage(LOG_ERROR, "StartListening() Unable to bind socket - %s.", strerror(errno));
1048 exit(errno);
1049 }
1050
1051 /*
1052 * Listen for new clients.
1053 */
1054
1055 if (listen(lis->fd, SOMAXCONN) < 0)
1056 {
1057 LogMessage(LOG_ERROR, "StartListening() Unable to listen for clients - %s.",
1058 strerror(errno));
1059 exit(errno);
1060 }
1061
1062 /*
1063 * Setup the select() input mask to contain the listening socket we have.
1064 */
1065
1066 FD_SET(lis->fd, &InputSet);
1067 }
1068
1069 LogMessage(LOG_INFO, "StartListening() NumListeners=%d", NumListeners);
1070}
1071
1072
1073/*
1074 * 'StopListening()' - Close all listening sockets...
1075 */
1076
1077void
1078StopListening(void)
1079{
1080 int i; /* Looping var */
1081 listener_t *lis; /* Current listening socket */
1082
1083
1084 for (i = NumListeners, lis = Listeners; i > 0; i --, lis ++)
1085 {
1086#if defined(WIN32) || defined(__EMX__)
1087 closesocket(lis->fd);
1088#else
1089 close(lis->fd);
1090#endif /* WIN32 || __EMX__ */
1091
1092 FD_CLR(lis->fd, &InputSet);
1093 }
1094
1095 LogMessage(LOG_INFO, "StopListening()");
1096}
1097
1098
1099/*
1100 * 'WriteClient()' - Write data to a client as needed.
1101 */
1102
1103int
1104WriteClient(client_t *con)
1105{
1106 int bytes;
1107 int status;
1108 char buf[HTTP_MAX_BUFFER];
1109
1110
1111 if (con->http.state != HTTP_GET_SEND &&
1112 con->http.state != HTTP_POST_SEND)
1113 return (1);
1114
b14d90ba 1115 if (con->response != NULL)
1116 bytes = ippWrite(&(con->http), con->response) != IPP_DATA;
1117 else if ((bytes = read(con->file, buf, sizeof(buf))) > 0)
a74b005d 1118 {
1119 if (httpWrite(HTTP(con), buf, bytes) < 0)
1120 {
1121 CloseClient(con);
1122 return (0);
1123 }
6a0c519d 1124
1125 con->bytes += bytes;
a74b005d 1126 }
b14d90ba 1127
1128 if (bytes <= 0)
a74b005d 1129 {
6a0c519d 1130 LogRequest(con, HTTP_OK);
1131
a74b005d 1132 if (con->http.data_encoding == HTTP_ENCODE_CHUNKED)
1133 {
1134 if (httpPrintf(HTTP(con), "0\r\n\r\n") < 0)
1135 {
1136 CloseClient(con);
1137 return (0);
1138 }
1139 }
1140
1141 FD_CLR(con->http.fd, &OutputSet);
1142 FD_CLR(con->file, &InputSet);
1143
1144 if (con->pipe_pid)
1145 {
1146 kill(con->pipe_pid, SIGKILL);
1147 waitpid(con->pipe_pid, &status, WNOHANG);
1148 }
1149
1150 close(con->file);
1151
1152 if (!con->http.keep_alive)
1153 {
1154 CloseClient(con);
1155 return (0);
1156 }
1157
1158 con->http.state = HTTP_WAITING;
1159 con->file = 0;
1160 con->pipe_pid = 0;
b14d90ba 1161
1162 if (con->request != NULL)
1163 {
1164 ippDelete(con->request);
1165 con->request = NULL;
1166 }
1167
1168 if (con->response != NULL)
1169 {
1170 ippDelete(con->response);
1171 con->response = NULL;
1172 }
a74b005d 1173 }
1174
1175 LogMessage(LOG_DEBUG, "WriteClient() %d %d bytes", con->http.fd, bytes);
1176
1177 con->http.activity = time(NULL);
1178
1179 return (1);
1180}
1181
1182
1183/*
1184 * 'decode_basic_auth()' - Decode a Basic authorization string.
1185 */
1186
1187static void
1188decode_basic_auth(client_t *con) /* I - Client to decode to */
1189{
1190 char value[1024]; /* Value string */
1191
1192
1193 /*
1194 * Decode the string and pull the username and password out...
1195 */
1196
1197 httpDecode64(value, con->http.fields[HTTP_FIELD_AUTHORIZATION]);
1198
1199 LogMessage(LOG_DEBUG, "decode_basic_auth() %d Authorization=\"%s\"",
1200 con->http.fd, value);
1201
1202 sscanf(value, "%[^:]:%[^\n]", con->username, con->password);
1203}
1204
1205
1206/*
1207 * 'check_if_modified()' - Decode an "If-Modified-Since" line.
1208 */
1209
1210static int /* O - 1 if modified since */
1211check_if_modified(client_t *con, /* I - Client connection */
1212 struct stat *filestats) /* I - File information */
1213{
1214 char *ptr; /* Pointer into field */
1215 time_t date; /* Time/date value */
1216 int size; /* Size/length value */
1217
1218
1219 size = 0;
1220 date = 0;
1221 ptr = con->http.fields[HTTP_FIELD_IF_MODIFIED_SINCE];
1222
1223 if (*ptr == '\0')
1224 return (1);
1225
4a64fdb7 1226 LogMessage(LOG_DEBUG, "check_if_modified() %d If-Modified-Since=\"%s\"",
1227 con->http.fd, ptr);
1228
a74b005d 1229 while (*ptr != '\0')
1230 {
4a64fdb7 1231 while (isspace(*ptr) || *ptr == ';')
a74b005d 1232 ptr ++;
1233
1234 if (strncasecmp(ptr, "length=", 7) == 0)
1235 {
1236 ptr += 7;
1237 size = atoi(ptr);
1238
1239 while (isdigit(*ptr))
1240 ptr ++;
1241 }
4a64fdb7 1242 else if (isalpha(*ptr))
a74b005d 1243 {
1244 date = httpGetDateTime(ptr);
4a64fdb7 1245 while (*ptr != '\0' && *ptr != ';')
a74b005d 1246 ptr ++;
1247 }
1248 }
1249
4a64fdb7 1250 LogMessage(LOG_DEBUG, "check_if_modified() %d sizes=%d,%d dates=%d,%d",
1251 con->http.fd, size, filestats->st_size, date, filestats->st_mtime);
1252
a74b005d 1253 return ((size != filestats->st_size && size != 0) ||
4a64fdb7 1254 (date < filestats->st_mtime && date != 0) ||
1255 (size == 0 && date == 0));
a74b005d 1256}
1257
1258
1259/*
1260 * 'get_extension()' - Get the extension for a filename.
1261 */
1262
1263static char * /* O - Pointer to extension */
1264get_extension(char *filename) /* I - Filename or URI */
1265{
1266 char *ext; /* Pointer to extension */
1267
1268
1269 ext = filename + strlen(filename) - 1;
1270
1271 while (ext > filename && *ext != '/')
1272 if (*ext == '.')
1273 return (ext + 1);
1274 else
1275 ext --;
1276
1277 return ("");
1278}
1279
1280
1281/*
1282 * 'get_file()' - Get a filename and state info.
1283 */
1284
1285static char * /* O - Real filename */
1286get_file(client_t *con, /* I - Client connection */
1287 struct stat *filestats)/* O - File information */
1288{
1289 int status; /* Status of filesystem calls */
1290 char *params; /* Pointer to parameters in URI */
1291 static char filename[1024]; /* Filename buffer */
1292
1293
1294 /*
1295 * Need to add DocumentRoot global...
1296 */
1297
1298 if (con->language != NULL)
1299 sprintf(filename, "%s/%s%s", DocumentRoot, con->language->language,
1300 con->uri);
1301 else
1302 sprintf(filename, "%s%s", DocumentRoot, con->uri);
1303
1304 if ((params = strchr(filename, '?')) != NULL)
1305 *params = '\0';
1306
1307 /*
1308 * Grab the status for this language; if there isn't a language-specific file
1309 * then fallback to the default one...
1310 */
1311
1312 if ((status = stat(filename, filestats)) != 0 && con->language != NULL)
1313 {
1314 /*
1315 * Drop the language prefix and try the current directory...
1316 */
1317
1318 sprintf(filename, "%s%s", DocumentRoot, con->uri);
1319
1320 status = stat(filename, filestats);
1321 }
1322
1323 /*
1324 * If we're found a directory, get the index.html file instead...
1325 */
1326
1327 if (!status && S_ISDIR(filestats->st_mode))
1328 {
1329 if (filename[strlen(filename) - 1] == '/')
1330 strcat(filename, "index.html");
1331 else
1332 strcat(filename, "/index.html");
1333
1334 status = stat(filename, filestats);
1335 }
1336
1337 LogMessage(LOG_DEBUG, "get_filename() %d filename=%s size=%d",
1338 con->http.fd, filename, filestats->st_size);
1339
1340 if (status)
1341 return (NULL);
1342 else
1343 return (filename);
1344}
1345
1346
1347/*
1348 * 'get_type()' - Get MIME type from the given extension.
1349 */
1350
1351static char *
1352get_type(char *extension)
1353{
1354 if (strcmp(extension, "html") == 0 || strcmp(extension, "htm") == 0)
1355 return ("text/html");
1356 else if (strcmp(extension, "txt") == 0)
1357 return ("text/plain");
1358 else if (strcmp(extension, "gif") == 0)
1359 return ("image/gif");
1360 else if (strcmp(extension, "jpg") == 0)
1361 return ("image/jpg");
1362 else if (strcmp(extension, "png") == 0)
1363 return ("image/png");
1364 else if (strcmp(extension, "ps") == 0)
1365 return ("application/postscript");
1366 else if (strcmp(extension, "pdf") == 0)
1367 return ("application/pdf");
1368 else if (strcmp(extension, "gz") == 0)
1369 return ("application/gzip");
1370 else
1371 return ("application/unknown");
1372}
1373
1374
1375/*
1376 * 'pipe_command()' - Pipe the output of a command to the remote client.
1377 */
1378
1379static int /* O - Process ID */
b14d90ba 1380pipe_command(client_t *con, /* I - Client connection */
1381 int infile, /* I - Standard input for command */
1382 int *outfile, /* O - Standard output for command */
1383 char *command, /* I - Command to run */
1384 char *options) /* I - Options for command */
a74b005d 1385{
1386 int pid; /* Process ID */
1387 char *commptr; /* Command string pointer */
1388 int fds[2]; /* Pipe FDs */
1389 int argc; /* Number of arguments */
1390 char argbuf[1024], /* Argument buffer */
b14d90ba 1391 *argv[100], /* Argument strings */
1392 *envp[100]; /* Environment variables */
1393 static char lang[1024]; /* LANG env variable */
1394 static char content_length[1024]; /* CONTENT_LENGTH env variable */
1395 static char content_type[1024]; /* CONTENT_TYPE env variable */
1396 static char server_port[1024]; /* Default listen port */
1397 static char remote_host[1024]; /* REMOTE_HOST env variable */
1398 static char remote_user[1024]; /* REMOTE_HOST env variable */
a74b005d 1399
1400
1401 /*
1402 * Copy the command string...
1403 */
1404
b14d90ba 1405 strncpy(argbuf, options, sizeof(argbuf) - 1);
a74b005d 1406 argbuf[sizeof(argbuf) - 1] = '\0';
1407
1408 /*
1409 * Parse the string; arguments can be separated by spaces or by ? or +...
1410 */
1411
1412 argv[0] = argbuf;
1413
1414 for (commptr = argbuf, argc = 1; *commptr != '\0'; commptr ++)
1415 if (*commptr == ' ' || *commptr == '?' || *commptr == '+')
1416 {
1417 *commptr++ = '\0';
1418
1419 while (*commptr == ' ')
1420 commptr ++;
1421
1422 if (*commptr != '\0')
1423 {
1424 argv[argc] = commptr;
1425 argc ++;
1426 }
1427
1428 commptr --;
1429 }
1430 else if (*commptr == '%')
1431 {
1432 if (commptr[1] >= '0' && commptr[1] <= '9')
1433 *commptr = (commptr[1] - '0') << 4;
1434 else
1435 *commptr = (tolower(commptr[1]) - 'a' + 10) << 4;
1436
1437 if (commptr[2] >= '0' && commptr[2] <= '9')
1438 *commptr |= commptr[2] - '0';
1439 else
1440 *commptr |= tolower(commptr[2]) - 'a' + 10;
1441
1442 strcpy(commptr + 1, commptr + 3);
1443 }
1444
1445 argv[argc] = NULL;
1446
b14d90ba 1447 if (argv[1][0] == '\0')
1448 argv[1] = strrchr(command, '/') + 1;
1449
1450 /*
1451 * Setup the environment variables as needed...
1452 */
1453
1454 sprintf(lang, "LANG=%s", con->language ? con->language->language : "C");
1455 sprintf(server_port, "SERVER_PORT=%d", ntohs(con->http.hostaddr.sin_port));
1456 sprintf(remote_host, "REMOTE_HOST=%s", con->http.hostname);
1457 sprintf(remote_user, "REMOTE_USER=%s", con->username);
1458
1459 envp[0] = "PATH=/bin:/usr/bin";
1460 envp[1] = "SERVER_SOFTWARE=CUPS/1.0";
1461 envp[2] = "SERVER_NAME=localhost"; /* This isn't 100% correct... */
1462 envp[3] = "GATEWAY_INTERFACE=CGI/1.1";
1463 envp[4] = "SERVER_PROTOCOL=HTTP/1.1";
1464 envp[5] = server_port;
1465 envp[6] = remote_host;
1466 envp[7] = remote_user;
1467 envp[8] = lang;
1468 envp[9] = "TZ=GMT";
1469
1470 if (con->operation == HTTP_GET)
1471 {
1472 envp[10] = "REQUEST_METHOD=GET";
1473 envp[11] = NULL;
1474 }
1475 else
1476 {
1477 sprintf(content_length, "CONTENT_LENGTH=%d", con->http.data_remaining);
1478 sprintf(content_type, "CONTENT_TYPE=%s",
1479 con->http.fields[HTTP_FIELD_CONTENT_TYPE]);
1480
1481 envp[10] = "REQUEST_METHOD=POST";
1482 envp[11] = content_length;
1483 envp[12] = content_type;
1484 envp[13] = NULL;
1485 }
1486
a74b005d 1487 /*
1488 * Create a pipe for the output...
1489 */
1490
1491 if (pipe(fds))
1492 return (0);
1493
1494 /*
1495 * Then execute the pipe command...
1496 */
1497
1498 if ((pid = fork()) == 0)
1499 {
1500 /*
1501 * Child comes here... Close stdin if necessary and dup the pipe to stdout.
1502 */
1503
b14d90ba 1504 setuid(User);
1505 setgid(Group);
1506
a74b005d 1507 if (infile)
1508 {
1509 close(0);
1510 dup(infile);
1511 }
1512
1513 close(1);
1514 dup(fds[1]);
1515
1516 close(fds[0]);
1517 close(fds[1]);
1518
1519 /*
1520 * Execute the pipe program; if an error occurs, exit with status 1...
1521 */
1522
b14d90ba 1523 execve(command, argv, envp);
a74b005d 1524 exit(1);
1525 return (0);
1526 }
1527 else if (pid < 0)
1528 {
1529 /*
1530 * Error - can't fork!
1531 */
1532
1533 close(fds[0]);
1534 close(fds[1]);
1535 return (0);
1536 }
1537 else
1538 {
1539 /*
1540 * Fork successful - return the PID...
1541 */
1542
1543 *outfile = fds[0];
1544 close(fds[1]);
1545
1546 return (pid);
1547 }
1548}
1549
1550
1551/*
1552 * 'show_printer_status()' - Show the current printer status.
1553 */
1554
1555static int
1556show_printer_status(client_t *con)
1557{
1558 printer_t *p;
1559 char buffer[HTTP_MAX_BUFFER];
1560 static char *states[] =
1561 {
1562 "", "", "", "Idle", "Processing", "Stopped"
1563 };
1564
1565
1566 p = NULL;
1567 if (con->uri[10] != '\0')
1568 if ((p = FindPrinter(con->uri + 10)) == NULL)
1569 return (0);
1570
1571 if (!SendHeader(con, HTTP_OK, "text/html"))
1572 return (0);
1573
1574 if (con->http.version == HTTP_1_1)
1575 {
1576 con->http.data_encoding = HTTP_ENCODE_CHUNKED;
1577
1578 if (httpPrintf(HTTP(con), "Transfer-Encoding: chunked\r\n") < 0)
1579 return (0);
1580 }
1581
1582 if (httpPrintf(HTTP(con), "\r\n") < 0)
1583 return (0);
1584
1585 if (p != NULL)
1586 {
1587 /*
1588 * Send printer information...
1589 */
1590
1591 sprintf(buffer, "<HTML>\n"
1592 "<HEAD>\n"
1593 "\t<TITLE>Printer Status for %s</TITLE>\n"
1594 "\t<META HTTP-EQUIV=\"Refresh\" CONTENT=\"10\">\n"
1595 "</HEAD>\n"
1596 "<BODY BGCOLOR=#ffffff>\n"
1597 "<TABLE WIDTH=100%%><TR>\n"
1598 "\t<TD ALIGN=LEFTV ALIGN=MIDDLE><IMG SRC=\"/images/cups-small.gif\"></TD>\n"
1599 "\t<TD ALIGN=CENTER VALIGN=MIDDLE><H1>Printer Status for %s</H1></TD>\n"
1600 "\t<TD ALIGN=RIGHT VALIGN=MIDDLE><IMG SRC=\"/images/vendor.gif\"></TD>\n"
1601 "</TR></TABLE>\n"
1602 "<CENTER><TABLE BORDER=1 WIDTH=80%%>\n"
1603 "<TR><TH>Name</TH><TH>Value</TH></TR>\n"
1604 "<TR><TD>Info</TD><TD>%s</TD></TR>\n"
1605 "<TR><TD>MoreInfo</TD><TD>%s</TD></TR>\n"
1606 "<TR><TD>Location</TD><TD>%s</TD></TR>\n"
1607 "<TR><TD>Device URI</TD><TD>%s</TD></TR>\n"
1608 "<TR><TD>PPDFile</TD><TD>%s</TD></TR>\n",
1609 p->name, p->name, p->info, p->more_info,
1610 p->location, p->device_uri, p->ppd);
1611
6a0c519d 1612 con->bytes += httpWrite(HTTP(con), buffer, strlen(buffer));
a74b005d 1613 }
1614 else
1615 {
1616 strcpy(buffer, "<HTML>\n"
1617 "<HEAD>\n"
1618 "\t<TITLE>Available Printers</TITLE>\n"
1619 "\t<META HTTP-EQUIV=\"Refresh\" CONTENT=\"10\">\n"
1620 "</HEAD>\n"
1621 "<BODY BGCOLOR=#ffffff>\n"
1622 "<TABLE WIDTH=100%%><TR>\n"
1623 "\t<TD ALIGN=LEFT VALIGN=MIDDLE><IMG SRC=\"/images/cups-small.gif\"></TD>\n"
1624 "\t<TD ALIGN=CENTER VALIGN=MIDDLE><H1>Available Printers</H1></TD>\n"
1625 "\t<TD ALIGN=RIGHT VALIGN=MIDDLE><IMG SRC=\"/images/vendor.gif\"></TD>\n"
1626 "</TR></TABLE>\n"
1627 "<CENTER><TABLE BORDER=1 WIDTH=80%%>\n"
1628 "<TR><TH>Name</TH><TH>State</TH><TH>Info</TH></TR>\n");
1629
6a0c519d 1630 con->bytes += httpWrite(HTTP(con), buffer, strlen(buffer));
a74b005d 1631
1632 for (p = Printers; p != NULL; p = p->next)
1633 {
1634 sprintf(buffer, "<TR><TD><A HREF=/printers/%s>%s</A></TD><TD>%s</TD><TD>%s</TD></TR>\n",
1635 p->name, p->name, states[p->state], p->info);
6a0c519d 1636 con->bytes += httpWrite(HTTP(con), buffer, strlen(buffer));
a74b005d 1637 }
1638 }
1639
1640 strcpy(buffer, "</TABLE></CENTER>\n"
1641 "<HR>\n"
1642 "CUPS Copyright 1997-1999 by Easy Software Products, "
1643 "All Rights Reserved. CUPS and the CUPS logo are the "
1644 "trademark property of Easy Software Products.\n"
1645 "</BODY>\n"
1646 "</HTML>\n");
6a0c519d 1647 con->bytes += httpWrite(HTTP(con), buffer, strlen(buffer));
a74b005d 1648 httpWrite(HTTP(con), buffer, 0);
1649
1650 return (1);
1651}
1652
1653
1654/*
1655 * 'sigpipe_handler()' - Handle 'broken pipe' signals from lost network
1656 * clients.
1657 */
1658
1659static void
1660sigpipe_handler(int sig) /* I - Signal number */
1661{
1662/* IGNORE */
1663}
1664
1665
1666/*
b14d90ba 1667 * End of "$Id: client.c,v 1.4 1999/02/19 22:07:04 mike Exp $".
a74b005d 1668 */