]> git.ipfire.org Git - thirdparty/cups.git/blob - cups/sidechannel.c
Fix clang warning.
[thirdparty/cups.git] / cups / sidechannel.c
1 /*
2 * Side-channel API code for CUPS.
3 *
4 * Copyright 2007-2014 by Apple Inc.
5 * Copyright 2006 by Easy Software Products.
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 #include "sidechannel.h"
15 #include "cups-private.h"
16 #include "debug-internal.h"
17 #ifdef _WIN32
18 # include <io.h>
19 #else
20 # include <unistd.h>
21 # include <sys/select.h>
22 # include <sys/time.h>
23 #endif /* _WIN32 */
24 #ifdef HAVE_POLL
25 # include <poll.h>
26 #endif /* HAVE_POLL */
27
28
29 /*
30 * Buffer size for side-channel requests...
31 */
32
33 #define _CUPS_SC_MAX_DATA 65535
34 #define _CUPS_SC_MAX_BUFFER 65540
35
36
37 /*
38 * 'cupsSideChannelDoRequest()' - Send a side-channel command to a backend and wait for a response.
39 *
40 * This function is normally only called by filters, drivers, or port
41 * monitors in order to communicate with the backend used by the current
42 * printer. Programs must be prepared to handle timeout or "not
43 * implemented" status codes, which indicate that the backend or device
44 * do not support the specified side-channel command.
45 *
46 * The "datalen" parameter must be initialized to the size of the buffer
47 * pointed to by the "data" parameter. cupsSideChannelDoRequest() will
48 * update the value to contain the number of data bytes in the buffer.
49 *
50 * @since CUPS 1.3/macOS 10.5@
51 */
52
53 cups_sc_status_t /* O - Status of command */
54 cupsSideChannelDoRequest(
55 cups_sc_command_t command, /* I - Command to send */
56 char *data, /* O - Response data buffer pointer */
57 int *datalen, /* IO - Size of data buffer on entry, number of bytes in buffer on return */
58 double timeout) /* I - Timeout in seconds */
59 {
60 cups_sc_status_t status; /* Status of command */
61 cups_sc_command_t rcommand; /* Response command */
62
63
64 if (cupsSideChannelWrite(command, CUPS_SC_STATUS_NONE, NULL, 0, timeout))
65 return (CUPS_SC_STATUS_TIMEOUT);
66
67 if (cupsSideChannelRead(&rcommand, &status, data, datalen, timeout))
68 return (CUPS_SC_STATUS_TIMEOUT);
69
70 if (rcommand != command)
71 return (CUPS_SC_STATUS_BAD_MESSAGE);
72
73 return (status);
74 }
75
76
77 /*
78 * 'cupsSideChannelRead()' - Read a side-channel message.
79 *
80 * This function is normally only called by backend programs to read
81 * commands from a filter, driver, or port monitor program. The
82 * caller must be prepared to handle incomplete or invalid messages
83 * and return the corresponding status codes.
84 *
85 * The "datalen" parameter must be initialized to the size of the buffer
86 * pointed to by the "data" parameter. cupsSideChannelDoRequest() will
87 * update the value to contain the number of data bytes in the buffer.
88 *
89 * @since CUPS 1.3/macOS 10.5@
90 */
91
92 int /* O - 0 on success, -1 on error */
93 cupsSideChannelRead(
94 cups_sc_command_t *command, /* O - Command code */
95 cups_sc_status_t *status, /* O - Status code */
96 char *data, /* O - Data buffer pointer */
97 int *datalen, /* IO - Size of data buffer on entry, number of bytes in buffer on return */
98 double timeout) /* I - Timeout in seconds */
99 {
100 char *buffer; /* Message buffer */
101 ssize_t bytes; /* Bytes read */
102 int templen; /* Data length from message */
103 int nfds; /* Number of file descriptors */
104 #ifdef HAVE_POLL
105 struct pollfd pfd; /* Poll structure for poll() */
106 #else /* select() */
107 fd_set input_set; /* Input set for select() */
108 struct timeval stimeout; /* Timeout value for select() */
109 #endif /* HAVE_POLL */
110
111
112 DEBUG_printf(("cupsSideChannelRead(command=%p, status=%p, data=%p, "
113 "datalen=%p(%d), timeout=%.3f)", command, status, data,
114 datalen, datalen ? *datalen : -1, timeout));
115
116 /*
117 * Range check input...
118 */
119
120 if (!command || !status)
121 return (-1);
122
123 /*
124 * See if we have pending data on the side-channel socket...
125 */
126
127 #ifdef HAVE_POLL
128 pfd.fd = CUPS_SC_FD;
129 pfd.events = POLLIN;
130
131 while ((nfds = poll(&pfd, 1,
132 timeout < 0.0 ? -1 : (int)(timeout * 1000))) < 0 &&
133 (errno == EINTR || errno == EAGAIN))
134 ;
135
136 #else /* select() */
137 FD_ZERO(&input_set);
138 FD_SET(CUPS_SC_FD, &input_set);
139
140 stimeout.tv_sec = (int)timeout;
141 stimeout.tv_usec = (int)(timeout * 1000000) % 1000000;
142
143 while ((nfds = select(CUPS_SC_FD + 1, &input_set, NULL, NULL,
144 timeout < 0.0 ? NULL : &stimeout)) < 0 &&
145 (errno == EINTR || errno == EAGAIN))
146 ;
147
148 #endif /* HAVE_POLL */
149
150 if (nfds < 1)
151 {
152 *command = CUPS_SC_CMD_NONE;
153 *status = nfds==0 ? CUPS_SC_STATUS_TIMEOUT : CUPS_SC_STATUS_IO_ERROR;
154 return (-1);
155 }
156
157 /*
158 * Read a side-channel message for the format:
159 *
160 * Byte(s) Description
161 * ------- -------------------------------------------
162 * 0 Command code
163 * 1 Status code
164 * 2-3 Data length (network byte order)
165 * 4-N Data
166 */
167
168 if ((buffer = _cupsBufferGet(_CUPS_SC_MAX_BUFFER)) == NULL)
169 {
170 *command = CUPS_SC_CMD_NONE;
171 *status = CUPS_SC_STATUS_TOO_BIG;
172
173 return (-1);
174 }
175
176 while ((bytes = read(CUPS_SC_FD, buffer, _CUPS_SC_MAX_BUFFER)) < 0)
177 if (errno != EINTR && errno != EAGAIN)
178 {
179 DEBUG_printf(("1cupsSideChannelRead: Read error: %s", strerror(errno)));
180
181 _cupsBufferRelease(buffer);
182
183 *command = CUPS_SC_CMD_NONE;
184 *status = CUPS_SC_STATUS_IO_ERROR;
185
186 return (-1);
187 }
188
189 /*
190 * Watch for EOF or too few bytes...
191 */
192
193 if (bytes < 4)
194 {
195 DEBUG_printf(("1cupsSideChannelRead: Short read of " CUPS_LLFMT " bytes", CUPS_LLCAST bytes));
196
197 _cupsBufferRelease(buffer);
198
199 *command = CUPS_SC_CMD_NONE;
200 *status = CUPS_SC_STATUS_BAD_MESSAGE;
201
202 return (-1);
203 }
204
205 /*
206 * Validate the command code in the message...
207 */
208
209 if (buffer[0] < CUPS_SC_CMD_SOFT_RESET ||
210 buffer[0] >= CUPS_SC_CMD_MAX)
211 {
212 DEBUG_printf(("1cupsSideChannelRead: Bad command %d!", buffer[0]));
213
214 _cupsBufferRelease(buffer);
215
216 *command = CUPS_SC_CMD_NONE;
217 *status = CUPS_SC_STATUS_BAD_MESSAGE;
218
219 return (-1);
220 }
221
222 *command = (cups_sc_command_t)buffer[0];
223
224 /*
225 * Validate the data length in the message...
226 */
227
228 templen = ((buffer[2] & 255) << 8) | (buffer[3] & 255);
229
230 if (templen > 0 && (!data || !datalen))
231 {
232 /*
233 * Either the response is bigger than the provided buffer or the
234 * response is bigger than we've read...
235 */
236
237 *status = CUPS_SC_STATUS_TOO_BIG;
238 }
239 else if (!datalen || templen > *datalen || templen > (bytes - 4))
240 {
241 /*
242 * Either the response is bigger than the provided buffer or the
243 * response is bigger than we've read...
244 */
245
246 *status = CUPS_SC_STATUS_TOO_BIG;
247 }
248 else
249 {
250 /*
251 * The response data will fit, copy it over and provide the actual
252 * length...
253 */
254
255 *status = (cups_sc_status_t)buffer[1];
256 *datalen = templen;
257
258 memcpy(data, buffer + 4, (size_t)templen);
259 }
260
261 _cupsBufferRelease(buffer);
262
263 DEBUG_printf(("1cupsSideChannelRead: Returning status=%d", *status));
264
265 return (0);
266 }
267
268
269 /*
270 * 'cupsSideChannelSNMPGet()' - Query a SNMP OID's value.
271 *
272 * This function asks the backend to do a SNMP OID query on behalf of the
273 * filter, port monitor, or backend using the default community name.
274 *
275 * "oid" contains a numeric OID consisting of integers separated by periods,
276 * for example ".1.3.6.1.2.1.43". Symbolic names from SNMP MIBs are not
277 * supported and must be converted to their numeric forms.
278 *
279 * On input, "data" and "datalen" provide the location and size of the
280 * buffer to hold the OID value as a string. HEX-String (binary) values are
281 * converted to hexadecimal strings representing the binary data, while
282 * NULL-Value and unknown OID types are returned as the empty string.
283 * The returned "datalen" does not include the trailing nul.
284 *
285 * @code CUPS_SC_STATUS_NOT_IMPLEMENTED@ is returned by backends that do not
286 * support SNMP queries. @code CUPS_SC_STATUS_NO_RESPONSE@ is returned when
287 * the printer does not respond to the SNMP query.
288 *
289 * @since CUPS 1.4/macOS 10.6@
290 */
291
292 cups_sc_status_t /* O - Query status */
293 cupsSideChannelSNMPGet(
294 const char *oid, /* I - OID to query */
295 char *data, /* I - Buffer for OID value */
296 int *datalen, /* IO - Size of OID buffer on entry, size of value on return */
297 double timeout) /* I - Timeout in seconds */
298 {
299 cups_sc_status_t status; /* Status of command */
300 cups_sc_command_t rcommand; /* Response command */
301 char *real_data; /* Real data buffer for response */
302 int real_datalen, /* Real length of data buffer */
303 real_oidlen; /* Length of returned OID string */
304
305
306 DEBUG_printf(("cupsSideChannelSNMPGet(oid=\"%s\", data=%p, datalen=%p(%d), "
307 "timeout=%.3f)", oid, data, datalen, datalen ? *datalen : -1,
308 timeout));
309
310 /*
311 * Range check input...
312 */
313
314 if (!oid || !*oid || !data || !datalen || *datalen < 2)
315 return (CUPS_SC_STATUS_BAD_MESSAGE);
316
317 *data = '\0';
318
319 /*
320 * Send the request to the backend and wait for a response...
321 */
322
323 if (cupsSideChannelWrite(CUPS_SC_CMD_SNMP_GET, CUPS_SC_STATUS_NONE, oid,
324 (int)strlen(oid) + 1, timeout))
325 return (CUPS_SC_STATUS_TIMEOUT);
326
327 if ((real_data = _cupsBufferGet(_CUPS_SC_MAX_BUFFER)) == NULL)
328 return (CUPS_SC_STATUS_TOO_BIG);
329
330 real_datalen = _CUPS_SC_MAX_BUFFER;
331 if (cupsSideChannelRead(&rcommand, &status, real_data, &real_datalen, timeout))
332 {
333 _cupsBufferRelease(real_data);
334 return (CUPS_SC_STATUS_TIMEOUT);
335 }
336
337 if (rcommand != CUPS_SC_CMD_SNMP_GET)
338 {
339 _cupsBufferRelease(real_data);
340 return (CUPS_SC_STATUS_BAD_MESSAGE);
341 }
342
343 if (status == CUPS_SC_STATUS_OK)
344 {
345 /*
346 * Parse the response of the form "oid\0value"...
347 */
348
349 real_oidlen = (int)strlen(real_data) + 1;
350 real_datalen -= real_oidlen;
351
352 if ((real_datalen + 1) > *datalen)
353 {
354 _cupsBufferRelease(real_data);
355 return (CUPS_SC_STATUS_TOO_BIG);
356 }
357
358 memcpy(data, real_data + real_oidlen, (size_t)real_datalen);
359 data[real_datalen] = '\0';
360
361 *datalen = real_datalen;
362 }
363
364 _cupsBufferRelease(real_data);
365
366 return (status);
367 }
368
369
370 /*
371 * 'cupsSideChannelSNMPWalk()' - Query multiple SNMP OID values.
372 *
373 * This function asks the backend to do multiple SNMP OID queries on behalf
374 * of the filter, port monitor, or backend using the default community name.
375 * All OIDs under the "parent" OID are queried and the results are sent to
376 * the callback function you provide.
377 *
378 * "oid" contains a numeric OID consisting of integers separated by periods,
379 * for example ".1.3.6.1.2.1.43". Symbolic names from SNMP MIBs are not
380 * supported and must be converted to their numeric forms.
381 *
382 * "timeout" specifies the timeout for each OID query. The total amount of
383 * time will depend on the number of OID values found and the time required
384 * for each query.
385 *
386 * "cb" provides a function to call for every value that is found. "context"
387 * is an application-defined pointer that is sent to the callback function
388 * along with the OID and current data. The data passed to the callback is the
389 * same as returned by @link cupsSideChannelSNMPGet@.
390 *
391 * @code CUPS_SC_STATUS_NOT_IMPLEMENTED@ is returned by backends that do not
392 * support SNMP queries. @code CUPS_SC_STATUS_NO_RESPONSE@ is returned when
393 * the printer does not respond to the first SNMP query.
394 *
395 * @since CUPS 1.4/macOS 10.6@
396 */
397
398 cups_sc_status_t /* O - Status of first query of @code CUPS_SC_STATUS_OK@ on success */
399 cupsSideChannelSNMPWalk(
400 const char *oid, /* I - First numeric OID to query */
401 double timeout, /* I - Timeout for each query in seconds */
402 cups_sc_walk_func_t cb, /* I - Function to call with each value */
403 void *context) /* I - Application-defined pointer to send to callback */
404 {
405 cups_sc_status_t status; /* Status of command */
406 cups_sc_command_t rcommand; /* Response command */
407 char *real_data; /* Real data buffer for response */
408 int real_datalen; /* Real length of data buffer */
409 size_t real_oidlen, /* Length of returned OID string */
410 oidlen; /* Length of first OID */
411 const char *current_oid; /* Current OID */
412 char last_oid[2048]; /* Last OID */
413
414
415 DEBUG_printf(("cupsSideChannelSNMPWalk(oid=\"%s\", timeout=%.3f, cb=%p, "
416 "context=%p)", oid, timeout, cb, context));
417
418 /*
419 * Range check input...
420 */
421
422 if (!oid || !*oid || !cb)
423 return (CUPS_SC_STATUS_BAD_MESSAGE);
424
425 if ((real_data = _cupsBufferGet(_CUPS_SC_MAX_BUFFER)) == NULL)
426 return (CUPS_SC_STATUS_TOO_BIG);
427
428 /*
429 * Loop until the OIDs don't match...
430 */
431
432 current_oid = oid;
433 oidlen = strlen(oid);
434 last_oid[0] = '\0';
435
436 do
437 {
438 /*
439 * Send the request to the backend and wait for a response...
440 */
441
442 if (cupsSideChannelWrite(CUPS_SC_CMD_SNMP_GET_NEXT, CUPS_SC_STATUS_NONE,
443 current_oid, (int)strlen(current_oid) + 1, timeout))
444 {
445 _cupsBufferRelease(real_data);
446 return (CUPS_SC_STATUS_TIMEOUT);
447 }
448
449 real_datalen = _CUPS_SC_MAX_BUFFER;
450 if (cupsSideChannelRead(&rcommand, &status, real_data, &real_datalen,
451 timeout))
452 {
453 _cupsBufferRelease(real_data);
454 return (CUPS_SC_STATUS_TIMEOUT);
455 }
456
457 if (rcommand != CUPS_SC_CMD_SNMP_GET_NEXT)
458 {
459 _cupsBufferRelease(real_data);
460 return (CUPS_SC_STATUS_BAD_MESSAGE);
461 }
462
463 if (status == CUPS_SC_STATUS_OK)
464 {
465 /*
466 * Parse the response of the form "oid\0value"...
467 */
468
469 if (strncmp(real_data, oid, oidlen) || real_data[oidlen] != '.' ||
470 !strcmp(real_data, last_oid))
471 {
472 /*
473 * Done with this set of OIDs...
474 */
475
476 _cupsBufferRelease(real_data);
477 return (CUPS_SC_STATUS_OK);
478 }
479
480 if ((size_t)real_datalen < sizeof(real_data))
481 real_data[real_datalen] = '\0';
482
483 real_oidlen = strlen(real_data) + 1;
484 real_datalen -= (int)real_oidlen;
485
486 /*
487 * Call the callback with the OID and data...
488 */
489
490 (*cb)(real_data, real_data + real_oidlen, real_datalen, context);
491
492 /*
493 * Update the current OID...
494 */
495
496 current_oid = real_data;
497 strlcpy(last_oid, current_oid, sizeof(last_oid));
498 }
499 }
500 while (status == CUPS_SC_STATUS_OK);
501
502 _cupsBufferRelease(real_data);
503
504 return (status);
505 }
506
507
508 /*
509 * 'cupsSideChannelWrite()' - Write a side-channel message.
510 *
511 * This function is normally only called by backend programs to send
512 * responses to a filter, driver, or port monitor program.
513 *
514 * @since CUPS 1.3/macOS 10.5@
515 */
516
517 int /* O - 0 on success, -1 on error */
518 cupsSideChannelWrite(
519 cups_sc_command_t command, /* I - Command code */
520 cups_sc_status_t status, /* I - Status code */
521 const char *data, /* I - Data buffer pointer */
522 int datalen, /* I - Number of bytes of data */
523 double timeout) /* I - Timeout in seconds */
524 {
525 char *buffer; /* Message buffer */
526 ssize_t bytes; /* Bytes written */
527 #ifdef HAVE_POLL
528 struct pollfd pfd; /* Poll structure for poll() */
529 #else /* select() */
530 fd_set output_set; /* Output set for select() */
531 struct timeval stimeout; /* Timeout value for select() */
532 #endif /* HAVE_POLL */
533
534
535 /*
536 * Range check input...
537 */
538
539 if (command < CUPS_SC_CMD_SOFT_RESET || command >= CUPS_SC_CMD_MAX ||
540 datalen < 0 || datalen > _CUPS_SC_MAX_DATA || (datalen > 0 && !data))
541 return (-1);
542
543 /*
544 * See if we can safely write to the side-channel socket...
545 */
546
547 #ifdef HAVE_POLL
548 pfd.fd = CUPS_SC_FD;
549 pfd.events = POLLOUT;
550
551 if (timeout < 0.0)
552 {
553 if (poll(&pfd, 1, -1) < 1)
554 return (-1);
555 }
556 else if (poll(&pfd, 1, (int)(timeout * 1000)) < 1)
557 return (-1);
558
559 #else /* select() */
560 FD_ZERO(&output_set);
561 FD_SET(CUPS_SC_FD, &output_set);
562
563 if (timeout < 0.0)
564 {
565 if (select(CUPS_SC_FD + 1, NULL, &output_set, NULL, NULL) < 1)
566 return (-1);
567 }
568 else
569 {
570 stimeout.tv_sec = (int)timeout;
571 stimeout.tv_usec = (int)(timeout * 1000000) % 1000000;
572
573 if (select(CUPS_SC_FD + 1, NULL, &output_set, NULL, &stimeout) < 1)
574 return (-1);
575 }
576 #endif /* HAVE_POLL */
577
578 /*
579 * Write a side-channel message in the format:
580 *
581 * Byte(s) Description
582 * ------- -------------------------------------------
583 * 0 Command code
584 * 1 Status code
585 * 2-3 Data length (network byte order) <= 16384
586 * 4-N Data
587 */
588
589 if ((buffer = _cupsBufferGet((size_t)datalen + 4)) == NULL)
590 return (-1);
591
592 buffer[0] = command;
593 buffer[1] = status;
594 buffer[2] = (char)(datalen >> 8);
595 buffer[3] = (char)(datalen & 255);
596
597 bytes = 4;
598
599 if (datalen > 0)
600 {
601 memcpy(buffer + 4, data, (size_t)datalen);
602 bytes += datalen;
603 }
604
605 while (write(CUPS_SC_FD, buffer, (size_t)bytes) < 0)
606 if (errno != EINTR && errno != EAGAIN)
607 {
608 _cupsBufferRelease(buffer);
609 return (-1);
610 }
611
612 _cupsBufferRelease(buffer);
613
614 return (0);
615 }