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