You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

617 lines
16 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
* Side-channel API code for CUPS.
*
* Copyright © 2007-2019 by Apple Inc.
* Copyright © 2006 by Easy Software Products.
*
* Licensed under Apache License v2.0. See the file "LICENSE" for more
* information.
*/
/*
* Include necessary headers...
*/
#include "sidechannel.h"
#include "cups-private.h"
#include "debug-internal.h"
#ifdef _WIN32
# include <io.h>
#else
# include <unistd.h>
# include <sys/select.h>
# include <sys/time.h>
#endif /* _WIN32 */
#ifdef HAVE_POLL
# 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.
*
* This function is normally only called by filters, drivers, or port
* monitors in order to communicate with the backend used by the current
* printer. Programs must be prepared to handle timeout or "not
* implemented" status codes, which indicate that the backend or device
* do not support the specified side-channel command.
*
* The "datalen" parameter must be initialized to the size of the buffer
* 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/macOS 10.5@
*/
cups_sc_status_t /* O - Status of command */
cupsSideChannelDoRequest(
cups_sc_command_t command, /* I - Command to send */
char *data, /* O - Response data buffer pointer */
int *datalen, /* IO - Size of data buffer on entry, number of bytes in buffer on return */
double timeout) /* I - Timeout in seconds */
{
cups_sc_status_t status; /* Status of command */
cups_sc_command_t rcommand; /* Response command */
if (cupsSideChannelWrite(command, CUPS_SC_STATUS_NONE, NULL, 0, timeout))
return (CUPS_SC_STATUS_TIMEOUT);
if (cupsSideChannelRead(&rcommand, &status, data, datalen, timeout))
return (CUPS_SC_STATUS_TIMEOUT);
if (rcommand != command)
return (CUPS_SC_STATUS_BAD_MESSAGE);
return (status);
}
/*
* 'cupsSideChannelRead()' - Read a side-channel message.
*
* This function is normally only called by backend programs to read
* commands from a filter, driver, or port monitor program. The
* caller must be prepared to handle incomplete or invalid messages
* and return the corresponding status codes.
*
* The "datalen" parameter must be initialized to the size of the buffer
* 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/macOS 10.5@
*/
int /* O - 0 on success, -1 on error */
cupsSideChannelRead(
cups_sc_command_t *command, /* O - Command code */
cups_sc_status_t *status, /* O - Status code */
char *data, /* O - Data buffer pointer */
int *datalen, /* IO - Size of data buffer on entry, number of bytes in buffer on return */
double timeout) /* I - Timeout in seconds */
{
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() */
fd_set input_set; /* Input set for select() */
struct timeval stimeout; /* Timeout value for 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...
*/
if (!command || !status)
return (-1);
/*
* See if we have pending data on the side-channel socket...
*/
#ifdef HAVE_POLL
pfd.fd = CUPS_SC_FD;
pfd.events = POLLIN;
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);
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))
;
#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:
*
* Byte(s) Description
* ------- -------------------------------------------
* 0 Command code
* 1 Status code
* 2-3 Data length (network byte order)
* 4-N Data
*/
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_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];
/*
* Validate the data length in the message...
*/
templen = ((buffer[2] & 255) << 8) | (buffer[3] & 255);
if (templen > 0 && (!data || !datalen))
{
/*
* Either the response is bigger than the provided buffer or the
* response is bigger than we've read...
*/
*status = CUPS_SC_STATUS_TOO_BIG;
}
else if (!datalen || templen > *datalen || templen > (bytes - 4))
{
/*
* Either the response is bigger than the provided buffer or the
* response is bigger than we've read...
*/
*status = CUPS_SC_STATUS_TOO_BIG;
}
else
{
/*
* The response data will fit, copy it over and provide the actual
* length...
*/
*status = (cups_sc_status_t)buffer[1];
*datalen = 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/macOS 10.5@
*/
int /* O - 0 on success, -1 on error */
cupsSideChannelWrite(
cups_sc_command_t command, /* I - Command code */
cups_sc_status_t status, /* I - Status code */
const char *data, /* I - Data buffer pointer */
int datalen, /* I - Number of bytes of data */
double timeout) /* I - Timeout in seconds */
{
char *buffer; /* Message buffer */
ssize_t bytes; /* Bytes written */
#ifdef HAVE_POLL
struct pollfd pfd; /* Poll structure for poll() */
#else /* select() */
fd_set output_set; /* Output set for select() */
struct timeval stimeout; /* Timeout value for select() */
#endif /* HAVE_POLL */
/*
* Range check input...
*/
if (command < CUPS_SC_CMD_SOFT_RESET || command >= CUPS_SC_CMD_MAX ||
datalen < 0 || datalen > _CUPS_SC_MAX_DATA || (datalen > 0 && !data))
return (-1);
/*
* See if we can safely write to the side-channel socket...
*/
#ifdef HAVE_POLL
pfd.fd = CUPS_SC_FD;
pfd.events = POLLOUT;
if (timeout < 0.0)
{
if (poll(&pfd, 1, -1) < 1)
return (-1);
}
else if (poll(&pfd, 1, (int)(timeout * 1000)) < 1)
return (-1);
#else /* select() */
FD_ZERO(&output_set);
FD_SET(CUPS_SC_FD, &output_set);
if (timeout < 0.0)
{
if (select(CUPS_SC_FD + 1, NULL, &output_set, NULL, NULL) < 1)
return (-1);
}
else
{
stimeout.tv_sec = (int)timeout;
stimeout.tv_usec = (int)(timeout * 1000000) % 1000000;
if (select(CUPS_SC_FD + 1, NULL, &output_set, NULL, &stimeout) < 1)
return (-1);
}
#endif /* HAVE_POLL */
/*
* Write a side-channel message in the format:
*
* Byte(s) Description
* ------- -------------------------------------------
* 0 Command code
* 1 Status code
* 2-3 Data length (network byte order) <= 16384
* 4-N Data
*/
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, (size_t)datalen);
bytes += datalen;
}
while (write(CUPS_SC_FD, buffer, (size_t)bytes) < 0)
if (errno != EINTR && errno != EAGAIN)
{
_cupsBufferRelease(buffer);
return (-1);
}
_cupsBufferRelease(buffer);
return (0);
}