Revert manifest to default one
[external/cups.git] / backend / snmp.c
1 /*
2  * "$Id: snmp.c 10209 2012-01-30 22:19:03Z mike $"
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
114 enum                                    /**** 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
124 typedef 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
130 typedef 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
147 static char             *add_array(cups_array_t *a, const char *s);
148 static void             add_cache(http_addr_t *addr, const char *addrname,
149                                   const char *uri, const char *id,
150                                   const char *make_and_model);
151 static device_uri_t     *add_device_uri(char *value);
152 static void             alarm_handler(int sig);
153 static int              compare_cache(snmp_cache_t *a, snmp_cache_t *b);
154 static void             debug_printf(const char *format, ...);
155 static void             fix_make_model(char *make_model,
156                                        const char *old_make_model,
157                                        int make_model_size);
158 static void             free_array(cups_array_t *a);
159 static void             free_cache(void);
160 static http_addrlist_t  *get_interface_addresses(const char *ifname);
161 static void             list_device(snmp_cache_t *cache);
162 static const char       *password_cb(const char *prompt);
163 static void             probe_device(snmp_cache_t *device);
164 static void             read_snmp_conf(const char *address);
165 static void             read_snmp_response(int fd);
166 static double           run_time(void);
167 static void             scan_devices(int ipv4, int ipv6);
168 static int              try_connect(http_addr_t *addr, const char *addrname,
169                                     int port);
170 static 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
178 static cups_array_t     *Addresses = NULL;
179 static cups_array_t     *Communities = NULL;
180 static cups_array_t     *Devices = NULL;
181 static int              DebugLevel = 0;
182 static const int        DescriptionOID[] = { CUPS_OID_hrDeviceDescr, 1, -1 };
183 static const int        LocationOID[] = { CUPS_OID_sysLocation, 0, -1 };
184 static const int        DeviceTypeOID[] = { CUPS_OID_hrDeviceType, 1, -1 };
185 static const int        DeviceIdOID[] = { CUPS_OID_ppmPrinterIEEE1284DeviceId, 1, -1 };
186 static const int        UriOID[] = { CUPS_OID_ppmPortServiceNameOrURI, 1, 1, -1 };
187 static const int        LexmarkProductOID[] = { 1,3,6,1,4,1,641,2,1,2,1,2,1,-1 };
188 static const int        LexmarkProductOID2[] = { 1,3,6,1,4,1,674,10898,100,2,1,2,1,2,1,-1 };
189 static const int        LexmarkDeviceIdOID[] = { 1,3,6,1,4,1,641,2,1,2,1,3,1,-1 };
190 static const int        HPDeviceIdOID[] = { 1,3,6,1,4,1,11,2,3,9,1,1,7,0,-1 };
191 static const int        RicohDeviceIdOID[] = { 1,3,6,1,4,1,367,3,2,1,1,1,11,0,-1 };
192 static const int        XeroxProductOID[] = { 1,3,6,1,4,1,128,2,1,3,1,2,0,-1 };
193 static cups_array_t     *DeviceURIs = NULL;
194 static int              HostNameLookups = 0;
195 static int              MaxRunTime = 120;
196 static struct timeval   StartTime;
197
198
199 /*
200  * 'main()' - Discover printers via SNMP.
201  */
202
203 int                                     /* O - Exit status */
204 main(int  argc,                         /* I - Number of command-line arguments (6 or 7) */
205      char *argv[])                      /* I - Command-line arguments */
206 {
207   int           ipv4,                   /* SNMP IPv4 socket */
208                 ipv6;                   /* SNMP IPv6 socket */
209 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
210   struct sigaction action;              /* Actions for POSIX signals */
211 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
212
213
214  /*
215   * Check command-line options...
216   */
217
218   if (argc > 2)
219   {
220     _cupsLangPuts(stderr, _("Usage: snmp [host-or-ip-address]"));
221     return (1);
222   }
223
224  /*
225   * Set the password callback for IPP operations...
226   */
227
228   cupsSetPasswordCB(password_cb);
229
230  /*
231   * Catch SIGALRM signals...
232   */
233
234 #ifdef HAVE_SIGSET
235   sigset(SIGALRM, alarm_handler);
236 #elif defined(HAVE_SIGACTION)
237   memset(&action, 0, sizeof(action));
238
239   sigemptyset(&action.sa_mask);
240   sigaddset(&action.sa_mask, SIGALRM);
241   action.sa_handler = alarm_handler;
242   sigaction(SIGALRM, &action, NULL);
243 #else
244   signal(SIGALRM, alarm_handler);
245 #endif /* HAVE_SIGSET */
246
247  /*
248   * Open the SNMP socket...
249   */
250
251   if ((ipv4 = _cupsSNMPOpen(AF_INET)) < 0)
252     return (1);
253
254 #ifdef AF_INET6
255   if ((ipv6 = _cupsSNMPOpen(AF_INET6)) < 0)
256     return (1);
257 #else
258   ipv6 = -1;
259 #endif /* AF_INET6 */
260
261  /*
262   * Read the configuration file and any cache data...
263   */
264
265   read_snmp_conf(argv[1]);
266
267   _cupsSNMPSetDebug(DebugLevel);
268
269   Devices = cupsArrayNew((cups_array_func_t)compare_cache, NULL);
270
271  /*
272   * Scan for devices...
273   */
274
275   scan_devices(ipv4, ipv6);
276
277  /*
278   * Close, free, and return with no errors...
279   */
280
281   _cupsSNMPClose(ipv4);
282   if (ipv6 >= 0)
283     _cupsSNMPClose(ipv6);
284
285   free_array(Addresses);
286   free_array(Communities);
287   free_cache();
288
289   return (0);
290 }
291
292
293 /*
294  * 'add_array()' - Add a string to an array.
295  */
296
297 static char *                           /* O - New string */
298 add_array(cups_array_t *a,              /* I - Array */
299           const char   *s)              /* I - String to add */
300 {
301   char  *dups;                          /* New string */
302
303
304   dups = strdup(s);
305
306   cupsArrayAdd(a, dups);
307
308   return (dups);
309 }
310
311
312 /*
313  * 'add_cache()' - Add a cached device...
314  */
315
316 static void
317 add_cache(http_addr_t *addr,            /* I - Device IP address */
318           const char  *addrname,        /* I - IP address or name string */
319           const char  *uri,             /* I - Device URI */
320           const char  *id,              /* I - 1284 device ID */
321           const char  *make_and_model)  /* I - Make and model */
322 {
323   snmp_cache_t  *temp;                  /* New device entry */
324
325
326   debug_printf("DEBUG: add_cache(addr=%p, addrname=\"%s\", uri=\"%s\", "
327                   "id=\"%s\", make_and_model=\"%s\")\n",
328                addr, addrname, uri ? uri : "(null)", id ? id : "(null)",
329                make_and_model ? make_and_model : "(null)");
330
331   temp = calloc(1, sizeof(snmp_cache_t));
332   memcpy(&(temp->address), addr, sizeof(temp->address));
333
334   temp->addrname = strdup(addrname);
335
336   if (uri)
337     temp->uri = strdup(uri);
338
339   if (id)
340     temp->id = strdup(id);
341
342   if (make_and_model)
343     temp->make_and_model = strdup(make_and_model);
344
345   cupsArrayAdd(Devices, temp);
346
347   if (uri)
348     list_device(temp);
349 }
350
351
352 /*
353  * 'add_device_uri()' - Add a device URI to the cache.
354  *
355  * The value string is modified (chopped up) as needed.
356  */
357
358 static device_uri_t *                   /* O - Device URI */
359 add_device_uri(char *value)             /* I - Value from snmp.conf */
360 {
361   device_uri_t  *device_uri;            /* Device URI */
362   char          *start;                 /* Start of value */
363
364
365  /*
366   * Allocate memory as needed...
367   */
368
369   if (!DeviceURIs)
370     DeviceURIs = cupsArrayNew(NULL, NULL);
371
372   if (!DeviceURIs)
373     return (NULL);
374
375   if ((device_uri = calloc(1, sizeof(device_uri_t))) == NULL)
376     return (NULL);
377
378   if ((device_uri->uris = cupsArrayNew(NULL, NULL)) == NULL)
379   {
380     free(device_uri);
381     return (NULL);
382   }
383
384  /*
385   * Scan the value string for the regular expression and URI(s)...
386   */
387
388   value ++; /* Skip leading " */
389
390   for (start = value; *value && *value != '\"'; value ++)
391     if (*value == '\\' && value[1])
392       _cups_strcpy(value, value + 1);
393
394   if (!*value)
395   {
396     fputs("ERROR: Missing end quote for DeviceURI!\n", stderr);
397
398     cupsArrayDelete(device_uri->uris);
399     free(device_uri);
400
401     return (NULL);
402   }
403
404   *value++ = '\0';
405
406   if (regcomp(&(device_uri->re), start, REG_EXTENDED | REG_ICASE))
407   {
408     fputs("ERROR: Bad regular expression for DeviceURI!\n", stderr);
409
410     cupsArrayDelete(device_uri->uris);
411     free(device_uri);
412
413     return (NULL);
414   }
415
416   while (*value)
417   {
418     while (isspace(*value & 255))
419       value ++;
420
421     if (!*value)
422       break;
423
424     for (start = value; *value && !isspace(*value & 255); value ++);
425
426     if (*value)
427       *value++ = '\0';
428
429     cupsArrayAdd(device_uri->uris, strdup(start));
430   }
431
432  /*
433   * Add the device URI to the list and return it...
434   */
435
436   cupsArrayAdd(DeviceURIs, device_uri);
437
438   return (device_uri);
439 }
440
441
442 /*
443  * 'alarm_handler()' - Handle alarm signals...
444  */
445
446 static void
447 alarm_handler(int sig)                  /* I - Signal number */
448 {
449  /*
450   * Do nothing...
451   */
452
453   (void)sig;
454
455 #if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION)
456   signal(SIGALRM, alarm_handler);
457 #endif /* !HAVE_SIGSET && !HAVE_SIGACTION */
458
459   if (DebugLevel)
460     write(2, "DEBUG: ALARM!\n", 14);
461 }
462
463
464 /*
465  * 'compare_cache()' - Compare two cache entries.
466  */
467
468 static int                              /* O - Result of comparison */
469 compare_cache(snmp_cache_t *a,          /* I - First cache entry */
470               snmp_cache_t *b)          /* I - Second cache entry */
471 {
472   return (_cups_strcasecmp(a->addrname, b->addrname));
473 }
474
475
476 /*
477  * 'debug_printf()' - Display some debugging information.
478  */
479
480 static void
481 debug_printf(const char *format,        /* I - Printf-style format string */
482              ...)                       /* I - Additional arguments as needed */
483 {
484   va_list       ap;                     /* Pointer to arguments */
485
486
487   if (!DebugLevel)
488     return;
489
490   va_start(ap, format);
491   vfprintf(stderr, format, ap);
492   va_end(ap);
493 }
494
495
496 /*
497  * 'fix_make_model()' - Fix common problems in the make-and-model string.
498  */
499
500 static void
501 fix_make_model(
502     char       *make_model,             /* I - New make-and-model string */
503     const char *old_make_model,         /* I - Old make-and-model string */
504     int        make_model_size)         /* I - Size of new string buffer */
505 {
506   char  *mmptr;                         /* Pointer into make-and-model string */
507
508
509  /*
510   * Fix some common problems with the make-and-model string so
511   * that printer driver detection works better...
512   */
513
514   if (!_cups_strncasecmp(old_make_model, "Hewlett-Packard", 15))
515   {
516    /*
517     * Strip leading Hewlett-Packard and hp prefixes and replace
518     * with a single HP manufacturer prefix...
519     */
520
521     mmptr = (char *)old_make_model + 15;
522
523     while (isspace(*mmptr & 255))
524       mmptr ++;
525
526     if (!_cups_strncasecmp(mmptr, "hp", 2))
527     {
528       mmptr += 2;
529
530       while (isspace(*mmptr & 255))
531         mmptr ++;
532     }
533
534     make_model[0] = 'H';
535     make_model[1] = 'P';
536     make_model[2] = ' ';
537     strlcpy(make_model + 3, mmptr, make_model_size - 3);
538   }
539   else if (!_cups_strncasecmp(old_make_model, "deskjet", 7))
540     snprintf(make_model, make_model_size, "HP DeskJet%s", old_make_model + 7);
541   else if (!_cups_strncasecmp(old_make_model, "officejet", 9))
542     snprintf(make_model, make_model_size, "HP OfficeJet%s", old_make_model + 9);
543   else if (!_cups_strncasecmp(old_make_model, "stylus_pro_", 11))
544     snprintf(make_model, make_model_size, "EPSON Stylus Pro %s",
545              old_make_model + 11);
546   else
547     strlcpy(make_model, old_make_model, make_model_size);
548
549   if ((mmptr = strstr(make_model, ", Inc.,")) != NULL)
550   {
551    /*
552     * Strip inc. from name, e.g. "Tektronix, Inc., Phaser 560"
553     * becomes "Tektronix Phaser 560"...
554     */
555
556     _cups_strcpy(mmptr, mmptr + 7);
557   }
558
559   if ((mmptr = strstr(make_model, " Network")) != NULL)
560   {
561    /*
562     * Drop unnecessary informational text, e.g. "Xerox DocuPrint N2025
563     * Network LaserJet - 2.12" becomes "Xerox DocuPrint N2025"...
564     */
565
566     *mmptr = '\0';
567   }
568
569   if ((mmptr = strchr(make_model, ',')) != NULL)
570   {
571    /*
572     * Drop anything after a trailing comma...
573     */
574
575     *mmptr = '\0';
576   }
577 }
578
579
580 /*
581  * 'free_array()' - Free an array of strings.
582  */
583
584 static void
585 free_array(cups_array_t *a)             /* I - Array */
586 {
587   char  *s;                             /* Current string */
588
589
590   for (s = (char *)cupsArrayFirst(a); s; s = (char *)cupsArrayNext(a))
591     free(s);
592
593   cupsArrayDelete(a);
594 }
595
596
597 /*
598  * 'free_cache()' - Free the array of cached devices.
599  */
600
601 static void
602 free_cache(void)
603 {
604   snmp_cache_t  *cache;                 /* Cached device */
605
606
607   for (cache = (snmp_cache_t *)cupsArrayFirst(Devices);
608        cache;
609        cache = (snmp_cache_t *)cupsArrayNext(Devices))
610   {
611     free(cache->addrname);
612
613     if (cache->uri)
614       free(cache->uri);
615
616     if (cache->id)
617       free(cache->id);
618
619     if (cache->make_and_model)
620       free(cache->make_and_model);
621
622     free(cache);
623   }
624
625   cupsArrayDelete(Devices);
626   Devices = NULL;
627 }
628
629
630 /*
631  * 'get_interface_addresses()' - Get the broadcast address(es) associated
632  *                               with an interface.
633  */
634
635 static http_addrlist_t *                /* O - List of addresses */
636 get_interface_addresses(
637     const char *ifname)                 /* I - Interface name */
638 {
639   struct ifaddrs        *addrs,         /* Interface address list */
640                         *addr;          /* Current interface address */
641   http_addrlist_t       *first,         /* First address in list */
642                         *last,          /* Last address in list */
643                         *current;       /* Current address */
644
645
646   if (getifaddrs(&addrs) < 0)
647     return (NULL);
648
649   for (addr = addrs, first = NULL, last = NULL; addr; addr = addr->ifa_next)
650     if ((addr->ifa_flags & IFF_BROADCAST) && addr->ifa_broadaddr &&
651         addr->ifa_broadaddr->sa_family == AF_INET &&
652         (!ifname || !strcmp(ifname, addr->ifa_name)))
653     {
654       current = calloc(1, sizeof(http_addrlist_t));
655
656       memcpy(&(current->addr), addr->ifa_broadaddr,
657              sizeof(struct sockaddr_in));
658
659       if (!last)
660         first = current;
661       else
662         last->next = current;
663
664       last = current;
665     }
666
667   freeifaddrs(addrs);
668
669   return (first);
670 }
671
672
673 /*
674  * 'list_device()' - List a device we found...
675  */
676
677 static void
678 list_device(snmp_cache_t *cache)        /* I - Cached device */
679 {
680   if (cache->uri)
681     cupsBackendReport("network", cache->uri, cache->make_and_model,
682                       cache->info, cache->id, cache->location);
683 }
684
685
686 /*
687  * 'password_cb()' - Handle authentication requests.
688  *
689  * All we do right now is return NULL, indicating that no authentication
690  * is possible.
691  */
692
693 static const char *                     /* O - Password (NULL) */
694 password_cb(const char *prompt)         /* I - Prompt message */
695 {
696   (void)prompt;                         /* Anti-compiler-warning-code */
697
698   return (NULL);
699 }
700
701
702 /*
703  * 'probe_device()' - Probe a device to discover whether it is a printer.
704  *
705  * TODO: Try using the Port Monitor MIB to discover the correct protocol
706  *       to use - first need a commercially-available printer that supports
707  *       it, though...
708  */
709
710 static void
711 probe_device(snmp_cache_t *device)      /* I - Device */
712 {
713   char          uri[1024],              /* Full device URI */
714                 *uriptr,                /* Pointer into URI */
715                 *format;                /* Format string for device */
716   device_uri_t  *device_uri;            /* Current DeviceURI match */
717
718
719   debug_printf("DEBUG: %.3f Probing %s...\n", run_time(), device->addrname);
720
721 #ifdef __APPLE__
722  /*
723   * If the printer supports Bonjour/mDNS, don't report it from the SNMP backend.
724   */
725
726   if (!try_connect(&(device->address), device->addrname, 5353))
727   {
728     debug_printf("DEBUG: %s supports mDNS, not reporting!\n", device->addrname);
729     return;
730   }
731 #endif /* __APPLE__ */
732
733  /*
734   * Lookup the device in the match table...
735   */
736
737   for (device_uri = (device_uri_t *)cupsArrayFirst(DeviceURIs);
738        device_uri;
739        device_uri = (device_uri_t *)cupsArrayNext(DeviceURIs))
740     if (device->make_and_model &&
741         !regexec(&(device_uri->re), device->make_and_model, 0, NULL, 0))
742     {
743      /*
744       * Found a match, add the URIs...
745       */
746
747       for (format = (char *)cupsArrayFirst(device_uri->uris);
748            format;
749            format = (char *)cupsArrayNext(device_uri->uris))
750       {
751         for (uriptr = uri; *format && uriptr < (uri + sizeof(uri) - 1);)
752           if (*format == '%' && format[1] == 's')
753           {
754            /*
755             * Insert hostname/address...
756             */
757
758             strlcpy(uriptr, device->addrname, sizeof(uri) - (uriptr - uri));
759             uriptr += strlen(uriptr);
760             format += 2;
761           }
762           else
763             *uriptr++ = *format++;
764
765         *uriptr = '\0';
766
767         update_cache(device, uri, NULL, NULL);
768       }
769
770       return;
771     }
772
773  /*
774   * Then try the standard ports...
775   */
776
777   if (!try_connect(&(device->address), device->addrname, 9100))
778   {
779     debug_printf("DEBUG: %s supports AppSocket!\n", device->addrname);
780
781     snprintf(uri, sizeof(uri), "socket://%s", device->addrname);
782     update_cache(device, uri, NULL, NULL);
783   }
784   else if (!try_connect(&(device->address), device->addrname, 515))
785   {
786     debug_printf("DEBUG: %s supports LPD!\n", device->addrname);
787
788     snprintf(uri, sizeof(uri), "lpd://%s/", device->addrname);
789     update_cache(device, uri, NULL, NULL);
790   }
791 }
792
793
794 /*
795  * 'read_snmp_conf()' - Read the snmp.conf file.
796  */
797
798 static void
799 read_snmp_conf(const char *address)     /* I - Single address to probe */
800 {
801   cups_file_t   *fp;                    /* File pointer */
802   char          filename[1024],         /* Filename */
803                 line[1024],             /* Line from file */
804                 *value;                 /* Value on line */
805   int           linenum;                /* Line number */
806   const char    *cups_serverroot;       /* CUPS_SERVERROOT env var */
807   const char    *debug;                 /* CUPS_DEBUG_LEVEL env var */
808   const char    *runtime;               /* CUPS_MAX_RUN_TIME env var */
809
810
811  /*
812   * Initialize the global address and community lists...
813   */
814
815   Addresses   = cupsArrayNew(NULL, NULL);
816   Communities = cupsArrayNew(NULL, NULL);
817
818   if (address)
819     add_array(Addresses, address);
820
821   if ((debug = getenv("CUPS_DEBUG_LEVEL")) != NULL)
822     DebugLevel = atoi(debug);
823
824   if ((runtime = getenv("CUPS_MAX_RUN_TIME")) != NULL)
825     MaxRunTime = atoi(runtime);
826
827  /*
828   * Find the snmp.conf file...
829   */
830
831   if ((cups_serverroot = getenv("CUPS_SERVERROOT")) == NULL)
832     cups_serverroot = CUPS_SERVERROOT;
833
834   snprintf(filename, sizeof(filename), "%s/snmp.conf", cups_serverroot);
835
836   if ((fp = cupsFileOpen(filename, "r")) != NULL)
837   {
838    /*
839     * Read the snmp.conf file...
840     */
841
842     linenum = 0;
843
844     while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
845     {
846       if (!value)
847         fprintf(stderr, "ERROR: Missing value on line %d of %s!\n", linenum,
848                 filename);
849       else if (!_cups_strcasecmp(line, "Address"))
850       {
851         if (!address)
852           add_array(Addresses, value);
853       }
854       else if (!_cups_strcasecmp(line, "Community"))
855         add_array(Communities, value);
856       else if (!_cups_strcasecmp(line, "DebugLevel"))
857         DebugLevel = atoi(value);
858       else if (!_cups_strcasecmp(line, "DeviceURI"))
859       {
860         if (*value != '\"')
861           fprintf(stderr,
862                   "ERROR: Missing double quote for regular expression on "
863                   "line %d of %s!\n", linenum, filename);
864         else
865           add_device_uri(value);
866       }
867       else if (!_cups_strcasecmp(line, "HostNameLookups"))
868         HostNameLookups = !_cups_strcasecmp(value, "on") ||
869                           !_cups_strcasecmp(value, "yes") ||
870                           !_cups_strcasecmp(value, "true") ||
871                           !_cups_strcasecmp(value, "double");
872       else if (!_cups_strcasecmp(line, "MaxRunTime"))
873         MaxRunTime = atoi(value);
874       else
875         fprintf(stderr, "ERROR: Unknown directive %s on line %d of %s!\n",
876                 line, linenum, filename);
877     }
878
879     cupsFileClose(fp);
880   }
881
882  /*
883   * Use defaults if parameters are undefined...
884   */
885
886   if (cupsArrayCount(Addresses) == 0)
887   {
888    /*
889     * If we have no addresses, exit immediately...
890     */
891
892     fprintf(stderr,
893             "DEBUG: No address specified and no Address line in %s...\n",
894             filename);
895     exit(0);
896   }
897
898   if (cupsArrayCount(Communities) == 0)
899   {
900     fputs("INFO: Using default SNMP Community public\n", stderr);
901     add_array(Communities, "public");
902   }
903 }
904
905
906 /*
907  * 'read_snmp_response()' - Read and parse a SNMP response...
908  */
909
910 static void
911 read_snmp_response(int fd)              /* I - SNMP socket file descriptor */
912 {
913   char          addrname[256];          /* Source address name */
914   cups_snmp_t   packet;                 /* Decoded packet */
915   snmp_cache_t  key,                    /* Search key */
916                 *device;                /* Matching device */
917
918
919  /*
920   * Read the response data...
921   */
922
923   if (!_cupsSNMPRead(fd, &packet, -1.0))
924   {
925     fprintf(stderr, "ERROR: Unable to read data from socket: %s\n",
926             strerror(errno));
927     return;
928   }
929
930   if (HostNameLookups)
931     httpAddrLookup(&(packet.address), addrname, sizeof(addrname));
932   else
933     httpAddrString(&(packet.address), addrname, sizeof(addrname));
934
935   debug_printf("DEBUG: %.3f Received data from %s...\n", run_time(), addrname);
936
937  /*
938   * Look for the response status code in the SNMP message header...
939   */
940
941   if (packet.error)
942   {
943     fprintf(stderr, "ERROR: Bad SNMP packet from %s: %s\n", addrname,
944             packet.error);
945
946     return;
947   }
948
949   debug_printf("DEBUG: community=\"%s\"\n", packet.community);
950   debug_printf("DEBUG: request-id=%d\n", packet.request_id);
951   debug_printf("DEBUG: error-status=%d\n", packet.error_status);
952
953   if (packet.error_status && packet.request_id != DEVICE_TYPE)
954     return;
955
956  /*
957   * Find a matching device in the cache...
958   */
959
960   key.addrname = addrname;
961   device       = (snmp_cache_t *)cupsArrayFind(Devices, &key);
962
963  /*
964   * Process the message...
965   */
966
967   switch (packet.request_id)
968   {
969     case DEVICE_TYPE :
970        /*
971         * Got the device type response...
972         */
973
974         if (device)
975         {
976           debug_printf("DEBUG: Discarding duplicate device type for \"%s\"...\n",
977                        addrname);
978           return;
979         }
980
981        /*
982         * Add the device and request the device data...
983         */
984
985         add_cache(&(packet.address), addrname, NULL, NULL, NULL);
986
987         _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
988                        packet.community, CUPS_ASN1_GET_REQUEST,
989                        DEVICE_DESCRIPTION, DescriptionOID);
990         _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
991                        packet.community, CUPS_ASN1_GET_REQUEST,
992                        DEVICE_ID, DeviceIdOID);
993         _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
994                        packet.community, CUPS_ASN1_GET_REQUEST,
995                        DEVICE_URI, UriOID);
996         _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
997                        packet.community, CUPS_ASN1_GET_REQUEST,
998                        DEVICE_LOCATION, LocationOID);
999         _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
1000                        packet.community, CUPS_ASN1_GET_REQUEST,
1001                        DEVICE_PRODUCT, LexmarkProductOID);
1002         _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
1003                        packet.community, CUPS_ASN1_GET_REQUEST,
1004                        DEVICE_PRODUCT, LexmarkProductOID2);
1005         _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
1006                        packet.community, CUPS_ASN1_GET_REQUEST,
1007                        DEVICE_ID, LexmarkDeviceIdOID);
1008         _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
1009                        packet.community, CUPS_ASN1_GET_REQUEST,
1010                        DEVICE_ID, RicohDeviceIdOID);
1011         _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
1012                        packet.community, CUPS_ASN1_GET_REQUEST,
1013                        DEVICE_PRODUCT, XeroxProductOID);
1014         _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1,
1015                        packet.community, CUPS_ASN1_GET_REQUEST,
1016                        DEVICE_ID, HPDeviceIdOID);
1017         break;
1018
1019     case DEVICE_DESCRIPTION :
1020         if (device && packet.object_type == CUPS_ASN1_OCTET_STRING)
1021         {
1022          /*
1023           * Update an existing cache entry...
1024           */
1025
1026           char  make_model[256];        /* Make and model */
1027
1028
1029           if (strchr((char *)packet.object_value.string.bytes, ':') &&
1030               strchr((char *)packet.object_value.string.bytes, ';'))
1031           {
1032            /*
1033             * Description is the IEEE-1284 device ID...
1034             */
1035
1036             if (!device->id)
1037               device->id = strdup((char *)packet.object_value.string.bytes);
1038
1039             backendGetMakeModel((char *)packet.object_value.string.bytes,
1040                                 make_model, sizeof(make_model));
1041
1042             if (device->info)
1043               free(device->info);
1044
1045             device->info = strdup(make_model);
1046           }
1047           else
1048           {
1049            /*
1050             * Description is plain text...
1051             */
1052
1053             fix_make_model(make_model, (char *)packet.object_value.string.bytes,
1054                            sizeof(make_model));
1055
1056             if (device->info)
1057               free(device->info);
1058
1059             device->info = strdup((char *)packet.object_value.string.bytes);
1060           }
1061
1062           if (!device->make_and_model)
1063             device->make_and_model = strdup(make_model);
1064         }
1065         break;
1066
1067     case DEVICE_ID :
1068         if (device && packet.object_type == CUPS_ASN1_OCTET_STRING &&
1069             (!device->id ||
1070              strlen(device->id) < packet.object_value.string.num_bytes))
1071         {
1072          /*
1073           * Update an existing cache entry...
1074           */
1075
1076           char  make_model[256];        /* Make and model */
1077
1078
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
1161 static double                           /* O - Number of seconds */
1162 run_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
1178 static void
1179 scan_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
1326 static int                              /* O - 0 on success or -1 on error */
1327 try_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
1362 static void
1363 update_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 10209 2012-01-30 22:19:03Z mike $".
1395  */