| /* |
| * 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); |
| } |