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