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