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