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