/*
- * "$Id: sidechannel.c 6649 2007-07-11 21:46:42Z mike $"
+ * Side-channel API code for CUPS.
*
- * Side-channel API code for the Common UNIX Printing System (CUPS).
+ * Copyright © 2007-2019 by Apple Inc.
+ * Copyright © 2006 by Easy Software Products.
*
- * Copyright 2007 by Apple Inc.
- * Copyright 2006 by Easy Software Products.
- *
- * These coded instructions, statements, and computer programs are the
- * property of Apple Inc. and are protected by Federal copyright
- * law. Distribution and use rights are outlined in the file "LICENSE.txt"
- * which should have been included with this file. If this file is
- * file is missing or damaged, see the license at "http://www.cups.org/".
- *
- * This file is subject to the Apple OS-Developed Software exception.
- *
- * Contents:
- *
- * cupsSideChannelDoRequest() - Send a side-channel command to a backend
- * and wait for a response.
- * cupsSideChannelRead() - Read a side-channel message.
- * cupsSideChannelWrite() - Write a side-channel message.
+ * Licensed under Apache License v2.0. See the file "LICENSE" for more
+ * information.
*/
/*
*/
#include "sidechannel.h"
-#include "string.h"
-#include <unistd.h>
-#include <errno.h>
-#ifdef __hpux
-# include <sys/time.h>
+#include "cups-private.h"
+#include "debug-internal.h"
+#ifdef _WIN32
+# include <io.h>
#else
+# include <unistd.h>
# include <sys/select.h>
-#endif /* __hpux */
-#ifndef WIN32
# include <sys/time.h>
-#endif /* !WIN32 */
+#endif /* _WIN32 */
#ifdef HAVE_POLL
-# include <sys/poll.h>
+# include <poll.h>
#endif /* HAVE_POLL */
+/*
+ * Buffer size for side-channel requests...
+ */
+
+#define _CUPS_SC_MAX_DATA 65535
+#define _CUPS_SC_MAX_BUFFER 65540
+
+
/*
* 'cupsSideChannelDoRequest()' - Send a side-channel command to a backend and wait for a response.
*
* pointed to by the "data" parameter. cupsSideChannelDoRequest() will
* update the value to contain the number of data bytes in the buffer.
*
- * @since CUPS 1.3@
+ * @since CUPS 1.3/macOS 10.5@
*/
cups_sc_status_t /* O - Status of command */
* pointed to by the "data" parameter. cupsSideChannelDoRequest() will
* update the value to contain the number of data bytes in the buffer.
*
- * @since CUPS 1.3@
+ * @since CUPS 1.3/macOS 10.5@
*/
int /* O - 0 on success, -1 on error */
int *datalen, /* IO - Size of data buffer on entry, number of bytes in buffer on return */
double timeout) /* I - Timeout in seconds */
{
- char buffer[16388]; /* Message buffer */
- int bytes; /* Bytes read */
+ char *buffer; /* Message buffer */
+ ssize_t bytes; /* Bytes read */
int templen; /* Data length from message */
+ int nfds; /* Number of file descriptors */
#ifdef HAVE_POLL
struct pollfd pfd; /* Poll structure for poll() */
#else /* select() */
#endif /* HAVE_POLL */
+ DEBUG_printf(("cupsSideChannelRead(command=%p, status=%p, data=%p, "
+ "datalen=%p(%d), timeout=%.3f)", command, status, data,
+ datalen, datalen ? *datalen : -1, timeout));
+
/*
* Range check input...
*/
pfd.fd = CUPS_SC_FD;
pfd.events = POLLIN;
- if (timeout < 0.0)
- {
- if (poll(&pfd, 1, -1) < 1)
- return (-1);
- }
- else if (poll(&pfd, 1, (long)(timeout * 1000)) < 1)
- return (-1);
+ while ((nfds = poll(&pfd, 1,
+ timeout < 0.0 ? -1 : (int)(timeout * 1000))) < 0 &&
+ (errno == EINTR || errno == EAGAIN))
+ ;
#else /* select() */
FD_ZERO(&input_set);
FD_SET(CUPS_SC_FD, &input_set);
- if (timeout < 0.0)
- {
- if (select(CUPS_SC_FD + 1, &input_set, NULL, NULL, NULL) < 1)
- return (-1);
- }
- else
- {
- stimeout.tv_sec = (int)timeout;
- stimeout.tv_usec = (int)(timeout * 1000000) % 1000000;
+ stimeout.tv_sec = (int)timeout;
+ stimeout.tv_usec = (int)(timeout * 1000000) % 1000000;
+
+ while ((nfds = select(CUPS_SC_FD + 1, &input_set, NULL, NULL,
+ timeout < 0.0 ? NULL : &stimeout)) < 0 &&
+ (errno == EINTR || errno == EAGAIN))
+ ;
- if (select(CUPS_SC_FD + 1, &input_set, NULL, NULL, &stimeout) < 1)
- return (-1);
- }
#endif /* HAVE_POLL */
+ if (nfds < 1)
+ {
+ *command = CUPS_SC_CMD_NONE;
+ *status = nfds==0 ? CUPS_SC_STATUS_TIMEOUT : CUPS_SC_STATUS_IO_ERROR;
+ return (-1);
+ }
+
/*
* Read a side-channel message for the format:
*
* ------- -------------------------------------------
* 0 Command code
* 1 Status code
- * 2-3 Data length (network byte order) <= 16384
+ * 2-3 Data length (network byte order)
* 4-N Data
*/
- while ((bytes = read(CUPS_SC_FD, buffer, sizeof(buffer))) < 0)
+ if ((buffer = _cupsBufferGet(_CUPS_SC_MAX_BUFFER)) == NULL)
+ {
+ *command = CUPS_SC_CMD_NONE;
+ *status = CUPS_SC_STATUS_TOO_BIG;
+
+ return (-1);
+ }
+
+ while ((bytes = read(CUPS_SC_FD, buffer, _CUPS_SC_MAX_BUFFER)) < 0)
if (errno != EINTR && errno != EAGAIN)
+ {
+ DEBUG_printf(("1cupsSideChannelRead: Read error: %s", strerror(errno)));
+
+ _cupsBufferRelease(buffer);
+
+ *command = CUPS_SC_CMD_NONE;
+ *status = CUPS_SC_STATUS_IO_ERROR;
+
return (-1);
+ }
+
+ /*
+ * Watch for EOF or too few bytes...
+ */
+
+ if (bytes < 4)
+ {
+ DEBUG_printf(("1cupsSideChannelRead: Short read of " CUPS_LLFMT " bytes", CUPS_LLCAST bytes));
+
+ _cupsBufferRelease(buffer);
+
+ *command = CUPS_SC_CMD_NONE;
+ *status = CUPS_SC_STATUS_BAD_MESSAGE;
+
+ return (-1);
+ }
/*
* Validate the command code in the message...
*/
- if (buffer[0] < CUPS_SC_CMD_SOFT_RESET || buffer[0] > CUPS_SC_CMD_GET_STATE)
+ if (buffer[0] < CUPS_SC_CMD_SOFT_RESET ||
+ buffer[0] >= CUPS_SC_CMD_MAX)
+ {
+ DEBUG_printf(("1cupsSideChannelRead: Bad command %d!", buffer[0]));
+
+ _cupsBufferRelease(buffer);
+
+ *command = CUPS_SC_CMD_NONE;
+ *status = CUPS_SC_STATUS_BAD_MESSAGE;
+
return (-1);
+ }
*command = (cups_sc_command_t)buffer[0];
*status = CUPS_SC_STATUS_TOO_BIG;
}
- else if (templen > *datalen || templen > (bytes - 4))
+ else if (!datalen || templen > *datalen || templen > (bytes - 4))
{
/*
* Either the response is bigger than the provided buffer or the
*status = (cups_sc_status_t)buffer[1];
*datalen = templen;
- memcpy(data, buffer + 4, templen);
+ memcpy(data, buffer + 4, (size_t)templen);
}
+ _cupsBufferRelease(buffer);
+
+ DEBUG_printf(("1cupsSideChannelRead: Returning status=%d", *status));
+
return (0);
}
+/*
+ * 'cupsSideChannelSNMPGet()' - Query a SNMP OID's value.
+ *
+ * This function asks the backend to do a SNMP OID query on behalf of the
+ * filter, port monitor, or backend using the default community name.
+ *
+ * "oid" contains a numeric OID consisting of integers separated by periods,
+ * for example ".1.3.6.1.2.1.43". Symbolic names from SNMP MIBs are not
+ * supported and must be converted to their numeric forms.
+ *
+ * On input, "data" and "datalen" provide the location and size of the
+ * buffer to hold the OID value as a string. HEX-String (binary) values are
+ * converted to hexadecimal strings representing the binary data, while
+ * NULL-Value and unknown OID types are returned as the empty string.
+ * The returned "datalen" does not include the trailing nul.
+ *
+ * @code CUPS_SC_STATUS_NOT_IMPLEMENTED@ is returned by backends that do not
+ * support SNMP queries. @code CUPS_SC_STATUS_NO_RESPONSE@ is returned when
+ * the printer does not respond to the SNMP query.
+ *
+ * @since CUPS 1.4/macOS 10.6@
+ */
+
+cups_sc_status_t /* O - Query status */
+cupsSideChannelSNMPGet(
+ const char *oid, /* I - OID to query */
+ char *data, /* I - Buffer for OID value */
+ int *datalen, /* IO - Size of OID buffer on entry, size of value on return */
+ double timeout) /* I - Timeout in seconds */
+{
+ cups_sc_status_t status; /* Status of command */
+ cups_sc_command_t rcommand; /* Response command */
+ char *real_data; /* Real data buffer for response */
+ int real_datalen, /* Real length of data buffer */
+ real_oidlen; /* Length of returned OID string */
+
+
+ DEBUG_printf(("cupsSideChannelSNMPGet(oid=\"%s\", data=%p, datalen=%p(%d), "
+ "timeout=%.3f)", oid, data, datalen, datalen ? *datalen : -1,
+ timeout));
+
+ /*
+ * Range check input...
+ */
+
+ if (!oid || !*oid || !data || !datalen || *datalen < 2)
+ return (CUPS_SC_STATUS_BAD_MESSAGE);
+
+ *data = '\0';
+
+ /*
+ * Send the request to the backend and wait for a response...
+ */
+
+ if (cupsSideChannelWrite(CUPS_SC_CMD_SNMP_GET, CUPS_SC_STATUS_NONE, oid,
+ (int)strlen(oid) + 1, timeout))
+ return (CUPS_SC_STATUS_TIMEOUT);
+
+ if ((real_data = _cupsBufferGet(_CUPS_SC_MAX_BUFFER)) == NULL)
+ return (CUPS_SC_STATUS_TOO_BIG);
+
+ real_datalen = _CUPS_SC_MAX_BUFFER;
+ if (cupsSideChannelRead(&rcommand, &status, real_data, &real_datalen, timeout))
+ {
+ _cupsBufferRelease(real_data);
+ return (CUPS_SC_STATUS_TIMEOUT);
+ }
+
+ if (rcommand != CUPS_SC_CMD_SNMP_GET)
+ {
+ _cupsBufferRelease(real_data);
+ return (CUPS_SC_STATUS_BAD_MESSAGE);
+ }
+
+ if (status == CUPS_SC_STATUS_OK)
+ {
+ /*
+ * Parse the response of the form "oid\0value"...
+ */
+
+ real_oidlen = (int)strlen(real_data) + 1;
+ real_datalen -= real_oidlen;
+
+ if ((real_datalen + 1) > *datalen)
+ {
+ _cupsBufferRelease(real_data);
+ return (CUPS_SC_STATUS_TOO_BIG);
+ }
+
+ memcpy(data, real_data + real_oidlen, (size_t)real_datalen);
+ data[real_datalen] = '\0';
+
+ *datalen = real_datalen;
+ }
+
+ _cupsBufferRelease(real_data);
+
+ return (status);
+}
+
+
+/*
+ * 'cupsSideChannelSNMPWalk()' - Query multiple SNMP OID values.
+ *
+ * This function asks the backend to do multiple SNMP OID queries on behalf
+ * of the filter, port monitor, or backend using the default community name.
+ * All OIDs under the "parent" OID are queried and the results are sent to
+ * the callback function you provide.
+ *
+ * "oid" contains a numeric OID consisting of integers separated by periods,
+ * for example ".1.3.6.1.2.1.43". Symbolic names from SNMP MIBs are not
+ * supported and must be converted to their numeric forms.
+ *
+ * "timeout" specifies the timeout for each OID query. The total amount of
+ * time will depend on the number of OID values found and the time required
+ * for each query.
+ *
+ * "cb" provides a function to call for every value that is found. "context"
+ * is an application-defined pointer that is sent to the callback function
+ * along with the OID and current data. The data passed to the callback is the
+ * same as returned by @link cupsSideChannelSNMPGet@.
+ *
+ * @code CUPS_SC_STATUS_NOT_IMPLEMENTED@ is returned by backends that do not
+ * support SNMP queries. @code CUPS_SC_STATUS_NO_RESPONSE@ is returned when
+ * the printer does not respond to the first SNMP query.
+ *
+ * @since CUPS 1.4/macOS 10.6@
+ */
+
+cups_sc_status_t /* O - Status of first query of @code CUPS_SC_STATUS_OK@ on success */
+cupsSideChannelSNMPWalk(
+ const char *oid, /* I - First numeric OID to query */
+ double timeout, /* I - Timeout for each query in seconds */
+ cups_sc_walk_func_t cb, /* I - Function to call with each value */
+ void *context) /* I - Application-defined pointer to send to callback */
+{
+ cups_sc_status_t status; /* Status of command */
+ cups_sc_command_t rcommand; /* Response command */
+ char *real_data; /* Real data buffer for response */
+ int real_datalen; /* Real length of data buffer */
+ size_t real_oidlen, /* Length of returned OID string */
+ oidlen; /* Length of first OID */
+ const char *current_oid; /* Current OID */
+ char last_oid[2048]; /* Last OID */
+
+
+ DEBUG_printf(("cupsSideChannelSNMPWalk(oid=\"%s\", timeout=%.3f, cb=%p, "
+ "context=%p)", oid, timeout, cb, context));
+
+ /*
+ * Range check input...
+ */
+
+ if (!oid || !*oid || !cb)
+ return (CUPS_SC_STATUS_BAD_MESSAGE);
+
+ if ((real_data = _cupsBufferGet(_CUPS_SC_MAX_BUFFER)) == NULL)
+ return (CUPS_SC_STATUS_TOO_BIG);
+
+ /*
+ * Loop until the OIDs don't match...
+ */
+
+ current_oid = oid;
+ oidlen = strlen(oid);
+ last_oid[0] = '\0';
+
+ do
+ {
+ /*
+ * Send the request to the backend and wait for a response...
+ */
+
+ if (cupsSideChannelWrite(CUPS_SC_CMD_SNMP_GET_NEXT, CUPS_SC_STATUS_NONE,
+ current_oid, (int)strlen(current_oid) + 1, timeout))
+ {
+ _cupsBufferRelease(real_data);
+ return (CUPS_SC_STATUS_TIMEOUT);
+ }
+
+ real_datalen = _CUPS_SC_MAX_BUFFER;
+ if (cupsSideChannelRead(&rcommand, &status, real_data, &real_datalen,
+ timeout))
+ {
+ _cupsBufferRelease(real_data);
+ return (CUPS_SC_STATUS_TIMEOUT);
+ }
+
+ if (rcommand != CUPS_SC_CMD_SNMP_GET_NEXT)
+ {
+ _cupsBufferRelease(real_data);
+ return (CUPS_SC_STATUS_BAD_MESSAGE);
+ }
+
+ if (status == CUPS_SC_STATUS_OK)
+ {
+ /*
+ * Parse the response of the form "oid\0value"...
+ */
+
+ if (strncmp(real_data, oid, oidlen) || real_data[oidlen] != '.' ||
+ !strcmp(real_data, last_oid))
+ {
+ /*
+ * Done with this set of OIDs...
+ */
+
+ _cupsBufferRelease(real_data);
+ return (CUPS_SC_STATUS_OK);
+ }
+
+ if ((size_t)real_datalen < sizeof(real_data))
+ real_data[real_datalen] = '\0';
+
+ real_oidlen = strlen(real_data) + 1;
+ real_datalen -= (int)real_oidlen;
+
+ /*
+ * Call the callback with the OID and data...
+ */
+
+ (*cb)(real_data, real_data + real_oidlen, real_datalen, context);
+
+ /*
+ * Update the current OID...
+ */
+
+ current_oid = real_data;
+ strlcpy(last_oid, current_oid, sizeof(last_oid));
+ }
+ }
+ while (status == CUPS_SC_STATUS_OK);
+
+ _cupsBufferRelease(real_data);
+
+ return (status);
+}
+
+
/*
* 'cupsSideChannelWrite()' - Write a side-channel message.
*
* This function is normally only called by backend programs to send
* responses to a filter, driver, or port monitor program.
*
- * @since CUPS 1.3@
+ * @since CUPS 1.3/macOS 10.5@
*/
int /* O - 0 on success, -1 on error */
int datalen, /* I - Number of bytes of data */
double timeout) /* I - Timeout in seconds */
{
- char buffer[16388]; /* Message buffer */
- int bytes; /* Bytes written */
+ char *buffer; /* Message buffer */
+ ssize_t bytes; /* Bytes written */
#ifdef HAVE_POLL
struct pollfd pfd; /* Poll structure for poll() */
#else /* select() */
* Range check input...
*/
- if (command < CUPS_SC_CMD_SOFT_RESET || command > CUPS_SC_CMD_GET_STATE ||
- datalen < 0 || datalen > 16384 || (datalen > 0 && !data))
+ if (command < CUPS_SC_CMD_SOFT_RESET || command >= CUPS_SC_CMD_MAX ||
+ datalen < 0 || datalen > _CUPS_SC_MAX_DATA || (datalen > 0 && !data))
return (-1);
/*
if (poll(&pfd, 1, -1) < 1)
return (-1);
}
- else if (poll(&pfd, 1, (long)(timeout * 1000)) < 1)
+ else if (poll(&pfd, 1, (int)(timeout * 1000)) < 1)
return (-1);
#else /* select() */
* 4-N Data
*/
- buffer[0] = command;
- buffer[1] = status;
- buffer[2] = datalen >> 8;
- buffer[3] = datalen & 255;
+ if ((buffer = _cupsBufferGet((size_t)datalen + 4)) == NULL)
+ return (-1);
+
+ buffer[0] = (char)command;
+ buffer[1] = (char)status;
+ buffer[2] = (char)(datalen >> 8);
+ buffer[3] = (char)(datalen & 255);
bytes = 4;
if (datalen > 0)
{
- memcpy(buffer + 4, data, datalen);
+ memcpy(buffer + 4, data, (size_t)datalen);
bytes += datalen;
}
- while (write(CUPS_SC_FD, buffer, bytes) < 0)
+ while (write(CUPS_SC_FD, buffer, (size_t)bytes) < 0)
if (errno != EINTR && errno != EAGAIN)
+ {
+ _cupsBufferRelease(buffer);
return (-1);
+ }
+
+ _cupsBufferRelease(buffer);
return (0);
}
-
-
-/*
- * End of "$Id: sidechannel.c 6649 2007-07-11 21:46:42Z mike $".
- */