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