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