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