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