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