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