]> git.ipfire.org Git - thirdparty/cups.git/blame - backend/snmp.c
Update more IPP strings.
[thirdparty/cups.git] / backend / snmp.c
CommitLineData
89d46774 1/*
7e86f2f6 2 * SNMP discovery backend for CUPS.
89d46774 3 *
53f8d64f
MS
4 * Copyright © 2007-2014 by Apple Inc.
5 * Copyright © 2006-2007 by Easy Software Products, all rights reserved.
89d46774 6 *
53f8d64f
MS
7 * Licensed under Apache License v2.0. See the file "LICENSE" for more
8 * information.
89d46774 9 */
10
11/*
12 * Include necessary headers.
13 */
14
ed486911 15#include "backend-private.h"
89d46774 16#include <cups/array.h>
17#include <cups/file.h>
91c84a35 18#include <cups/http-private.h>
b94498cf 19#include <regex.h>
89d46774 20
21
22/*
23 * This backend implements SNMP printer discovery. It uses a broadcast-
b94498cf 24 * based approach to get SNMP response packets from potential printers,
7a14d768
MS
25 * requesting OIDs from the Host and Port Monitor MIBs, does a URI
26 * lookup based on the device description string, and finally a probe of
27 * port 9100 (AppSocket) and 515 (LPD).
89d46774 28 *
29 * The current focus is on printers with internal network cards, although
7a14d768 30 * the code also works with many external print servers as well.
89d46774 31 *
32 * The backend reads the snmp.conf file from the CUPS_SERVERROOT directory
33 * which can contain comments, blank lines, or any number of the following
34 * directives:
35 *
36 * Address ip-address
37 * Address @LOCAL
38 * Address @IF(name)
39 * Community name
40 * DebugLevel N
b94498cf 41 * DeviceURI "regex pattern" uri
89d46774 42 * HostNameLookups on
43 * HostNameLookups off
b94498cf 44 * MaxRunTime N
89d46774 45 *
46 * The default is to use:
47 *
48 * Address @LOCAL
49 * Community public
50 * DebugLevel 0
51 * HostNameLookups off
ae71f5de 52 * MaxRunTime 120
89d46774 53 *
54 * This backend is known to work with the following network printers and
55 * print servers:
56 *
57 * Axis OfficeBasic, 5400, 5600
7a14d768 58 * Brother
89d46774 59 * EPSON
60 * Genicom
61 * HP JetDirect
62 * Lexmark
63 * Sharp
64 * Tektronix
65 * Xerox
66 *
67 * It does not currently work with:
68 *
69 * DLink
70 * Linksys
71 * Netgear
72 * Okidata
73 *
74 * (for all of these, they do not support the Host MIB)
75 */
76
89d46774 77/*
78 * Types...
79 */
80
749b1e90
MS
81enum /**** Request IDs for each field ****/
82{
83 DEVICE_TYPE = 1,
84 DEVICE_DESCRIPTION,
85 DEVICE_LOCATION,
86 DEVICE_ID,
87 DEVICE_URI,
88 DEVICE_PRODUCT
89};
90
b94498cf 91typedef struct device_uri_s /**** DeviceURI values ****/
92{
93 regex_t re; /* Regular expression to match */
94 cups_array_t *uris; /* URIs */
95} device_uri_t;
96
89d46774 97typedef struct snmp_cache_s /**** SNMP scan cache ****/
98{
99 http_addr_t address; /* Address of device */
100 char *addrname, /* Name of device */
101 *uri, /* device-uri */
102 *id, /* device-id */
7a14d768 103 *info, /* device-info */
749b1e90 104 *location, /* device-location */
89d46774 105 *make_and_model; /* device-make-and-model */
749b1e90 106 int sent; /* Has this device been listed? */
89d46774 107} snmp_cache_t;
108
89d46774 109
110/*
111 * Local functions...
112 */
113
114static char *add_array(cups_array_t *a, const char *s);
115static void add_cache(http_addr_t *addr, const char *addrname,
116 const char *uri, const char *id,
117 const char *make_and_model);
b94498cf 118static device_uri_t *add_device_uri(char *value);
89d46774 119static void alarm_handler(int sig);
89d46774 120static int compare_cache(snmp_cache_t *a, snmp_cache_t *b);
121static void debug_printf(const char *format, ...);
122static void fix_make_model(char *make_model,
123 const char *old_make_model,
124 int make_model_size);
125static void free_array(cups_array_t *a);
126static void free_cache(void);
127static http_addrlist_t *get_interface_addresses(const char *ifname);
d09495fa 128static void list_device(snmp_cache_t *cache);
89d46774 129static const char *password_cb(const char *prompt);
130static void probe_device(snmp_cache_t *device);
131static void read_snmp_conf(const char *address);
132static void read_snmp_response(int fd);
133static double run_time(void);
22c9029b 134static void scan_devices(int ipv4, int ipv6);
89d46774 135static int try_connect(http_addr_t *addr, const char *addrname,
136 int port);
137static void update_cache(snmp_cache_t *device, const char *uri,
138 const char *id, const char *make_model);
139
140
141/*
142 * Local globals...
143 */
144
145static cups_array_t *Addresses = NULL;
146static cups_array_t *Communities = NULL;
147static cups_array_t *Devices = NULL;
148static int DebugLevel = 0;
749b1e90
MS
149static const int DescriptionOID[] = { CUPS_OID_hrDeviceDescr, 1, -1 };
150static const int LocationOID[] = { CUPS_OID_sysLocation, 0, -1 };
7a14d768 151static const int DeviceTypeOID[] = { CUPS_OID_hrDeviceType, 1, -1 };
7a14d768 152static const int DeviceIdOID[] = { CUPS_OID_ppmPrinterIEEE1284DeviceId, 1, -1 };
749b1e90
MS
153static const int UriOID[] = { CUPS_OID_ppmPortServiceNameOrURI, 1, 1, -1 };
154static const int LexmarkProductOID[] = { 1,3,6,1,4,1,641,2,1,2,1,2,1,-1 };
155static const int LexmarkProductOID2[] = { 1,3,6,1,4,1,674,10898,100,2,1,2,1,2,1,-1 };
156static const int LexmarkDeviceIdOID[] = { 1,3,6,1,4,1,641,2,1,2,1,3,1,-1 };
157static const int XeroxProductOID[] = { 1,3,6,1,4,1,128,2,1,3,1,2,0,-1 };
b94498cf 158static cups_array_t *DeviceURIs = NULL;
89d46774 159static int HostNameLookups = 0;
ae71f5de 160static int MaxRunTime = 120;
89d46774 161static struct timeval StartTime;
162
163
164/*
165 * 'main()' - Discover printers via SNMP.
166 */
167
168int /* O - Exit status */
169main(int argc, /* I - Number of command-line arguments (6 or 7) */
170 char *argv[]) /* I - Command-line arguments */
171{
22c9029b
MS
172 int ipv4, /* SNMP IPv4 socket */
173 ipv6; /* SNMP IPv6 socket */
d09495fa 174#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
175 struct sigaction action; /* Actions for POSIX signals */
176#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
89d46774 177
178
179 /*
180 * Check command-line options...
181 */
182
183 if (argc > 2)
184 {
0837b7e8 185 _cupsLangPuts(stderr, _("Usage: snmp [host-or-ip-address]"));
89d46774 186 return (1);
187 }
188
189 /*
190 * Set the password callback for IPP operations...
191 */
192
193 cupsSetPasswordCB(password_cb);
194
d09495fa 195 /*
196 * Catch SIGALRM signals...
197 */
198
199#ifdef HAVE_SIGSET
200 sigset(SIGALRM, alarm_handler);
201#elif defined(HAVE_SIGACTION)
202 memset(&action, 0, sizeof(action));
203
204 sigemptyset(&action.sa_mask);
205 sigaddset(&action.sa_mask, SIGALRM);
206 action.sa_handler = alarm_handler;
207 sigaction(SIGALRM, &action, NULL);
208#else
209 signal(SIGALRM, alarm_handler);
210#endif /* HAVE_SIGSET */
211
89d46774 212 /*
213 * Open the SNMP socket...
214 */
215
22c9029b 216 if ((ipv4 = _cupsSNMPOpen(AF_INET)) < 0)
89d46774 217 return (1);
218
22c9029b
MS
219#ifdef AF_INET6
220 if ((ipv6 = _cupsSNMPOpen(AF_INET6)) < 0)
37e7e6e0 221 perror("DEBUG: Unable to create IPv6 socket");
22c9029b
MS
222#else
223 ipv6 = -1;
224#endif /* AF_INET6 */
225
89d46774 226 /*
227 * Read the configuration file and any cache data...
228 */
229
230 read_snmp_conf(argv[1]);
231
7a14d768 232 _cupsSNMPSetDebug(DebugLevel);
91c84a35 233
89d46774 234 Devices = cupsArrayNew((cups_array_func_t)compare_cache, NULL);
235
236 /*
237 * Scan for devices...
238 */
239
22c9029b 240 scan_devices(ipv4, ipv6);
89d46774 241
89d46774 242 /*
243 * Close, free, and return with no errors...
244 */
245
22c9029b
MS
246 _cupsSNMPClose(ipv4);
247 if (ipv6 >= 0)
248 _cupsSNMPClose(ipv6);
89d46774 249
250 free_array(Addresses);
251 free_array(Communities);
252 free_cache();
253
254 return (0);
255}
256
257
258/*
259 * 'add_array()' - Add a string to an array.
260 */
261
262static char * /* O - New string */
263add_array(cups_array_t *a, /* I - Array */
264 const char *s) /* I - String to add */
265{
266 char *dups; /* New string */
267
268
269 dups = strdup(s);
270
271 cupsArrayAdd(a, dups);
272
273 return (dups);
274}
275
276
277/*
278 * 'add_cache()' - Add a cached device...
279 */
280
281static void
282add_cache(http_addr_t *addr, /* I - Device IP address */
283 const char *addrname, /* I - IP address or name string */
284 const char *uri, /* I - Device URI */
285 const char *id, /* I - 1284 device ID */
286 const char *make_and_model) /* I - Make and model */
287{
288 snmp_cache_t *temp; /* New device entry */
289
290
291 debug_printf("DEBUG: add_cache(addr=%p, addrname=\"%s\", uri=\"%s\", "
292 "id=\"%s\", make_and_model=\"%s\")\n",
d09495fa 293 addr, addrname, uri ? uri : "(null)", id ? id : "(null)",
89d46774 294 make_and_model ? make_and_model : "(null)");
295
296 temp = calloc(1, sizeof(snmp_cache_t));
297 memcpy(&(temp->address), addr, sizeof(temp->address));
298
299 temp->addrname = strdup(addrname);
300
301 if (uri)
302 temp->uri = strdup(uri);
303
304 if (id)
305 temp->id = strdup(id);
306
307 if (make_and_model)
308 temp->make_and_model = strdup(make_and_model);
309
310 cupsArrayAdd(Devices, temp);
d09495fa 311
312 if (uri)
313 list_device(temp);
89d46774 314}
315
316
b94498cf 317/*
318 * 'add_device_uri()' - Add a device URI to the cache.
319 *
320 * The value string is modified (chopped up) as needed.
321 */
322
323static device_uri_t * /* O - Device URI */
324add_device_uri(char *value) /* I - Value from snmp.conf */
325{
326 device_uri_t *device_uri; /* Device URI */
327 char *start; /* Start of value */
328
329
330 /*
331 * Allocate memory as needed...
332 */
333
334 if (!DeviceURIs)
335 DeviceURIs = cupsArrayNew(NULL, NULL);
336
337 if (!DeviceURIs)
338 return (NULL);
339
340 if ((device_uri = calloc(1, sizeof(device_uri_t))) == NULL)
341 return (NULL);
342
343 if ((device_uri->uris = cupsArrayNew(NULL, NULL)) == NULL)
344 {
345 free(device_uri);
346 return (NULL);
347 }
348
349 /*
350 * Scan the value string for the regular expression and URI(s)...
351 */
352
353 value ++; /* Skip leading " */
354
355 for (start = value; *value && *value != '\"'; value ++)
356 if (*value == '\\' && value[1])
357 _cups_strcpy(value, value + 1);
358
359 if (!*value)
360 {
361 fputs("ERROR: Missing end quote for DeviceURI!\n", stderr);
362
363 cupsArrayDelete(device_uri->uris);
364 free(device_uri);
365
366 return (NULL);
367 }
368
369 *value++ = '\0';
370
371 if (regcomp(&(device_uri->re), start, REG_EXTENDED | REG_ICASE))
372 {
373 fputs("ERROR: Bad regular expression for DeviceURI!\n", stderr);
374
375 cupsArrayDelete(device_uri->uris);
376 free(device_uri);
377
378 return (NULL);
379 }
380
381 while (*value)
382 {
383 while (isspace(*value & 255))
384 value ++;
385
386 if (!*value)
387 break;
388
389 for (start = value; *value && !isspace(*value & 255); value ++);
390
391 if (*value)
392 *value++ = '\0';
393
394 cupsArrayAdd(device_uri->uris, strdup(start));
395 }
396
397 /*
398 * Add the device URI to the list and return it...
399 */
400
401 cupsArrayAdd(DeviceURIs, device_uri);
402
403 return (device_uri);
404}
405
406
89d46774 407/*
408 * 'alarm_handler()' - Handle alarm signals...
409 */
410
411static void
412alarm_handler(int sig) /* I - Signal number */
413{
414 /*
415 * Do nothing...
416 */
417
418 (void)sig;
419
d09495fa 420#if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION)
421 signal(SIGALRM, alarm_handler);
422#endif /* !HAVE_SIGSET && !HAVE_SIGACTION */
423
89d46774 424 if (DebugLevel)
425 write(2, "DEBUG: ALARM!\n", 14);
426}
427
428
89d46774 429/*
430 * 'compare_cache()' - Compare two cache entries.
431 */
432
433static int /* O - Result of comparison */
434compare_cache(snmp_cache_t *a, /* I - First cache entry */
435 snmp_cache_t *b) /* I - Second cache entry */
436{
88f9aafc 437 return (_cups_strcasecmp(a->addrname, b->addrname));
89d46774 438}
439
440
441/*
442 * 'debug_printf()' - Display some debugging information.
443 */
444
445static void
446debug_printf(const char *format, /* I - Printf-style format string */
447 ...) /* I - Additional arguments as needed */
448{
449 va_list ap; /* Pointer to arguments */
450
451
452 if (!DebugLevel)
453 return;
454
455 va_start(ap, format);
456 vfprintf(stderr, format, ap);
457 va_end(ap);
458}
459
460
461/*
462 * 'fix_make_model()' - Fix common problems in the make-and-model string.
463 */
464
465static void
466fix_make_model(
467 char *make_model, /* I - New make-and-model string */
468 const char *old_make_model, /* I - Old make-and-model string */
469 int make_model_size) /* I - Size of new string buffer */
470{
a74454a7 471 char *mmptr; /* Pointer into make-and-model string */
89d46774 472
473
474 /*
475 * Fix some common problems with the make-and-model string so
476 * that printer driver detection works better...
477 */
478
88f9aafc 479 if (!_cups_strncasecmp(old_make_model, "Hewlett-Packard", 15))
89d46774 480 {
481 /*
482 * Strip leading Hewlett-Packard and hp prefixes and replace
483 * with a single HP manufacturer prefix...
484 */
485
a74454a7 486 mmptr = (char *)old_make_model + 15;
89d46774 487
488 while (isspace(*mmptr & 255))
489 mmptr ++;
490
88f9aafc 491 if (!_cups_strncasecmp(mmptr, "hp", 2))
89d46774 492 {
493 mmptr += 2;
494
495 while (isspace(*mmptr & 255))
496 mmptr ++;
497 }
498
499 make_model[0] = 'H';
500 make_model[1] = 'P';
501 make_model[2] = ' ';
07623986 502 strlcpy(make_model + 3, mmptr, (size_t)make_model_size - 3);
89d46774 503 }
88f9aafc 504 else if (!_cups_strncasecmp(old_make_model, "deskjet", 7))
07623986 505 snprintf(make_model, (size_t)make_model_size, "HP DeskJet%s", old_make_model + 7);
88f9aafc 506 else if (!_cups_strncasecmp(old_make_model, "officejet", 9))
07623986 507 snprintf(make_model, (size_t)make_model_size, "HP OfficeJet%s", old_make_model + 9);
88f9aafc 508 else if (!_cups_strncasecmp(old_make_model, "stylus_pro_", 11))
07623986 509 snprintf(make_model, (size_t)make_model_size, "EPSON Stylus Pro %s", old_make_model + 11);
89d46774 510 else
07623986 511 strlcpy(make_model, old_make_model, (size_t)make_model_size);
89d46774 512
513 if ((mmptr = strstr(make_model, ", Inc.,")) != NULL)
514 {
515 /*
516 * Strip inc. from name, e.g. "Tektronix, Inc., Phaser 560"
517 * becomes "Tektronix Phaser 560"...
518 */
519
a74454a7 520 _cups_strcpy(mmptr, mmptr + 7);
521 }
522
d09495fa 523 if ((mmptr = strstr(make_model, " Network")) != NULL)
524 {
525 /*
526 * Drop unnecessary informational text, e.g. "Xerox DocuPrint N2025
527 * Network LaserJet - 2.12" becomes "Xerox DocuPrint N2025"...
528 */
529
530 *mmptr = '\0';
531 }
532
a74454a7 533 if ((mmptr = strchr(make_model, ',')) != NULL)
534 {
535 /*
536 * Drop anything after a trailing comma...
537 */
538
539 *mmptr = '\0';
89d46774 540 }
541}
542
543
544/*
545 * 'free_array()' - Free an array of strings.
546 */
547
548static void
549free_array(cups_array_t *a) /* I - Array */
550{
551 char *s; /* Current string */
552
553
554 for (s = (char *)cupsArrayFirst(a); s; s = (char *)cupsArrayNext(a))
555 free(s);
556
557 cupsArrayDelete(a);
558}
559
560
561/*
562 * 'free_cache()' - Free the array of cached devices.
563 */
564
565static void
566free_cache(void)
567{
568 snmp_cache_t *cache; /* Cached device */
569
570
571 for (cache = (snmp_cache_t *)cupsArrayFirst(Devices);
572 cache;
573 cache = (snmp_cache_t *)cupsArrayNext(Devices))
574 {
575 free(cache->addrname);
576
577 if (cache->uri)
578 free(cache->uri);
579
580 if (cache->id)
581 free(cache->id);
582
583 if (cache->make_and_model)
584 free(cache->make_and_model);
585
586 free(cache);
587 }
588
589 cupsArrayDelete(Devices);
590 Devices = NULL;
591}
592
593
594/*
595 * 'get_interface_addresses()' - Get the broadcast address(es) associated
596 * with an interface.
597 */
598
599static http_addrlist_t * /* O - List of addresses */
600get_interface_addresses(
601 const char *ifname) /* I - Interface name */
602{
603 struct ifaddrs *addrs, /* Interface address list */
604 *addr; /* Current interface address */
605 http_addrlist_t *first, /* First address in list */
606 *last, /* Last address in list */
607 *current; /* Current address */
608
609
610 if (getifaddrs(&addrs) < 0)
611 return (NULL);
612
613 for (addr = addrs, first = NULL, last = NULL; addr; addr = addr->ifa_next)
614 if ((addr->ifa_flags & IFF_BROADCAST) && addr->ifa_broadaddr &&
615 addr->ifa_broadaddr->sa_family == AF_INET &&
616 (!ifname || !strcmp(ifname, addr->ifa_name)))
617 {
618 current = calloc(1, sizeof(http_addrlist_t));
619
620 memcpy(&(current->addr), addr->ifa_broadaddr,
621 sizeof(struct sockaddr_in));
622
623 if (!last)
624 first = current;
625 else
626 last->next = current;
627
628 last = current;
629 }
630
631 freeifaddrs(addrs);
632
633 return (first);
634}
635
636
89d46774 637/*
d09495fa 638 * 'list_device()' - List a device we found...
89d46774 639 */
640
641static void
d09495fa 642list_device(snmp_cache_t *cache) /* I - Cached device */
89d46774 643{
d09495fa 644 if (cache->uri)
749b1e90
MS
645 cupsBackendReport("network", cache->uri, cache->make_and_model,
646 cache->info, cache->id, cache->location);
89d46774 647}
648
649
89d46774 650/*
651 * 'password_cb()' - Handle authentication requests.
652 *
653 * All we do right now is return NULL, indicating that no authentication
654 * is possible.
655 */
656
657static const char * /* O - Password (NULL) */
658password_cb(const char *prompt) /* I - Prompt message */
659{
660 (void)prompt; /* Anti-compiler-warning-code */
661
662 return (NULL);
663}
664
665
666/*
667 * 'probe_device()' - Probe a device to discover whether it is a printer.
668 *
669 * TODO: Try using the Port Monitor MIB to discover the correct protocol
670 * to use - first need a commercially-available printer that supports
671 * it, though...
672 */
673
674static void
675probe_device(snmp_cache_t *device) /* I - Device */
676{
b94498cf 677 char uri[1024], /* Full device URI */
678 *uriptr, /* Pointer into URI */
679 *format; /* Format string for device */
680 device_uri_t *device_uri; /* Current DeviceURI match */
89d46774 681
682
89d46774 683 debug_printf("DEBUG: %.3f Probing %s...\n", run_time(), device->addrname);
684
b94498cf 685#ifdef __APPLE__
686 /*
22c9029b 687 * If the printer supports Bonjour/mDNS, don't report it from the SNMP backend.
b94498cf 688 */
d09495fa 689
b94498cf 690 if (!try_connect(&(device->address), device->addrname, 5353))
d09495fa 691 {
b94498cf 692 debug_printf("DEBUG: %s supports mDNS, not reporting!\n", device->addrname);
693 return;
d09495fa 694 }
b94498cf 695#endif /* __APPLE__ */
d09495fa 696
b94498cf 697 /*
698 * Lookup the device in the match table...
699 */
89d46774 700
b94498cf 701 for (device_uri = (device_uri_t *)cupsArrayFirst(DeviceURIs);
702 device_uri;
703 device_uri = (device_uri_t *)cupsArrayNext(DeviceURIs))
ac884b6a
MS
704 if (device->make_and_model &&
705 !regexec(&(device_uri->re), device->make_and_model, 0, NULL, 0))
89d46774 706 {
d09495fa 707 /*
b94498cf 708 * Found a match, add the URIs...
89d46774 709 */
710
b94498cf 711 for (format = (char *)cupsArrayFirst(device_uri->uris);
712 format;
713 format = (char *)cupsArrayNext(device_uri->uris))
89d46774 714 {
b94498cf 715 for (uriptr = uri; *format && uriptr < (uri + sizeof(uri) - 1);)
716 if (*format == '%' && format[1] == 's')
89d46774 717 {
b94498cf 718 /*
719 * Insert hostname/address...
720 */
89d46774 721
7e86f2f6 722 strlcpy(uriptr, device->addrname, sizeof(uri) - (size_t)(uriptr - uri));
b94498cf 723 uriptr += strlen(uriptr);
724 format += 2;
725 }
726 else
727 *uriptr++ = *format++;
89d46774 728
b94498cf 729 *uriptr = '\0';
89d46774 730
b94498cf 731 update_cache(device, uri, NULL, NULL);
89d46774 732 }
733
89d46774 734 return;
b94498cf 735 }
89d46774 736
737 /*
b94498cf 738 * Then try the standard ports...
89d46774 739 */
740
741 if (!try_connect(&(device->address), device->addrname, 9100))
742 {
743 debug_printf("DEBUG: %s supports AppSocket!\n", device->addrname);
744
745 snprintf(uri, sizeof(uri), "socket://%s", device->addrname);
746 update_cache(device, uri, NULL, NULL);
747 }
748 else if (!try_connect(&(device->address), device->addrname, 515))
749 {
750 debug_printf("DEBUG: %s supports LPD!\n", device->addrname);
751
752 snprintf(uri, sizeof(uri), "lpd://%s/", device->addrname);
753 update_cache(device, uri, NULL, NULL);
754 }
755}
756
757
758/*
759 * 'read_snmp_conf()' - Read the snmp.conf file.
760 */
761
762static void
763read_snmp_conf(const char *address) /* I - Single address to probe */
764{
765 cups_file_t *fp; /* File pointer */
766 char filename[1024], /* Filename */
767 line[1024], /* Line from file */
768 *value; /* Value on line */
769 int linenum; /* Line number */
770 const char *cups_serverroot; /* CUPS_SERVERROOT env var */
771 const char *debug; /* CUPS_DEBUG_LEVEL env var */
d09495fa 772 const char *runtime; /* CUPS_MAX_RUN_TIME env var */
89d46774 773
774
775 /*
776 * Initialize the global address and community lists...
777 */
778
779 Addresses = cupsArrayNew(NULL, NULL);
780 Communities = cupsArrayNew(NULL, NULL);
781
782 if (address)
783 add_array(Addresses, address);
784
785 if ((debug = getenv("CUPS_DEBUG_LEVEL")) != NULL)
786 DebugLevel = atoi(debug);
787
d09495fa 788 if ((runtime = getenv("CUPS_MAX_RUN_TIME")) != NULL)
789 MaxRunTime = atoi(runtime);
790
89d46774 791 /*
792 * Find the snmp.conf file...
793 */
794
795 if ((cups_serverroot = getenv("CUPS_SERVERROOT")) == NULL)
796 cups_serverroot = CUPS_SERVERROOT;
797
798 snprintf(filename, sizeof(filename), "%s/snmp.conf", cups_serverroot);
799
800 if ((fp = cupsFileOpen(filename, "r")) != NULL)
801 {
802 /*
803 * Read the snmp.conf file...
804 */
805
806 linenum = 0;
807
808 while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
809 {
810 if (!value)
811 fprintf(stderr, "ERROR: Missing value on line %d of %s!\n", linenum,
812 filename);
88f9aafc 813 else if (!_cups_strcasecmp(line, "Address"))
89d46774 814 {
815 if (!address)
816 add_array(Addresses, value);
817 }
88f9aafc 818 else if (!_cups_strcasecmp(line, "Community"))
89d46774 819 add_array(Communities, value);
88f9aafc 820 else if (!_cups_strcasecmp(line, "DebugLevel"))
89d46774 821 DebugLevel = atoi(value);
88f9aafc 822 else if (!_cups_strcasecmp(line, "DeviceURI"))
b94498cf 823 {
824 if (*value != '\"')
825 fprintf(stderr,
826 "ERROR: Missing double quote for regular expression on "
827 "line %d of %s!\n", linenum, filename);
828 else
829 add_device_uri(value);
830 }
88f9aafc
MS
831 else if (!_cups_strcasecmp(line, "HostNameLookups"))
832 HostNameLookups = !_cups_strcasecmp(value, "on") ||
833 !_cups_strcasecmp(value, "yes") ||
834 !_cups_strcasecmp(value, "true") ||
835 !_cups_strcasecmp(value, "double");
836 else if (!_cups_strcasecmp(line, "MaxRunTime"))
d09495fa 837 MaxRunTime = atoi(value);
89d46774 838 else
839 fprintf(stderr, "ERROR: Unknown directive %s on line %d of %s!\n",
840 line, linenum, filename);
841 }
842
843 cupsFileClose(fp);
844 }
845
846 /*
847 * Use defaults if parameters are undefined...
848 */
849
850 if (cupsArrayCount(Addresses) == 0)
851 {
bc44d920 852 /*
853 * If we have no addresses, exit immediately...
854 */
855
856 fprintf(stderr,
857 "DEBUG: No address specified and no Address line in %s...\n",
858 filename);
859 exit(0);
89d46774 860 }
861
862 if (cupsArrayCount(Communities) == 0)
863 {
864 fputs("INFO: Using default SNMP Community public\n", stderr);
865 add_array(Communities, "public");
866 }
867}
868
869
870/*
871 * 'read_snmp_response()' - Read and parse a SNMP response...
872 */
873
874static void
875read_snmp_response(int fd) /* I - SNMP socket file descriptor */
876{
89d46774 877 char addrname[256]; /* Source address name */
91c84a35 878 cups_snmp_t packet; /* Decoded packet */
89d46774 879 snmp_cache_t key, /* Search key */
880 *device; /* Matching device */
881
882
883 /*
884 * Read the response data...
885 */
886
7a14d768 887 if (!_cupsSNMPRead(fd, &packet, -1.0))
89d46774 888 {
889 fprintf(stderr, "ERROR: Unable to read data from socket: %s\n",
890 strerror(errno));
891 return;
892 }
893
894 if (HostNameLookups)
91c84a35 895 httpAddrLookup(&(packet.address), addrname, sizeof(addrname));
89d46774 896 else
91c84a35 897 httpAddrString(&(packet.address), addrname, sizeof(addrname));
89d46774 898
91c84a35 899 debug_printf("DEBUG: %.3f Received data from %s...\n", run_time(), addrname);
89d46774 900
901 /*
902 * Look for the response status code in the SNMP message header...
903 */
904
91c84a35 905 if (packet.error)
89d46774 906 {
91c84a35
MS
907 fprintf(stderr, "ERROR: Bad SNMP packet from %s: %s\n", addrname,
908 packet.error);
89d46774 909
910 return;
911 }
912
913 debug_printf("DEBUG: community=\"%s\"\n", packet.community);
914 debug_printf("DEBUG: request-id=%d\n", packet.request_id);
915 debug_printf("DEBUG: error-status=%d\n", packet.error_status);
916
771bd8cb 917 if (packet.error_status && packet.request_id != DEVICE_TYPE)
89d46774 918 return;
919
920 /*
921 * Find a matching device in the cache...
922 */
923
8ca02f3c 924 key.addrname = addrname;
925 device = (snmp_cache_t *)cupsArrayFind(Devices, &key);
89d46774 926
927 /*
928 * Process the message...
929 */
930
749b1e90 931 switch (packet.request_id)
89d46774 932 {
749b1e90
MS
933 case DEVICE_TYPE :
934 /*
935 * Got the device type response...
936 */
937
938 if (device)
939 {
940 debug_printf("DEBUG: Discarding duplicate device type for \"%s\"...\n",
941 addrname);
942 return;
943 }
944
945 /*
946 * Add the device and request the device data...
947 */
948
949 add_cache(&(packet.address), addrname, NULL, NULL, NULL);
950
951 _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
952 packet.community, CUPS_ASN1_GET_REQUEST,
953 DEVICE_DESCRIPTION, DescriptionOID);
954 _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
955 packet.community, CUPS_ASN1_GET_REQUEST,
956 DEVICE_ID, DeviceIdOID);
957 _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
958 packet.community, CUPS_ASN1_GET_REQUEST,
959 DEVICE_URI, UriOID);
960 _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
961 packet.community, CUPS_ASN1_GET_REQUEST,
962 DEVICE_LOCATION, LocationOID);
963 _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
964 packet.community, CUPS_ASN1_GET_REQUEST,
965 DEVICE_PRODUCT, LexmarkProductOID);
966 _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
967 packet.community, CUPS_ASN1_GET_REQUEST,
968 DEVICE_PRODUCT, LexmarkProductOID2);
969 _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
970 packet.community, CUPS_ASN1_GET_REQUEST,
ef55b745 971 DEVICE_ID, LexmarkDeviceIdOID);
749b1e90
MS
972 _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
973 packet.community, CUPS_ASN1_GET_REQUEST,
974 DEVICE_PRODUCT, XeroxProductOID);
975 break;
976
977 case DEVICE_DESCRIPTION :
978 if (device && packet.object_type == CUPS_ASN1_OCTET_STRING)
979 {
980 /*
981 * Update an existing cache entry...
982 */
983
984 char make_model[256]; /* Make and model */
985
986
d1c13e16
MS
987 if (strchr((char *)packet.object_value.string.bytes, ':') &&
988 strchr((char *)packet.object_value.string.bytes, ';'))
749b1e90
MS
989 {
990 /*
991 * Description is the IEEE-1284 device ID...
992 */
89d46774 993
3dd9c340
MS
994 char *ptr; /* Pointer into device ID */
995
996 for (ptr = (char *)packet.object_value.string.bytes; *ptr; ptr ++)
997 if (*ptr == '\n')
998 *ptr = ';'; /* A lot of bad printers put a newline */
749b1e90 999 if (!device->id)
d1c13e16 1000 device->id = strdup((char *)packet.object_value.string.bytes);
89d46774 1001
d1c13e16
MS
1002 backendGetMakeModel((char *)packet.object_value.string.bytes,
1003 make_model, sizeof(make_model));
89d46774 1004
749b1e90
MS
1005 if (device->info)
1006 free(device->info);
89d46774 1007
749b1e90
MS
1008 device->info = strdup(make_model);
1009 }
1010 else
1011 {
1012 /*
1013 * Description is plain text...
1014 */
89d46774 1015
d1c13e16 1016 fix_make_model(make_model, (char *)packet.object_value.string.bytes,
749b1e90 1017 sizeof(make_model));
89d46774 1018
749b1e90
MS
1019 if (device->info)
1020 free(device->info);
89d46774 1021
d1c13e16 1022 device->info = strdup((char *)packet.object_value.string.bytes);
749b1e90 1023 }
89d46774 1024
749b1e90
MS
1025 if (!device->make_and_model)
1026 device->make_and_model = strdup(make_model);
1027 }
1028 break;
89d46774 1029
749b1e90 1030 case DEVICE_ID :
ef55b745
MS
1031 if (device && packet.object_type == CUPS_ASN1_OCTET_STRING &&
1032 (!device->id ||
1033 strlen(device->id) < packet.object_value.string.num_bytes))
749b1e90
MS
1034 {
1035 /*
1036 * Update an existing cache entry...
1037 */
7a14d768 1038
749b1e90 1039 char make_model[256]; /* Make and model */
3dd9c340 1040 char *ptr; /* Pointer into device ID */
89d46774 1041
3dd9c340
MS
1042 for (ptr = (char *)packet.object_value.string.bytes; *ptr; ptr ++)
1043 if (*ptr == '\n')
1044 *ptr = ';'; /* A lot of bad printers put a newline */
749b1e90
MS
1045 if (device->id)
1046 free(device->id);
89d46774 1047
d1c13e16 1048 device->id = strdup((char *)packet.object_value.string.bytes);
7a14d768 1049
749b1e90
MS
1050 /*
1051 * Convert the ID to a make and model string...
1052 */
7a14d768 1053
d1c13e16
MS
1054 backendGetMakeModel((char *)packet.object_value.string.bytes,
1055 make_model, sizeof(make_model));
749b1e90
MS
1056 if (device->make_and_model)
1057 free(device->make_and_model);
7a14d768 1058
749b1e90
MS
1059 device->make_and_model = strdup(make_model);
1060 }
1061 break;
7a14d768 1062
749b1e90
MS
1063 case DEVICE_LOCATION :
1064 if (device && packet.object_type == CUPS_ASN1_OCTET_STRING &&
1065 !device->location)
d1c13e16 1066 device->location = strdup((char *)packet.object_value.string.bytes);
749b1e90 1067 break;
7a14d768 1068
749b1e90
MS
1069 case DEVICE_PRODUCT :
1070 if (device && packet.object_type == CUPS_ASN1_OCTET_STRING &&
1071 !device->id)
1072 {
1073 /*
1074 * Update an existing cache entry...
1075 */
7a14d768 1076
749b1e90 1077 if (!device->info)
d1c13e16 1078 device->info = strdup((char *)packet.object_value.string.bytes);
7a14d768 1079
749b1e90
MS
1080 if (device->make_and_model)
1081 free(device->make_and_model);
7a14d768 1082
d1c13e16 1083 device->make_and_model = strdup((char *)packet.object_value.string.bytes);
749b1e90
MS
1084 }
1085 break;
7a14d768 1086
749b1e90
MS
1087 case DEVICE_URI :
1088 if (device && packet.object_type == CUPS_ASN1_OCTET_STRING &&
12f89d24 1089 !device->uri && packet.object_value.string.num_bytes > 3)
749b1e90
MS
1090 {
1091 /*
1092 * Update an existing cache entry...
1093 */
7a14d768 1094
12f89d24
MS
1095 char scheme[32], /* URI scheme */
1096 userpass[256], /* Username:password in URI */
1097 hostname[256], /* Hostname in URI */
1098 resource[1024]; /* Resource path in URI */
1099 int port; /* Port number in URI */
1100
d1c13e16 1101 if (!strncmp((char *)packet.object_value.string.bytes, "lpr:", 4))
749b1e90
MS
1102 {
1103 /*
1104 * We want "lpd://..." for the URI...
1105 */
7a14d768 1106
d1c13e16 1107 packet.object_value.string.bytes[2] = 'd';
749b1e90 1108 }
7a14d768 1109
12f89d24
MS
1110 if (httpSeparateURI(HTTP_URI_CODING_ALL,
1111 (char *)packet.object_value.string.bytes,
1112 scheme, sizeof(scheme),
1113 userpass, sizeof(userpass),
1114 hostname, sizeof(hostname), &port,
1115 resource, sizeof(resource)) >= HTTP_URI_OK)
1116 device->uri = strdup((char *)packet.object_value.string.bytes);
749b1e90
MS
1117 }
1118 break;
89d46774 1119 }
1120}
1121
1122
1123/*
1124 * 'run_time()' - Return the total running time...
1125 */
1126
1127static double /* O - Number of seconds */
1128run_time(void)
1129{
1130 struct timeval curtime; /* Current time */
1131
1132
1133 gettimeofday(&curtime, NULL);
1134
1135 return (curtime.tv_sec - StartTime.tv_sec +
1136 0.000001 * (curtime.tv_usec - StartTime.tv_usec));
1137}
1138
1139
1140/*
1141 * 'scan_devices()' - Scan for devices using SNMP.
1142 */
1143
1144static void
22c9029b
MS
1145scan_devices(int ipv4, /* I - SNMP IPv4 socket */
1146 int ipv6) /* I - SNMP IPv6 socket */
89d46774 1147{
22c9029b
MS
1148 int fd, /* File descriptor for this address */
1149 busy; /* Are we busy processing something? */
89d46774 1150 char *address, /* Current address */
1151 *community; /* Current community */
1152 fd_set input; /* Input set for select() */
1153 struct timeval timeout; /* Timeout for select() */
1154 time_t endtime; /* End time for scan */
1155 http_addrlist_t *addrs, /* List of addresses */
1156 *addr; /* Current address */
1157 snmp_cache_t *device; /* Current device */
22c9029b 1158 char temp[1024]; /* Temporary address string */
89d46774 1159
1160
89d46774 1161 gettimeofday(&StartTime, NULL);
1162
89d46774 1163 /*
1164 * First send all of the broadcast queries...
1165 */
1166
1167 for (address = (char *)cupsArrayFirst(Addresses);
1168 address;
1169 address = (char *)cupsArrayNext(Addresses))
1170 {
1171 if (!strcmp(address, "@LOCAL"))
1172 addrs = get_interface_addresses(NULL);
1173 else if (!strncmp(address, "@IF(", 4))
1174 {
1175 char ifname[255]; /* Interface name */
1176
89d46774 1177 strlcpy(ifname, address + 4, sizeof(ifname));
1178 if (ifname[0])
1179 ifname[strlen(ifname) - 1] = '\0';
1180
1181 addrs = get_interface_addresses(ifname);
1182 }
1183 else
22c9029b 1184 addrs = httpAddrGetList(address, AF_UNSPEC, NULL);
89d46774 1185
1186 if (!addrs)
1187 {
1188 fprintf(stderr, "ERROR: Unable to scan \"%s\"!\n", address);
1189 continue;
1190 }
1191
1192 for (community = (char *)cupsArrayFirst(Communities);
1193 community;
1194 community = (char *)cupsArrayNext(Communities))
1195 {
1196 debug_printf("DEBUG: Scanning for devices in \"%s\" via \"%s\"...\n",
1197 community, address);
1198
1199 for (addr = addrs; addr; addr = addr->next)
22c9029b
MS
1200 {
1201#ifdef AF_INET6
5ec1fd3d 1202 if (httpAddrFamily(&(addr->addr)) == AF_INET6)
22c9029b
MS
1203 fd = ipv6;
1204 else
1205#endif /* AF_INET6 */
1206 fd = ipv4;
1207
1208 debug_printf("DEBUG: Sending get request to %s...\n",
1209 httpAddrString(&(addr->addr), temp, sizeof(temp)));
1210
7a14d768 1211 _cupsSNMPWrite(fd, &(addr->addr), CUPS_SNMP_VERSION_1, community,
749b1e90 1212 CUPS_ASN1_GET_REQUEST, DEVICE_TYPE, DeviceTypeOID);
22c9029b 1213 }
89d46774 1214 }
1215
1216 httpAddrFreeList(addrs);
1217 }
1218
1219 /*
1220 * Then read any responses that come in over the next 3 seconds...
1221 */
1222
ae71f5de 1223 endtime = time(NULL) + MaxRunTime;
89d46774 1224
1225 FD_ZERO(&input);
1226
1227 while (time(NULL) < endtime)
1228 {
ae71f5de 1229 timeout.tv_sec = 2;
89d46774 1230 timeout.tv_usec = 0;
1231
22c9029b
MS
1232 FD_SET(ipv4, &input);
1233 if (ipv6 >= 0)
1234 FD_SET(ipv6, &input);
1235
1236 fd = ipv4 > ipv6 ? ipv4 : ipv6;
89d46774 1237 if (select(fd + 1, &input, NULL, NULL, &timeout) < 0)
1238 {
22c9029b
MS
1239 fprintf(stderr, "ERROR: %.3f select() for %d/%d failed: %s\n", run_time(),
1240 ipv4, ipv6, strerror(errno));
89d46774 1241 break;
1242 }
1243
22c9029b
MS
1244 busy = 0;
1245
1246 if (FD_ISSET(ipv4, &input))
1247 {
1248 read_snmp_response(ipv4);
1249 busy = 1;
1250 }
1251
1252 if (ipv6 >= 0 && FD_ISSET(ipv6, &input))
1253 {
1254 read_snmp_response(ipv6);
1255 busy = 1;
1256 }
1257
1258 if (!busy)
749b1e90
MS
1259 {
1260 /*
1261 * List devices with complete information...
1262 */
89d46774 1263
749b1e90 1264 int sent_something = 0;
89d46774 1265
749b1e90
MS
1266 for (device = (snmp_cache_t *)cupsArrayFirst(Devices);
1267 device;
1268 device = (snmp_cache_t *)cupsArrayNext(Devices))
1269 if (!device->sent && device->info && device->make_and_model)
1270 {
1271 if (device->uri)
1272 list_device(device);
1273 else
1274 probe_device(device);
1275
1276 device->sent = sent_something = 1;
1277 }
1278
1279 if (!sent_something)
1280 break;
1281 }
1282 }
89d46774 1283
1284 debug_printf("DEBUG: %.3f Scan complete!\n", run_time());
1285}
1286
1287
89d46774 1288/*
1289 * 'try_connect()' - Try connecting on a port...
1290 */
1291
1292static int /* O - 0 on success or -1 on error */
1293try_connect(http_addr_t *addr, /* I - Socket address */
1294 const char *addrname, /* I - Hostname or IP address */
1295 int port) /* I - Port number */
1296{
1297 int fd; /* Socket */
1298 int status; /* Connection status */
1299
1300
1301 debug_printf("DEBUG: %.3f Trying %s://%s:%d...\n", run_time(),
1302 port == 515 ? "lpd" : "socket", addrname, port);
1303
5ec1fd3d 1304 if ((fd = socket(httpAddrFamily(addr), SOCK_STREAM, 0)) < 0)
89d46774 1305 {
c0e1af83 1306 fprintf(stderr, "ERROR: Unable to create socket: %s\n",
1307 strerror(errno));
89d46774 1308 return (-1);
1309 }
1310
22c9029b 1311 _httpAddrSetPort(addr, port);
89d46774 1312
89d46774 1313 alarm(1);
1314
7e86f2f6 1315 status = connect(fd, (void *)addr, (socklen_t)httpAddrLength(addr));
89d46774 1316
1317 close(fd);
1318 alarm(0);
1319
1320 return (status);
1321}
1322
1323
1324/*
1325 * 'update_cache()' - Update a cached device...
1326 */
1327
1328static void
1329update_cache(snmp_cache_t *device, /* I - Device */
1330 const char *uri, /* I - Device URI */
1331 const char *id, /* I - Device ID */
1332 const char *make_model) /* I - Device make and model */
1333{
1334 if (device->uri)
1335 free(device->uri);
1336
1337 device->uri = strdup(uri);
1338
1339 if (id)
1340 {
1341 if (device->id)
1342 free(device->id);
1343
1344 device->id = strdup(id);
1345 }
1346
1347 if (make_model)
1348 {
1349 if (device->make_and_model)
1350 free(device->make_and_model);
1351
1352 device->make_and_model = strdup(make_model);
1353 }
d09495fa 1354
1355 list_device(device);
89d46774 1356}