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