]> git.ipfire.org Git - thirdparty/cups.git/blob - scheduler/cups-lpd.c
License change: Apache License, Version 2.0.
[thirdparty/cups.git] / scheduler / cups-lpd.c
1 /*
2 * Line Printer Daemon interface for CUPS.
3 *
4 * Copyright 2007-2016 by Apple Inc.
5 * Copyright 1997-2006 by Easy Software Products, all rights reserved.
6 *
7 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
8 */
9
10 /*
11 * Include necessary headers...
12 */
13
14 #define _CUPS_NO_DEPRECATED
15 #include <cups/cups-private.h>
16 #include <syslog.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #include <netinet/in.h>
22 #include <netdb.h>
23
24 #ifdef HAVE_INTTYPES_H
25 # include <inttypes.h>
26 #endif /* HAVE_INTTYPES_H */
27 #ifdef __APPLE__
28 # include <xpc/xpc.h>
29 #endif /* __APPLE__ */
30
31
32 /*
33 * LPD "mini-daemon" for CUPS. This program must be used in conjunction
34 * with inetd or another similar program that monitors ports and starts
35 * daemons for each client connection. A typical configuration is:
36 *
37 * printer stream tcp nowait lp /usr/lib/cups/daemon/cups-lpd cups-lpd
38 *
39 * This daemon implements most of RFC 1179 (the unofficial LPD specification)
40 * except for:
41 *
42 * - This daemon does not check to make sure that the source port is
43 * between 721 and 731, since it isn't necessary for proper
44 * functioning and port-based security is no security at all!
45 *
46 * - The "Print any waiting jobs" command is a no-op.
47 *
48 * The LPD-to-IPP mapping is as defined in RFC 2569. The report formats
49 * currently match the Solaris LPD mini-daemon.
50 */
51
52 /*
53 * Prototypes...
54 */
55
56 static int create_job(http_t *http, const char *dest, const char *title, const char *user, int num_options, cups_option_t *options);
57 static int get_printer(http_t *http, const char *name, char *dest,
58 size_t destsize, cups_option_t **options,
59 int *accepting, int *shared, ipp_pstate_t *state);
60 static int print_file(http_t *http, int id, const char *filename,
61 const char *docname, const char *user,
62 const char *format, int last);
63 static int recv_print_job(const char *name, int num_defaults,
64 cups_option_t *defaults);
65 static int remove_jobs(const char *name, const char *agent,
66 const char *list);
67 static int send_state(const char *name, const char *list,
68 int longstatus);
69 static char *smart_gets(char *s, int len, FILE *fp);
70 static void smart_strlcpy(char *dst, const char *src, size_t dstsize);
71
72
73 /*
74 * 'main()' - Process an incoming LPD request...
75 */
76
77 int /* O - Exit status */
78 main(int argc, /* I - Number of command-line arguments */
79 char *argv[]) /* I - Command-line arguments */
80 {
81 int i; /* Looping var */
82 int num_defaults; /* Number of default options */
83 cups_option_t *defaults; /* Default options */
84 char line[256], /* Command string */
85 command, /* Command code */
86 *dest, /* Pointer to destination */
87 *list, /* Pointer to list */
88 *agent, /* Pointer to user */
89 status; /* Status for client */
90 socklen_t hostlen; /* Size of client address */
91 http_addr_t hostaddr; /* Address of client */
92 char hostname[256], /* Name of client */
93 hostip[256], /* IP address */
94 *hostfamily; /* Address family */
95 int hostlookups; /* Do hostname lookups? */
96
97
98 #ifdef __APPLE__
99 xpc_transaction_begin();
100 #endif /* __APPLE__ */
101
102 /*
103 * Don't buffer the output...
104 */
105
106 setbuf(stdout, NULL);
107
108 /*
109 * Log things using the "cups-lpd" name...
110 */
111
112 openlog("cups-lpd", LOG_PID, LOG_LPR);
113
114 /*
115 * Scan the command-line for options...
116 */
117
118 num_defaults = 0;
119 defaults = NULL;
120 hostlookups = 1;
121
122 for (i = 1; i < argc; i ++)
123 if (argv[i][0] == '-')
124 {
125 switch (argv[i][1])
126 {
127 case 'h' : /* -h hostname[:port] */
128 if (argv[i][2])
129 cupsSetServer(argv[i] + 2);
130 else
131 {
132 i ++;
133 if (i < argc)
134 cupsSetServer(argv[i]);
135 else
136 syslog(LOG_WARNING, "Expected hostname string after -h option!");
137 }
138 break;
139
140 case 'o' : /* Option */
141 if (argv[i][2])
142 num_defaults = cupsParseOptions(argv[i] + 2, num_defaults,
143 &defaults);
144 else
145 {
146 i ++;
147 if (i < argc)
148 num_defaults = cupsParseOptions(argv[i], num_defaults,
149 &defaults);
150 else
151 syslog(LOG_WARNING, "Expected option string after -o option!");
152 }
153 break;
154
155 case 'n' : /* Don't do hostname lookups */
156 hostlookups = 0;
157 break;
158
159 default :
160 syslog(LOG_WARNING, "Unknown option \"%c\" ignored!", argv[i][1]);
161 break;
162 }
163 }
164 else
165 syslog(LOG_WARNING, "Unknown command-line option \"%s\" ignored!",
166 argv[i]);
167
168 /*
169 * Get the address of the client...
170 */
171
172 hostlen = sizeof(hostaddr);
173
174 if (getpeername(0, (struct sockaddr *)&hostaddr, &hostlen))
175 {
176 syslog(LOG_WARNING, "Unable to get client address - %s", strerror(errno));
177 strlcpy(hostname, "unknown", sizeof(hostname));
178 }
179 else
180 {
181 httpAddrString(&hostaddr, hostip, sizeof(hostip));
182
183 if (hostlookups)
184 httpAddrLookup(&hostaddr, hostname, sizeof(hostname));
185 else
186 strlcpy(hostname, hostip, sizeof(hostname));
187
188 #ifdef AF_INET6
189 if (hostaddr.addr.sa_family == AF_INET6)
190 hostfamily = "IPv6";
191 else
192 #endif /* AF_INET6 */
193 hostfamily = "IPv4";
194
195 syslog(LOG_INFO, "Connection from %s (%s %s)", hostname, hostfamily,
196 hostip);
197 }
198
199 num_defaults = cupsAddOption("job-originating-host-name", hostname,
200 num_defaults, &defaults);
201
202 /*
203 * RFC1179 specifies that only 1 daemon command can be received for
204 * every connection.
205 */
206
207 if (smart_gets(line, sizeof(line), stdin) == NULL)
208 {
209 /*
210 * Unable to get command from client! Send an error status and return.
211 */
212
213 syslog(LOG_ERR, "Unable to get command line from client!");
214 putchar(1);
215
216 #ifdef __APPLE__
217 xpc_transaction_end();
218 #endif /* __APPLE__ */
219
220 return (1);
221 }
222
223 /*
224 * The first byte is the command byte. After that will be the queue name,
225 * resource list, and/or user name.
226 */
227
228 if ((command = line[0]) == '\0')
229 dest = line;
230 else
231 dest = line + 1;
232
233 if (command == 0x02)
234 list = NULL;
235 else
236 {
237 for (list = dest; *list && !isspace(*list & 255); list ++);
238
239 while (isspace(*list & 255))
240 *list++ = '\0';
241 }
242
243 /*
244 * Do the command...
245 */
246
247 switch (command)
248 {
249 default : /* Unknown command */
250 syslog(LOG_ERR, "Unknown LPD command 0x%02X!", command);
251 syslog(LOG_ERR, "Command line = %s", line + 1);
252 putchar(1);
253
254 status = 1;
255 break;
256
257 case 0x01 : /* Print any waiting jobs */
258 syslog(LOG_INFO, "Print waiting jobs (no-op)");
259 putchar(0);
260
261 status = 0;
262 break;
263
264 case 0x02 : /* Receive a printer job */
265 syslog(LOG_INFO, "Receive print job for %s", dest);
266 /* recv_print_job() sends initial status byte */
267
268 status = (char)recv_print_job(dest, num_defaults, defaults);
269 break;
270
271 case 0x03 : /* Send queue state (short) */
272 syslog(LOG_INFO, "Send queue state (short) for %s %s", dest, list);
273 /* no status byte for this command */
274
275 status = (char)send_state(dest, list, 0);
276 break;
277
278 case 0x04 : /* Send queue state (long) */
279 syslog(LOG_INFO, "Send queue state (long) for %s %s", dest, list);
280 /* no status byte for this command */
281
282 status = (char)send_state(dest, list, 1);
283 break;
284
285 case 0x05 : /* Remove jobs */
286 if (list)
287 {
288 /*
289 * Grab the agent and skip to the list of users and/or jobs.
290 */
291
292 agent = list;
293
294 for (; *list && !isspace(*list & 255); list ++);
295 while (isspace(*list & 255))
296 *list++ = '\0';
297
298 syslog(LOG_INFO, "Remove jobs %s on %s by %s", list, dest, agent);
299
300 status = (char)remove_jobs(dest, agent, list);
301 }
302 else
303 status = 1;
304
305 putchar(status);
306 break;
307 }
308
309 syslog(LOG_INFO, "Closing connection");
310 closelog();
311
312 #ifdef __APPLE__
313 xpc_transaction_end();
314 #endif /* __APPLE__ */
315
316 return (status);
317 }
318
319
320 /*
321 * 'create_job()' - Create a new print job.
322 */
323
324 static int /* O - Job ID or -1 on error */
325 create_job(http_t *http, /* I - HTTP connection */
326 const char *dest, /* I - Destination name */
327 const char *title, /* I - job-name */
328 const char *user, /* I - requesting-user-name */
329 int num_options, /* I - Number of options for job */
330 cups_option_t *options) /* I - Options for job */
331 {
332 ipp_t *request; /* IPP request */
333 ipp_t *response; /* IPP response */
334 ipp_attribute_t *attr; /* IPP attribute */
335 char uri[HTTP_MAX_URI]; /* Printer URI */
336 int id; /* Job ID */
337
338
339 /*
340 * Setup the Create-Job request...
341 */
342
343 request = ippNewRequest(IPP_OP_CREATE_JOB);
344
345 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
346 "localhost", 0, "/printers/%s", dest);
347
348 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
349 NULL, uri);
350
351 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
352 "requesting-user-name", NULL, user);
353
354 if (title[0])
355 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name",
356 NULL, title);
357
358 cupsEncodeOptions(request, num_options, options);
359
360 /*
361 * Do the request...
362 */
363
364 snprintf(uri, sizeof(uri), "/printers/%s", dest);
365
366 response = cupsDoRequest(http, request, uri);
367
368 if (!response || cupsLastError() > IPP_STATUS_OK_CONFLICTING)
369 {
370 syslog(LOG_ERR, "Unable to create job - %s", cupsLastErrorString());
371
372 ippDelete(response);
373
374 return (-1);
375 }
376
377 /*
378 * Get the job-id value from the response and return it...
379 */
380
381 if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL)
382 {
383 id = -1;
384
385 syslog(LOG_ERR, "No job-id attribute found in response from server!");
386 }
387 else
388 {
389 id = attr->values[0].integer;
390
391 syslog(LOG_INFO, "Print file - job ID = %d", id);
392 }
393
394 ippDelete(response);
395
396 return (id);
397 }
398
399
400 /*
401 * 'get_printer()' - Get the named printer and its options.
402 */
403
404 static int /* O - Number of options or -1 on error */
405 get_printer(http_t *http, /* I - HTTP connection */
406 const char *name, /* I - Printer name from request */
407 char *dest, /* I - Destination buffer */
408 size_t destsize, /* I - Size of destination buffer */
409 cups_option_t **options, /* O - Printer options */
410 int *accepting, /* O - printer-is-accepting-jobs value */
411 int *shared, /* O - printer-is-shared value */
412 ipp_pstate_t *state) /* O - printer-state value */
413 {
414 int num_options; /* Number of options */
415 cups_file_t *fp; /* lpoptions file */
416 char line[1024], /* Line from lpoptions file */
417 *value, /* Pointer to value on line */
418 *optptr; /* Pointer to options on line */
419 int linenum; /* Line number in file */
420 const char *cups_serverroot; /* CUPS_SERVERROOT env var */
421 ipp_t *request; /* IPP request */
422 ipp_t *response; /* IPP response */
423 ipp_attribute_t *attr; /* IPP attribute */
424 char uri[HTTP_MAX_URI]; /* Printer URI */
425 static const char * const requested[] =
426 { /* Requested attributes */
427 "printer-info",
428 "printer-is-accepting-jobs",
429 "printer-is-shared",
430 "printer-name",
431 "printer-state"
432 };
433
434
435 /*
436 * Initialize everything...
437 */
438
439 if (accepting)
440 *accepting = 0;
441 if (shared)
442 *shared = 0;
443 if (state)
444 *state = IPP_PSTATE_STOPPED;
445 if (options)
446 *options = NULL;
447
448 /*
449 * See if the name is a queue name optionally with an instance name.
450 */
451
452 strlcpy(dest, name, destsize);
453 if ((value = strchr(dest, '/')) != NULL)
454 *value = '\0';
455
456 /*
457 * Setup the Get-Printer-Attributes request...
458 */
459
460 request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
461
462 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
463 "localhost", 0, "/printers/%s", dest);
464
465 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
466 NULL, uri);
467
468 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
469 "requested-attributes",
470 (int)(sizeof(requested) / sizeof(requested[0])),
471 NULL, requested);
472
473 /*
474 * Do the request...
475 */
476
477 response = cupsDoRequest(http, request, "/");
478
479 if (!response || cupsLastError() > IPP_STATUS_OK_CONFLICTING)
480 {
481 /*
482 * If we can't find the printer by name, look up the printer-name
483 * using the printer-info values...
484 */
485
486 ipp_attribute_t *accepting_attr,/* printer-is-accepting-jobs */
487 *info_attr, /* printer-info */
488 *name_attr, /* printer-name */
489 *shared_attr, /* printer-is-shared */
490 *state_attr; /* printer-state */
491
492
493 ippDelete(response);
494
495 /*
496 * Setup the CUPS-Get-Printers request...
497 */
498
499 request = ippNewRequest(IPP_OP_CUPS_GET_PRINTERS);
500
501 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
502 "requested-attributes",
503 (int)(sizeof(requested) / sizeof(requested[0])),
504 NULL, requested);
505
506 /*
507 * Do the request...
508 */
509
510 response = cupsDoRequest(http, request, "/");
511
512 if (!response || cupsLastError() > IPP_STATUS_OK_CONFLICTING)
513 {
514 syslog(LOG_ERR, "Unable to get list of printers - %s",
515 cupsLastErrorString());
516
517 ippDelete(response);
518
519 return (-1);
520 }
521
522 /*
523 * Scan the response for printers...
524 */
525
526 *dest = '\0';
527 attr = response->attrs;
528
529 while (attr)
530 {
531 /*
532 * Skip to the next printer...
533 */
534
535 while (attr && attr->group_tag != IPP_TAG_PRINTER)
536 attr = attr->next;
537
538 if (!attr)
539 break;
540
541 /*
542 * Get all of the attributes for the current printer...
543 */
544
545 accepting_attr = NULL;
546 info_attr = NULL;
547 name_attr = NULL;
548 shared_attr = NULL;
549 state_attr = NULL;
550
551 while (attr && attr->group_tag == IPP_TAG_PRINTER)
552 {
553 if (!strcmp(attr->name, "printer-is-accepting-jobs") &&
554 attr->value_tag == IPP_TAG_BOOLEAN)
555 accepting_attr = attr;
556 else if (!strcmp(attr->name, "printer-info") &&
557 attr->value_tag == IPP_TAG_TEXT)
558 info_attr = attr;
559 else if (!strcmp(attr->name, "printer-name") &&
560 attr->value_tag == IPP_TAG_NAME)
561 name_attr = attr;
562 else if (!strcmp(attr->name, "printer-is-shared") &&
563 attr->value_tag == IPP_TAG_BOOLEAN)
564 shared_attr = attr;
565 else if (!strcmp(attr->name, "printer-state") &&
566 attr->value_tag == IPP_TAG_ENUM)
567 state_attr = attr;
568
569 attr = attr->next;
570 }
571
572 if (info_attr && name_attr &&
573 !_cups_strcasecmp(name, info_attr->values[0].string.text))
574 {
575 /*
576 * Found a match, use this one!
577 */
578
579 strlcpy(dest, name_attr->values[0].string.text, destsize);
580
581 if (accepting && accepting_attr)
582 *accepting = accepting_attr->values[0].boolean;
583
584 if (shared && shared_attr)
585 *shared = shared_attr->values[0].boolean;
586
587 if (state && state_attr)
588 *state = (ipp_pstate_t)state_attr->values[0].integer;
589
590 break;
591 }
592 }
593
594 ippDelete(response);
595
596 if (!*dest)
597 {
598 syslog(LOG_ERR, "Unable to find \"%s\" in list of printers!", name);
599
600 return (-1);
601 }
602
603 name = dest;
604 }
605 else
606 {
607 /*
608 * Get values from the response...
609 */
610
611 if (accepting)
612 {
613 if ((attr = ippFindAttribute(response, "printer-is-accepting-jobs",
614 IPP_TAG_BOOLEAN)) == NULL)
615 syslog(LOG_ERR, "No printer-is-accepting-jobs attribute found in "
616 "response from server!");
617 else
618 *accepting = attr->values[0].boolean;
619 }
620
621 if (shared)
622 {
623 if ((attr = ippFindAttribute(response, "printer-is-shared",
624 IPP_TAG_BOOLEAN)) == NULL)
625 {
626 syslog(LOG_ERR, "No printer-is-shared attribute found in "
627 "response from server!");
628 *shared = 1;
629 }
630 else
631 *shared = attr->values[0].boolean;
632 }
633
634 if (state)
635 {
636 if ((attr = ippFindAttribute(response, "printer-state",
637 IPP_TAG_ENUM)) == NULL)
638 syslog(LOG_ERR, "No printer-state attribute found in "
639 "response from server!");
640 else
641 *state = (ipp_pstate_t)attr->values[0].integer;
642 }
643
644 ippDelete(response);
645 }
646
647 /*
648 * Next look for the printer in the lpoptions file...
649 */
650
651 num_options = 0;
652
653 if (options && shared && accepting)
654 {
655 if ((cups_serverroot = getenv("CUPS_SERVERROOT")) == NULL)
656 cups_serverroot = CUPS_SERVERROOT;
657
658 snprintf(line, sizeof(line), "%s/lpoptions", cups_serverroot);
659 if ((fp = cupsFileOpen(line, "r")) != NULL)
660 {
661 linenum = 0;
662 while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
663 {
664 /*
665 * Make sure we have "Dest name options" or "Default name options"...
666 */
667
668 if ((_cups_strcasecmp(line, "Dest") && _cups_strcasecmp(line, "Default")) || !value)
669 continue;
670
671 /*
672 * Separate destination name from options...
673 */
674
675 for (optptr = value; *optptr && !isspace(*optptr & 255); optptr ++);
676
677 while (*optptr == ' ')
678 *optptr++ = '\0';
679
680 /*
681 * If this is our destination, parse the options and break out of
682 * the loop - we're done!
683 */
684
685 if (!_cups_strcasecmp(value, name))
686 {
687 num_options = cupsParseOptions(optptr, num_options, options);
688 break;
689 }
690 }
691
692 cupsFileClose(fp);
693 }
694 }
695 else if (options)
696 *options = NULL;
697
698 /*
699 * Return the number of options for this destination...
700 */
701
702 return (num_options);
703 }
704
705
706 /*
707 * 'print_file()' - Add a file to the current job.
708 */
709
710 static int /* O - 0 on success, -1 on failure */
711 print_file(http_t *http, /* I - HTTP connection */
712 int id, /* I - Job ID */
713 const char *filename, /* I - File to print */
714 const char *docname, /* I - document-name */
715 const char *user, /* I - requesting-user-name */
716 const char *format, /* I - document-format */
717 int last) /* I - 1 = last file in job */
718 {
719 ipp_t *request; /* IPP request */
720 char uri[HTTP_MAX_URI]; /* Printer URI */
721
722
723 /*
724 * Setup the Send-Document request...
725 */
726
727 request = ippNewRequest(IPP_OP_SEND_DOCUMENT);
728
729 snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", id);
730 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
731
732 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
733 "requesting-user-name", NULL, user);
734
735 if (docname)
736 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
737 "document-name", NULL, docname);
738
739 if (format)
740 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
741 "document-format", NULL, format);
742
743 ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", (char)last);
744
745 /*
746 * Do the request...
747 */
748
749 snprintf(uri, sizeof(uri), "/jobs/%d", id);
750
751 ippDelete(cupsDoFileRequest(http, request, uri, filename));
752
753 if (cupsLastError() > IPP_STATUS_OK_CONFLICTING)
754 {
755 syslog(LOG_ERR, "Unable to send document - %s", cupsLastErrorString());
756
757 return (-1);
758 }
759
760 return (0);
761 }
762
763
764 /*
765 * 'recv_print_job()' - Receive a print job from the client.
766 */
767
768 static int /* O - Command status */
769 recv_print_job(
770 const char *queue, /* I - Printer name */
771 int num_defaults, /* I - Number of default options */
772 cups_option_t *defaults) /* I - Default options */
773 {
774 http_t *http; /* HTTP connection */
775 int i; /* Looping var */
776 int status; /* Command status */
777 int fd; /* Temporary file */
778 FILE *fp; /* File pointer */
779 char filename[1024]; /* Temporary filename */
780 ssize_t bytes; /* Bytes received */
781 size_t total; /* Total bytes */
782 char line[256], /* Line from file/stdin */
783 command, /* Command from line */
784 *count, /* Number of bytes */
785 *name; /* Name of file */
786 const char *job_sheets; /* Job sheets */
787 int num_data; /* Number of data files */
788 char control[1024], /* Control filename */
789 data[100][256], /* Data files */
790 temp[100][1024]; /* Temporary files */
791 char user[1024], /* User name */
792 title[1024], /* Job title */
793 docname[1024], /* Document name */
794 dest[256]; /* Printer/class queue */
795 int accepting, /* printer-is-accepting */
796 shared, /* printer-is-shared */
797 num_options; /* Number of options */
798 cups_option_t *options; /* Options */
799 int id; /* Job ID */
800 int docnumber, /* Current document number */
801 doccount; /* Count of documents */
802
803
804 /*
805 * Connect to the server...
806 */
807
808 http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, cupsEncryption(), 1, 30000, NULL);
809 if (!http)
810 {
811 syslog(LOG_ERR, "Unable to connect to server: %s", strerror(errno));
812
813 putchar(1);
814
815 return (1);
816 }
817
818 /*
819 * See if the printer is available...
820 */
821
822 num_options = get_printer(http, queue, dest, sizeof(dest), &options,
823 &accepting, &shared, NULL);
824
825 if (num_options < 0 || !accepting || !shared)
826 {
827 if (dest[0])
828 syslog(LOG_INFO, "Rejecting job because \"%s\" is not %s", dest,
829 !accepting ? "accepting jobs" : "shared");
830 else
831 syslog(LOG_ERR, "Unable to get printer information for \"%s\"", queue);
832
833 httpClose(http);
834
835 putchar(1);
836
837 return (1);
838 }
839
840 putchar(0); /* OK so far... */
841
842 /*
843 * Read the request...
844 */
845
846 status = 0;
847 num_data = 0;
848 fd = -1;
849
850 control[0] = '\0';
851
852 while (smart_gets(line, sizeof(line), stdin) != NULL)
853 {
854 if (strlen(line) < 2)
855 {
856 status = 1;
857 break;
858 }
859
860 command = line[0];
861 count = line + 1;
862
863 for (name = count + 1; *name && !isspace(*name & 255); name ++);
864 while (isspace(*name & 255))
865 *name++ = '\0';
866
867 switch (command)
868 {
869 default :
870 case 0x01 : /* Abort */
871 status = 1;
872 break;
873
874 case 0x02 : /* Receive control file */
875 if (strlen(name) < 2)
876 {
877 syslog(LOG_ERR, "Bad control file name \"%s\"", name);
878 putchar(1);
879 status = 1;
880 break;
881 }
882
883 if (control[0])
884 {
885 /*
886 * Append to the existing control file - the LPD spec is
887 * not entirely clear, but at least the OS/2 LPD code sends
888 * multiple control files per connection...
889 */
890
891 if ((fd = open(control, O_WRONLY)) < 0)
892 {
893 syslog(LOG_ERR,
894 "Unable to append to temporary control file \"%s\" - %s",
895 control, strerror(errno));
896 putchar(1);
897 status = 1;
898 break;
899 }
900
901 lseek(fd, 0, SEEK_END);
902 }
903 else
904 {
905 if ((fd = cupsTempFd(control, sizeof(control))) < 0)
906 {
907 syslog(LOG_ERR, "Unable to open temporary control file \"%s\" - %s",
908 control, strerror(errno));
909 putchar(1);
910 status = 1;
911 break;
912 }
913
914 strlcpy(filename, control, sizeof(filename));
915 }
916 break;
917
918 case 0x03 : /* Receive data file */
919 if (strlen(name) < 2)
920 {
921 syslog(LOG_ERR, "Bad data file name \"%s\"", name);
922 putchar(1);
923 status = 1;
924 break;
925 }
926
927 if (num_data >= (int)(sizeof(data) / sizeof(data[0])))
928 {
929 /*
930 * Too many data files...
931 */
932
933 syslog(LOG_ERR, "Too many data files (%d)", num_data);
934 putchar(1);
935 status = 1;
936 break;
937 }
938
939 strlcpy(data[num_data], name, sizeof(data[0]));
940
941 if ((fd = cupsTempFd(temp[num_data], sizeof(temp[0]))) < 0)
942 {
943 syslog(LOG_ERR, "Unable to open temporary data file \"%s\" - %s",
944 temp[num_data], strerror(errno));
945 putchar(1);
946 status = 1;
947 break;
948 }
949
950 strlcpy(filename, temp[num_data], sizeof(filename));
951
952 num_data ++;
953 break;
954 }
955
956 putchar(status);
957
958 if (status)
959 break;
960
961 /*
962 * Copy the data or control file from the client...
963 */
964
965 for (total = (size_t)strtoll(count, NULL, 10); total > 0; total -= (size_t)bytes)
966 {
967 if (total > sizeof(line))
968 bytes = (ssize_t)sizeof(line);
969 else
970 bytes = (ssize_t)total;
971
972 if ((bytes = (ssize_t)fread(line, 1, (size_t)bytes, stdin)) > 0)
973 bytes = write(fd, line, (size_t)bytes);
974
975 if (bytes < 1)
976 {
977 syslog(LOG_ERR, "Error while reading file - %s",
978 strerror(errno));
979 status = 1;
980 break;
981 }
982 }
983
984 /*
985 * Read trailing nul...
986 */
987
988 if (!status)
989 {
990 if (fread(line, 1, 1, stdin) < 1)
991 {
992 status = 1;
993 syslog(LOG_ERR, "Error while reading trailing nul - %s",
994 strerror(errno));
995 }
996 else if (line[0])
997 {
998 status = 1;
999 syslog(LOG_ERR, "Trailing character after file is not nul (%02X)!",
1000 line[0]);
1001 }
1002 }
1003
1004 /*
1005 * Close the file and send an acknowledgement...
1006 */
1007
1008 close(fd);
1009
1010 putchar(status);
1011
1012 if (status)
1013 break;
1014 }
1015
1016 if (!status)
1017 {
1018 /*
1019 * Process the control file and print stuff...
1020 */
1021
1022 if ((fp = fopen(control, "rb")) == NULL)
1023 status = 1;
1024 else
1025 {
1026 /*
1027 * Copy the default options...
1028 */
1029
1030 for (i = 0; i < num_defaults; i ++)
1031 num_options = cupsAddOption(defaults[i].name,
1032 defaults[i].value,
1033 num_options, &options);
1034
1035 /*
1036 * Grab the job information...
1037 */
1038
1039 title[0] = '\0';
1040 user[0] = '\0';
1041 docname[0] = '\0';
1042 doccount = 0;
1043
1044 while (smart_gets(line, sizeof(line), fp) != NULL)
1045 {
1046 /*
1047 * Process control lines...
1048 */
1049
1050 switch (line[0])
1051 {
1052 case 'J' : /* Job name */
1053 smart_strlcpy(title, line + 1, sizeof(title));
1054 break;
1055
1056 case 'N' : /* Document name */
1057 smart_strlcpy(docname, line + 1, sizeof(docname));
1058 break;
1059
1060 case 'P' : /* User identification */
1061 smart_strlcpy(user, line + 1, sizeof(user));
1062 break;
1063
1064 case 'L' : /* Print banner page */
1065 /*
1066 * If a banner was requested and it's not overridden by a
1067 * command line option and the destination's default is none
1068 * then add the standard banner...
1069 */
1070
1071 if (cupsGetOption("job-sheets", num_defaults, defaults) == NULL &&
1072 ((job_sheets = cupsGetOption("job-sheets", num_options,
1073 options)) == NULL ||
1074 !strcmp(job_sheets, "none,none")))
1075 {
1076 num_options = cupsAddOption("job-sheets", "standard",
1077 num_options, &options);
1078 }
1079 break;
1080
1081 case 'c' : /* Plot CIF file */
1082 case 'd' : /* Print DVI file */
1083 case 'f' : /* Print formatted file */
1084 case 'g' : /* Plot file */
1085 case 'l' : /* Print file leaving control characters (raw) */
1086 case 'n' : /* Print ditroff output file */
1087 case 'o' : /* Print PostScript output file */
1088 case 'p' : /* Print file with 'pr' format (prettyprint) */
1089 case 'r' : /* File to print with FORTRAN carriage control */
1090 case 't' : /* Print troff output file */
1091 case 'v' : /* Print raster file */
1092 doccount ++;
1093
1094 if (line[0] == 'l' &&
1095 !cupsGetOption("document-format", num_options, options))
1096 num_options = cupsAddOption("raw", "", num_options, &options);
1097
1098 if (line[0] == 'p')
1099 num_options = cupsAddOption("prettyprint", "", num_options,
1100 &options);
1101 break;
1102 }
1103
1104 if (status)
1105 break;
1106 }
1107
1108 /*
1109 * Check that we have a username...
1110 */
1111
1112 if (!user[0])
1113 {
1114 syslog(LOG_WARNING, "No username specified by client! "
1115 "Using \"anonymous\"...");
1116 strlcpy(user, "anonymous", sizeof(user));
1117 }
1118
1119 /*
1120 * Create the job...
1121 */
1122
1123 if ((id = create_job(http, dest, title, user, num_options, options)) < 0)
1124 status = 1;
1125 else
1126 {
1127 /*
1128 * Then print the job files...
1129 */
1130
1131 rewind(fp);
1132
1133 docname[0] = '\0';
1134 docnumber = 0;
1135
1136 while (smart_gets(line, sizeof(line), fp) != NULL)
1137 {
1138 /*
1139 * Process control lines...
1140 */
1141
1142 switch (line[0])
1143 {
1144 case 'N' : /* Document name */
1145 smart_strlcpy(docname, line + 1, sizeof(docname));
1146 break;
1147
1148 case 'c' : /* Plot CIF file */
1149 case 'd' : /* Print DVI file */
1150 case 'f' : /* Print formatted file */
1151 case 'g' : /* Plot file */
1152 case 'l' : /* Print file leaving control characters (raw) */
1153 case 'n' : /* Print ditroff output file */
1154 case 'o' : /* Print PostScript output file */
1155 case 'p' : /* Print file with 'pr' format (prettyprint) */
1156 case 'r' : /* File to print with FORTRAN carriage control */
1157 case 't' : /* Print troff output file */
1158 case 'v' : /* Print raster file */
1159 /*
1160 * Figure out which file we are printing...
1161 */
1162
1163 for (i = 0; i < num_data; i ++)
1164 if (!strcmp(data[i], line + 1))
1165 break;
1166
1167 if (i >= num_data)
1168 {
1169 status = 1;
1170 break;
1171 }
1172
1173 /*
1174 * Send the print file...
1175 */
1176
1177 docnumber ++;
1178
1179 if (print_file(http, id, temp[i], docname, user,
1180 cupsGetOption("document-format", num_options,
1181 options),
1182 docnumber == doccount))
1183 status = 1;
1184 else
1185 status = 0;
1186
1187 break;
1188 }
1189
1190 if (status)
1191 break;
1192 }
1193 }
1194
1195 fclose(fp);
1196 }
1197 }
1198
1199 cupsFreeOptions(num_options, options);
1200
1201 httpClose(http);
1202
1203 /*
1204 * Clean up all temporary files and return...
1205 */
1206
1207 unlink(control);
1208
1209 for (i = 0; i < num_data; i ++)
1210 unlink(temp[i]);
1211
1212 return (status);
1213 }
1214
1215
1216 /*
1217 * 'remove_jobs()' - Cancel one or more jobs.
1218 */
1219
1220 static int /* O - Command status */
1221 remove_jobs(const char *dest, /* I - Destination */
1222 const char *agent, /* I - User agent */
1223 const char *list) /* I - List of jobs or users */
1224 {
1225 int id; /* Job ID */
1226 http_t *http; /* HTTP server connection */
1227 ipp_t *request; /* IPP Request */
1228 char uri[HTTP_MAX_URI]; /* Job URI */
1229
1230
1231 (void)dest; /* Suppress compiler warnings... */
1232
1233 /*
1234 * Try connecting to the local server...
1235 */
1236
1237 if ((http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, cupsEncryption(), 1, 30000, NULL)) == NULL)
1238 {
1239 syslog(LOG_ERR, "Unable to connect to server %s: %s", cupsServer(),
1240 strerror(errno));
1241 return (1);
1242 }
1243
1244 /*
1245 * Loop for each job...
1246 */
1247
1248 while ((id = atoi(list)) > 0)
1249 {
1250 /*
1251 * Skip job ID in list...
1252 */
1253
1254 while (isdigit(*list & 255))
1255 list ++;
1256 while (isspace(*list & 255))
1257 list ++;
1258
1259 /*
1260 * Build an IPP_OP_CANCEL_JOB request, which requires the following
1261 * attributes:
1262 *
1263 * attributes-charset
1264 * attributes-natural-language
1265 * job-uri
1266 * requesting-user-name
1267 */
1268
1269 request = ippNewRequest(IPP_OP_CANCEL_JOB);
1270
1271 sprintf(uri, "ipp://localhost/jobs/%d", id);
1272 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
1273
1274 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1275 "requesting-user-name", NULL, agent);
1276
1277 /*
1278 * Do the request and get back a response...
1279 */
1280
1281 ippDelete(cupsDoRequest(http, request, "/jobs"));
1282
1283 if (cupsLastError() > IPP_STATUS_OK_CONFLICTING)
1284 {
1285 syslog(LOG_WARNING, "Cancel of job ID %d failed: %s\n", id,
1286 cupsLastErrorString());
1287 httpClose(http);
1288 return (1);
1289 }
1290 else
1291 syslog(LOG_INFO, "Job ID %d canceled", id);
1292 }
1293
1294 httpClose(http);
1295
1296 return (0);
1297 }
1298
1299
1300 /*
1301 * 'send_state()' - Send the queue state.
1302 */
1303
1304 static int /* O - Command status */
1305 send_state(const char *queue, /* I - Destination */
1306 const char *list, /* I - Job or user */
1307 int longstatus) /* I - List of jobs or users */
1308 {
1309 int id; /* Job ID from list */
1310 http_t *http; /* HTTP server connection */
1311 ipp_t *request, /* IPP Request */
1312 *response; /* IPP Response */
1313 ipp_attribute_t *attr; /* Current attribute */
1314 ipp_pstate_t state; /* Printer state */
1315 const char *jobdest, /* Pointer into job-printer-uri */
1316 *jobuser, /* Pointer to job-originating-user-name */
1317 *jobname; /* Pointer to job-name */
1318 ipp_jstate_t jobstate; /* job-state */
1319 int jobid, /* job-id */
1320 jobsize, /* job-k-octets */
1321 jobcount, /* Number of jobs */
1322 jobcopies, /* Number of copies */
1323 rank; /* Rank of job */
1324 char rankstr[255]; /* Rank string */
1325 char namestr[1024]; /* Job name string */
1326 char uri[HTTP_MAX_URI]; /* Printer URI */
1327 char dest[256]; /* Printer/class queue */
1328 static const char * const ranks[10] = /* Ranking strings */
1329 {
1330 "th",
1331 "st",
1332 "nd",
1333 "rd",
1334 "th",
1335 "th",
1336 "th",
1337 "th",
1338 "th",
1339 "th"
1340 };
1341 static const char * const requested[] =
1342 { /* Requested attributes */
1343 "job-id",
1344 "job-k-octets",
1345 "job-state",
1346 "job-printer-uri",
1347 "job-originating-user-name",
1348 "job-name",
1349 "copies"
1350 };
1351
1352
1353 /*
1354 * Try connecting to the local server...
1355 */
1356
1357 if ((http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, cupsEncryption(), 1, 30000, NULL)) == NULL)
1358 {
1359 syslog(LOG_ERR, "Unable to connect to server %s: %s", cupsServer(),
1360 strerror(errno));
1361 printf("Unable to connect to server %s: %s", cupsServer(), strerror(errno));
1362 return (1);
1363 }
1364
1365 /*
1366 * Get the actual destination name and printer state...
1367 */
1368
1369 if (get_printer(http, queue, dest, sizeof(dest), NULL, NULL, NULL, &state))
1370 {
1371 syslog(LOG_ERR, "Unable to get printer %s: %s", queue,
1372 cupsLastErrorString());
1373 printf("Unable to get printer %s: %s", queue, cupsLastErrorString());
1374 return (1);
1375 }
1376
1377 /*
1378 * Show the queue state...
1379 */
1380
1381 switch (state)
1382 {
1383 case IPP_PSTATE_IDLE :
1384 printf("%s is ready\n", dest);
1385 break;
1386 case IPP_PSTATE_PROCESSING :
1387 printf("%s is ready and printing\n", dest);
1388 break;
1389 case IPP_PSTATE_STOPPED :
1390 printf("%s is not ready\n", dest);
1391 break;
1392 }
1393
1394 /*
1395 * Build an IPP_OP_GET_JOBS or IPP_OP_GET_JOB_ATTRIBUTES request, which requires
1396 * the following attributes:
1397 *
1398 * attributes-charset
1399 * attributes-natural-language
1400 * job-uri or printer-uri
1401 */
1402
1403 id = atoi(list);
1404
1405 request = ippNewRequest(id ? IPP_OP_GET_JOB_ATTRIBUTES : IPP_OP_GET_JOBS);
1406
1407 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
1408 "localhost", 0, "/printers/%s", dest);
1409
1410 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
1411 NULL, uri);
1412
1413 if (id)
1414 ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", id);
1415 else
1416 {
1417 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1418 "requesting-user-name", NULL, list);
1419 ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1);
1420 }
1421
1422 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
1423 "requested-attributes",
1424 sizeof(requested) / sizeof(requested[0]),
1425 NULL, requested);
1426
1427 /*
1428 * Do the request and get back a response...
1429 */
1430
1431 jobcount = 0;
1432 response = cupsDoRequest(http, request, "/");
1433
1434 if (cupsLastError() > IPP_STATUS_OK_CONFLICTING)
1435 {
1436 printf("get-jobs failed: %s\n", cupsLastErrorString());
1437 ippDelete(response);
1438 return (1);
1439 }
1440
1441 /*
1442 * Loop through the job list and display them...
1443 */
1444
1445 for (attr = response->attrs, rank = 1; attr; attr = attr->next)
1446 {
1447 /*
1448 * Skip leading attributes until we hit a job...
1449 */
1450
1451 while (attr && (attr->group_tag != IPP_TAG_JOB || !attr->name))
1452 attr = attr->next;
1453
1454 if (!attr)
1455 break;
1456
1457 /*
1458 * Pull the needed attributes from this job...
1459 */
1460
1461 jobid = 0;
1462 jobsize = 0;
1463 jobstate = IPP_JSTATE_PENDING;
1464 jobname = "untitled";
1465 jobuser = NULL;
1466 jobdest = NULL;
1467 jobcopies = 1;
1468
1469 while (attr && attr->group_tag == IPP_TAG_JOB)
1470 {
1471 if (!strcmp(attr->name, "job-id") &&
1472 attr->value_tag == IPP_TAG_INTEGER)
1473 jobid = attr->values[0].integer;
1474
1475 if (!strcmp(attr->name, "job-k-octets") &&
1476 attr->value_tag == IPP_TAG_INTEGER)
1477 jobsize = attr->values[0].integer;
1478
1479 if (!strcmp(attr->name, "job-state") &&
1480 attr->value_tag == IPP_TAG_ENUM)
1481 jobstate = (ipp_jstate_t)attr->values[0].integer;
1482
1483 if (!strcmp(attr->name, "job-printer-uri") &&
1484 attr->value_tag == IPP_TAG_URI)
1485 if ((jobdest = strrchr(attr->values[0].string.text, '/')) != NULL)
1486 jobdest ++;
1487
1488 if (!strcmp(attr->name, "job-originating-user-name") &&
1489 attr->value_tag == IPP_TAG_NAME)
1490 jobuser = attr->values[0].string.text;
1491
1492 if (!strcmp(attr->name, "job-name") &&
1493 attr->value_tag == IPP_TAG_NAME)
1494 jobname = attr->values[0].string.text;
1495
1496 if (!strcmp(attr->name, "copies") &&
1497 attr->value_tag == IPP_TAG_INTEGER)
1498 jobcopies = attr->values[0].integer;
1499
1500 attr = attr->next;
1501 }
1502
1503 /*
1504 * See if we have everything needed...
1505 */
1506
1507 if (!jobdest || !jobid)
1508 {
1509 if (!attr)
1510 break;
1511 else
1512 continue;
1513 }
1514
1515 if (!longstatus && jobcount == 0)
1516 puts("Rank Owner Job File(s) Total Size");
1517
1518 jobcount ++;
1519
1520 /*
1521 * Display the job...
1522 */
1523
1524 if (jobstate == IPP_JSTATE_PROCESSING)
1525 strlcpy(rankstr, "active", sizeof(rankstr));
1526 else
1527 {
1528 snprintf(rankstr, sizeof(rankstr), "%d%s", rank, ranks[rank % 10]);
1529 rank ++;
1530 }
1531
1532 if (longstatus)
1533 {
1534 puts("");
1535
1536 if (jobcopies > 1)
1537 snprintf(namestr, sizeof(namestr), "%d copies of %s", jobcopies,
1538 jobname);
1539 else
1540 strlcpy(namestr, jobname, sizeof(namestr));
1541
1542 printf("%s: %-33.33s [job %d localhost]\n", jobuser, rankstr, jobid);
1543 printf(" %-39.39s %.0f bytes\n", namestr, 1024.0 * jobsize);
1544 }
1545 else
1546 printf("%-7s %-7.7s %-7d %-31.31s %.0f bytes\n", rankstr, jobuser,
1547 jobid, jobname, 1024.0 * jobsize);
1548
1549 if (!attr)
1550 break;
1551 }
1552
1553 ippDelete(response);
1554
1555 if (jobcount == 0)
1556 puts("no entries");
1557
1558 httpClose(http);
1559
1560 return (0);
1561 }
1562
1563
1564 /*
1565 * 'smart_gets()' - Get a line of text, removing the trailing CR and/or LF.
1566 */
1567
1568 static char * /* O - Line read or NULL */
1569 smart_gets(char *s, /* I - Pointer to line buffer */
1570 int len, /* I - Size of line buffer */
1571 FILE *fp) /* I - File to read from */
1572 {
1573 char *ptr, /* Pointer into line */
1574 *end; /* End of line */
1575 int ch; /* Character from file */
1576
1577
1578 /*
1579 * Read the line; unlike fgets(), we read the entire line but dump
1580 * characters that go past the end of the buffer. Also, we accept
1581 * CR, LF, or CR LF for the line endings to be "safe", although
1582 * RFC 1179 specifically says "just use LF".
1583 */
1584
1585 ptr = s;
1586 end = s + len - 1;
1587
1588 while ((ch = getc(fp)) != EOF)
1589 {
1590 if (ch == '\n')
1591 break;
1592 else if (ch == '\r')
1593 {
1594 /*
1595 * See if a LF follows...
1596 */
1597
1598 ch = getc(fp);
1599
1600 if (ch != '\n')
1601 ungetc(ch, fp);
1602
1603 break;
1604 }
1605 else if (ptr < end)
1606 *ptr++ = (char)ch;
1607 }
1608
1609 *ptr = '\0';
1610
1611 if (ch == EOF && ptr == s)
1612 return (NULL);
1613 else
1614 return (s);
1615 }
1616
1617
1618 /*
1619 * 'smart_strlcpy()' - Copy a string and convert from ISO-8859-1 to UTF-8 as needed.
1620 */
1621
1622 static void
1623 smart_strlcpy(char *dst, /* I - Output buffer */
1624 const char *src, /* I - Input string */
1625 size_t dstsize) /* I - Size of output buffer */
1626 {
1627 const unsigned char *srcptr; /* Pointer into input string */
1628 unsigned char *dstptr, /* Pointer into output buffer */
1629 *dstend; /* End of output buffer */
1630 int saw_8859 = 0; /* Saw an extended character that was not UTF-8? */
1631
1632
1633 for (srcptr = (unsigned char *)src, dstptr = (unsigned char *)dst, dstend = dstptr + dstsize - 1; *srcptr;)
1634 {
1635 if (*srcptr < 0x80)
1636 *dstptr++ = *srcptr++; /* ASCII */
1637 else if (saw_8859)
1638 {
1639 /*
1640 * Map ISO-8859-1 (most likely character set for legacy LPD clients) to
1641 * UTF-8...
1642 */
1643
1644 if (dstptr > (dstend - 2))
1645 break;
1646
1647 *dstptr++ = 0xc0 | (*srcptr >> 6);
1648 *dstptr++ = 0x80 | (*srcptr++ & 0x3f);
1649 }
1650 else if ((*srcptr & 0xe0) == 0xc0 && (srcptr[1] & 0xc0) == 0x80)
1651 {
1652 /*
1653 * 2-byte UTF-8 sequence...
1654 */
1655
1656 if (dstptr > (dstend - 2))
1657 break;
1658
1659 *dstptr++ = *srcptr++;
1660 *dstptr++ = *srcptr++;
1661 }
1662 else if ((*srcptr & 0xf0) == 0xe0 && (srcptr[1] & 0xc0) == 0x80 && (srcptr[2] & 0xc0) == 0x80)
1663 {
1664 /*
1665 * 3-byte UTF-8 sequence...
1666 */
1667
1668 if (dstptr > (dstend - 3))
1669 break;
1670
1671 *dstptr++ = *srcptr++;
1672 *dstptr++ = *srcptr++;
1673 *dstptr++ = *srcptr++;
1674 }
1675 else if ((*srcptr & 0xf8) == 0xf0 && (srcptr[1] & 0xc0) == 0x80 && (srcptr[2] & 0xc0) == 0x80 && (srcptr[3] & 0xc0) == 0x80)
1676 {
1677 /*
1678 * 4-byte UTF-8 sequence...
1679 */
1680
1681 if (dstptr > (dstend - 4))
1682 break;
1683
1684 *dstptr++ = *srcptr++;
1685 *dstptr++ = *srcptr++;
1686 *dstptr++ = *srcptr++;
1687 *dstptr++ = *srcptr++;
1688 }
1689 else
1690 {
1691 /*
1692 * Bad UTF-8 sequence, this must be an ISO-8859-1 string...
1693 */
1694
1695 saw_8859 = 1;
1696
1697 if (dstptr > (dstend - 2))
1698 break;
1699
1700 *dstptr++ = 0xc0 | (*srcptr >> 6);
1701 *dstptr++ = 0x80 | (*srcptr++ & 0x3f);
1702 }
1703 }
1704
1705 *dstptr = '\0';
1706 }