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