| /* |
| * SNMP discovery backend for CUPS. |
| * |
| * Copyright © 2007-2014 by Apple Inc. |
| * Copyright © 2006-2007 by Easy Software Products, all rights reserved. |
| * |
| * Licensed under Apache License v2.0. See the file "LICENSE" for more |
| * information. |
| */ |
| |
| /* |
| * Include necessary headers. |
| */ |
| |
| #include "backend-private.h" |
| #include <cups/array.h> |
| #include <cups/file.h> |
| #include <cups/http-private.h> |
| #include <regex.h> |
| |
| |
| /* |
| * This backend implements SNMP printer discovery. It uses a broadcast- |
| * based approach to get SNMP response packets from potential printers, |
| * requesting OIDs from the Host and Port Monitor MIBs, does a URI |
| * lookup based on the device description string, and finally a probe of |
| * port 9100 (AppSocket) and 515 (LPD). |
| * |
| * The current focus is on printers with internal network cards, although |
| * the code also works with many external print servers as well. |
| * |
| * The backend reads the snmp.conf file from the CUPS_SERVERROOT directory |
| * which can contain comments, blank lines, or any number of the following |
| * directives: |
| * |
| * Address ip-address |
| * Address @LOCAL |
| * Address @IF(name) |
| * Community name |
| * DebugLevel N |
| * DeviceURI "regex pattern" uri |
| * HostNameLookups on |
| * HostNameLookups off |
| * MaxRunTime N |
| * |
| * The default is to use: |
| * |
| * Address @LOCAL |
| * Community public |
| * DebugLevel 0 |
| * HostNameLookups off |
| * MaxRunTime 120 |
| * |
| * This backend is known to work with the following network printers and |
| * print servers: |
| * |
| * Axis OfficeBasic, 5400, 5600 |
| * Brother |
| * EPSON |
| * Genicom |
| * HP JetDirect |
| * Lexmark |
| * Sharp |
| * Tektronix |
| * Xerox |
| * |
| * It does not currently work with: |
| * |
| * DLink |
| * Linksys |
| * Netgear |
| * Okidata |
| * |
| * (for all of these, they do not support the Host MIB) |
| */ |
| |
| /* |
| * Types... |
| */ |
| |
| enum /**** Request IDs for each field ****/ |
| { |
| DEVICE_TYPE = 1, |
| DEVICE_DESCRIPTION, |
| DEVICE_LOCATION, |
| DEVICE_ID, |
| DEVICE_URI, |
| DEVICE_PRODUCT |
| }; |
| |
| typedef struct device_uri_s /**** DeviceURI values ****/ |
| { |
| regex_t re; /* Regular expression to match */ |
| cups_array_t *uris; /* URIs */ |
| } device_uri_t; |
| |
| typedef struct snmp_cache_s /**** SNMP scan cache ****/ |
| { |
| http_addr_t address; /* Address of device */ |
| char *addrname, /* Name of device */ |
| *uri, /* device-uri */ |
| *id, /* device-id */ |
| *info, /* device-info */ |
| *location, /* device-location */ |
| *make_and_model; /* device-make-and-model */ |
| int sent; /* Has this device been listed? */ |
| } snmp_cache_t; |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| static char *add_array(cups_array_t *a, const char *s); |
| static void add_cache(http_addr_t *addr, const char *addrname, |
| const char *uri, const char *id, |
| const char *make_and_model); |
| static device_uri_t *add_device_uri(char *value); |
| static void alarm_handler(int sig); |
| static int compare_cache(snmp_cache_t *a, snmp_cache_t *b); |
| static void debug_printf(const char *format, ...); |
| static void fix_make_model(char *make_model, |
| const char *old_make_model, |
| int make_model_size); |
| static void free_array(cups_array_t *a); |
| static void free_cache(void); |
| static http_addrlist_t *get_interface_addresses(const char *ifname); |
| static void list_device(snmp_cache_t *cache); |
| static const char *password_cb(const char *prompt); |
| static void probe_device(snmp_cache_t *device); |
| static void read_snmp_conf(const char *address); |
| static void read_snmp_response(int fd); |
| static double run_time(void); |
| static void scan_devices(int ipv4, int ipv6); |
| static int try_connect(http_addr_t *addr, const char *addrname, |
| int port); |
| static void update_cache(snmp_cache_t *device, const char *uri, |
| const char *id, const char *make_model); |
| |
| |
| /* |
| * Local globals... |
| */ |
| |
| static cups_array_t *Addresses = NULL; |
| static cups_array_t *Communities = NULL; |
| static cups_array_t *Devices = NULL; |
| static int DebugLevel = 0; |
| static const int DescriptionOID[] = { CUPS_OID_hrDeviceDescr, 1, -1 }; |
| static const int LocationOID[] = { CUPS_OID_sysLocation, 0, -1 }; |
| static const int DeviceTypeOID[] = { CUPS_OID_hrDeviceType, 1, -1 }; |
| static const int DeviceIdOID[] = { CUPS_OID_ppmPrinterIEEE1284DeviceId, 1, -1 }; |
| static const int UriOID[] = { CUPS_OID_ppmPortServiceNameOrURI, 1, 1, -1 }; |
| static const int LexmarkProductOID[] = { 1,3,6,1,4,1,641,2,1,2,1,2,1,-1 }; |
| static const int LexmarkProductOID2[] = { 1,3,6,1,4,1,674,10898,100,2,1,2,1,2,1,-1 }; |
| static const int LexmarkDeviceIdOID[] = { 1,3,6,1,4,1,641,2,1,2,1,3,1,-1 }; |
| static const int XeroxProductOID[] = { 1,3,6,1,4,1,128,2,1,3,1,2,0,-1 }; |
| static cups_array_t *DeviceURIs = NULL; |
| static int HostNameLookups = 0; |
| static int MaxRunTime = 120; |
| static struct timeval StartTime; |
| |
| |
| /* |
| * 'main()' - Discover printers via SNMP. |
| */ |
| |
| int /* O - Exit status */ |
| main(int argc, /* I - Number of command-line arguments (6 or 7) */ |
| char *argv[]) /* I - Command-line arguments */ |
| { |
| int ipv4, /* SNMP IPv4 socket */ |
| ipv6; /* SNMP IPv6 socket */ |
| #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) |
| struct sigaction action; /* Actions for POSIX signals */ |
| #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ |
| |
| |
| /* |
| * Check command-line options... |
| */ |
| |
| if (argc > 2) |
| { |
| _cupsLangPuts(stderr, _("Usage: snmp [host-or-ip-address]")); |
| return (1); |
| } |
| |
| /* |
| * Set the password callback for IPP operations... |
| */ |
| |
| cupsSetPasswordCB(password_cb); |
| |
| /* |
| * Catch SIGALRM signals... |
| */ |
| |
| #ifdef HAVE_SIGSET |
| sigset(SIGALRM, alarm_handler); |
| #elif defined(HAVE_SIGACTION) |
| memset(&action, 0, sizeof(action)); |
| |
| sigemptyset(&action.sa_mask); |
| sigaddset(&action.sa_mask, SIGALRM); |
| action.sa_handler = alarm_handler; |
| sigaction(SIGALRM, &action, NULL); |
| #else |
| signal(SIGALRM, alarm_handler); |
| #endif /* HAVE_SIGSET */ |
| |
| /* |
| * Open the SNMP socket... |
| */ |
| |
| if ((ipv4 = _cupsSNMPOpen(AF_INET)) < 0) |
| return (1); |
| |
| #ifdef AF_INET6 |
| if ((ipv6 = _cupsSNMPOpen(AF_INET6)) < 0) |
| perror("DEBUG: Unable to create IPv6 socket"); |
| #else |
| ipv6 = -1; |
| #endif /* AF_INET6 */ |
| |
| /* |
| * Read the configuration file and any cache data... |
| */ |
| |
| read_snmp_conf(argv[1]); |
| |
| _cupsSNMPSetDebug(DebugLevel); |
| |
| Devices = cupsArrayNew((cups_array_func_t)compare_cache, NULL); |
| |
| /* |
| * Scan for devices... |
| */ |
| |
| scan_devices(ipv4, ipv6); |
| |
| /* |
| * Close, free, and return with no errors... |
| */ |
| |
| _cupsSNMPClose(ipv4); |
| if (ipv6 >= 0) |
| _cupsSNMPClose(ipv6); |
| |
| free_array(Addresses); |
| free_array(Communities); |
| free_cache(); |
| |
| return (0); |
| } |
| |
| |
| /* |
| * 'add_array()' - Add a string to an array. |
| */ |
| |
| static char * /* O - New string */ |
| add_array(cups_array_t *a, /* I - Array */ |
| const char *s) /* I - String to add */ |
| { |
| char *dups; /* New string */ |
| |
| |
| dups = strdup(s); |
| |
| cupsArrayAdd(a, dups); |
| |
| return (dups); |
| } |
| |
| |
| /* |
| * 'add_cache()' - Add a cached device... |
| */ |
| |
| static void |
| add_cache(http_addr_t *addr, /* I - Device IP address */ |
| const char *addrname, /* I - IP address or name string */ |
| const char *uri, /* I - Device URI */ |
| const char *id, /* I - 1284 device ID */ |
| const char *make_and_model) /* I - Make and model */ |
| { |
| snmp_cache_t *temp; /* New device entry */ |
| |
| |
| debug_printf("DEBUG: add_cache(addr=%p, addrname=\"%s\", uri=\"%s\", " |
| "id=\"%s\", make_and_model=\"%s\")\n", |
| addr, addrname, uri ? uri : "(null)", id ? id : "(null)", |
| make_and_model ? make_and_model : "(null)"); |
| |
| temp = calloc(1, sizeof(snmp_cache_t)); |
| memcpy(&(temp->address), addr, sizeof(temp->address)); |
| |
| temp->addrname = strdup(addrname); |
| |
| if (uri) |
| temp->uri = strdup(uri); |
| |
| if (id) |
| temp->id = strdup(id); |
| |
| if (make_and_model) |
| temp->make_and_model = strdup(make_and_model); |
| |
| cupsArrayAdd(Devices, temp); |
| |
| if (uri) |
| list_device(temp); |
| } |
| |
| |
| /* |
| * 'add_device_uri()' - Add a device URI to the cache. |
| * |
| * The value string is modified (chopped up) as needed. |
| */ |
| |
| static device_uri_t * /* O - Device URI */ |
| add_device_uri(char *value) /* I - Value from snmp.conf */ |
| { |
| device_uri_t *device_uri; /* Device URI */ |
| char *start; /* Start of value */ |
| |
| |
| /* |
| * Allocate memory as needed... |
| */ |
| |
| if (!DeviceURIs) |
| DeviceURIs = cupsArrayNew(NULL, NULL); |
| |
| if (!DeviceURIs) |
| return (NULL); |
| |
| if ((device_uri = calloc(1, sizeof(device_uri_t))) == NULL) |
| return (NULL); |
| |
| if ((device_uri->uris = cupsArrayNew(NULL, NULL)) == NULL) |
| { |
| free(device_uri); |
| return (NULL); |
| } |
| |
| /* |
| * Scan the value string for the regular expression and URI(s)... |
| */ |
| |
| value ++; /* Skip leading " */ |
| |
| for (start = value; *value && *value != '\"'; value ++) |
| if (*value == '\\' && value[1]) |
| _cups_strcpy(value, value + 1); |
| |
| if (!*value) |
| { |
| fputs("ERROR: Missing end quote for DeviceURI!\n", stderr); |
| |
| cupsArrayDelete(device_uri->uris); |
| free(device_uri); |
| |
| return (NULL); |
| } |
| |
| *value++ = '\0'; |
| |
| if (regcomp(&(device_uri->re), start, REG_EXTENDED | REG_ICASE)) |
| { |
| fputs("ERROR: Bad regular expression for DeviceURI!\n", stderr); |
| |
| cupsArrayDelete(device_uri->uris); |
| free(device_uri); |
| |
| return (NULL); |
| } |
| |
| while (*value) |
| { |
| while (isspace(*value & 255)) |
| value ++; |
| |
| if (!*value) |
| break; |
| |
| for (start = value; *value && !isspace(*value & 255); value ++); |
| |
| if (*value) |
| *value++ = '\0'; |
| |
| cupsArrayAdd(device_uri->uris, strdup(start)); |
| } |
| |
| /* |
| * Add the device URI to the list and return it... |
| */ |
| |
| cupsArrayAdd(DeviceURIs, device_uri); |
| |
| return (device_uri); |
| } |
| |
| |
| /* |
| * 'alarm_handler()' - Handle alarm signals... |
| */ |
| |
| static void |
| alarm_handler(int sig) /* I - Signal number */ |
| { |
| /* |
| * Do nothing... |
| */ |
| |
| (void)sig; |
| |
| #if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION) |
| signal(SIGALRM, alarm_handler); |
| #endif /* !HAVE_SIGSET && !HAVE_SIGACTION */ |
| |
| if (DebugLevel) |
| write(2, "DEBUG: ALARM!\n", 14); |
| } |
| |
| |
| /* |
| * 'compare_cache()' - Compare two cache entries. |
| */ |
| |
| static int /* O - Result of comparison */ |
| compare_cache(snmp_cache_t *a, /* I - First cache entry */ |
| snmp_cache_t *b) /* I - Second cache entry */ |
| { |
| return (_cups_strcasecmp(a->addrname, b->addrname)); |
| } |
| |
| |
| /* |
| * 'debug_printf()' - Display some debugging information. |
| */ |
| |
| static void |
| debug_printf(const char *format, /* I - Printf-style format string */ |
| ...) /* I - Additional arguments as needed */ |
| { |
| va_list ap; /* Pointer to arguments */ |
| |
| |
| if (!DebugLevel) |
| return; |
| |
| va_start(ap, format); |
| vfprintf(stderr, format, ap); |
| va_end(ap); |
| } |
| |
| |
| /* |
| * 'fix_make_model()' - Fix common problems in the make-and-model string. |
| */ |
| |
| static void |
| fix_make_model( |
| char *make_model, /* I - New make-and-model string */ |
| const char *old_make_model, /* I - Old make-and-model string */ |
| int make_model_size) /* I - Size of new string buffer */ |
| { |
| char *mmptr; /* Pointer into make-and-model string */ |
| |
| |
| /* |
| * Fix some common problems with the make-and-model string so |
| * that printer driver detection works better... |
| */ |
| |
| if (!_cups_strncasecmp(old_make_model, "Hewlett-Packard", 15)) |
| { |
| /* |
| * Strip leading Hewlett-Packard and hp prefixes and replace |
| * with a single HP manufacturer prefix... |
| */ |
| |
| mmptr = (char *)old_make_model + 15; |
| |
| while (isspace(*mmptr & 255)) |
| mmptr ++; |
| |
| if (!_cups_strncasecmp(mmptr, "hp", 2)) |
| { |
| mmptr += 2; |
| |
| while (isspace(*mmptr & 255)) |
| mmptr ++; |
| } |
| |
| make_model[0] = 'H'; |
| make_model[1] = 'P'; |
| make_model[2] = ' '; |
| strlcpy(make_model + 3, mmptr, (size_t)make_model_size - 3); |
| } |
| else if (!_cups_strncasecmp(old_make_model, "deskjet", 7)) |
| snprintf(make_model, (size_t)make_model_size, "HP DeskJet%s", old_make_model + 7); |
| else if (!_cups_strncasecmp(old_make_model, "officejet", 9)) |
| snprintf(make_model, (size_t)make_model_size, "HP OfficeJet%s", old_make_model + 9); |
| else if (!_cups_strncasecmp(old_make_model, "stylus_pro_", 11)) |
| snprintf(make_model, (size_t)make_model_size, "EPSON Stylus Pro %s", old_make_model + 11); |
| else |
| strlcpy(make_model, old_make_model, (size_t)make_model_size); |
| |
| if ((mmptr = strstr(make_model, ", Inc.,")) != NULL) |
| { |
| /* |
| * Strip inc. from name, e.g. "Tektronix, Inc., Phaser 560" |
| * becomes "Tektronix Phaser 560"... |
| */ |
| |
| _cups_strcpy(mmptr, mmptr + 7); |
| } |
| |
| if ((mmptr = strstr(make_model, " Network")) != NULL) |
| { |
| /* |
| * Drop unnecessary informational text, e.g. "Xerox DocuPrint N2025 |
| * Network LaserJet - 2.12" becomes "Xerox DocuPrint N2025"... |
| */ |
| |
| *mmptr = '\0'; |
| } |
| |
| if ((mmptr = strchr(make_model, ',')) != NULL) |
| { |
| /* |
| * Drop anything after a trailing comma... |
| */ |
| |
| *mmptr = '\0'; |
| } |
| } |
| |
| |
| /* |
| * 'free_array()' - Free an array of strings. |
| */ |
| |
| static void |
| free_array(cups_array_t *a) /* I - Array */ |
| { |
| char *s; /* Current string */ |
| |
| |
| for (s = (char *)cupsArrayFirst(a); s; s = (char *)cupsArrayNext(a)) |
| free(s); |
| |
| cupsArrayDelete(a); |
| } |
| |
| |
| /* |
| * 'free_cache()' - Free the array of cached devices. |
| */ |
| |
| static void |
| free_cache(void) |
| { |
| snmp_cache_t *cache; /* Cached device */ |
| |
| |
| for (cache = (snmp_cache_t *)cupsArrayFirst(Devices); |
| cache; |
| cache = (snmp_cache_t *)cupsArrayNext(Devices)) |
| { |
| free(cache->addrname); |
| |
| if (cache->uri) |
| free(cache->uri); |
| |
| if (cache->id) |
| free(cache->id); |
| |
| if (cache->make_and_model) |
| free(cache->make_and_model); |
| |
| free(cache); |
| } |
| |
| cupsArrayDelete(Devices); |
| Devices = NULL; |
| } |
| |
| |
| /* |
| * 'get_interface_addresses()' - Get the broadcast address(es) associated |
| * with an interface. |
| */ |
| |
| static http_addrlist_t * /* O - List of addresses */ |
| get_interface_addresses( |
| const char *ifname) /* I - Interface name */ |
| { |
| struct ifaddrs *addrs, /* Interface address list */ |
| *addr; /* Current interface address */ |
| http_addrlist_t *first, /* First address in list */ |
| *last, /* Last address in list */ |
| *current; /* Current address */ |
| |
| |
| if (getifaddrs(&addrs) < 0) |
| return (NULL); |
| |
| for (addr = addrs, first = NULL, last = NULL; addr; addr = addr->ifa_next) |
| if ((addr->ifa_flags & IFF_BROADCAST) && addr->ifa_broadaddr && |
| addr->ifa_broadaddr->sa_family == AF_INET && |
| (!ifname || !strcmp(ifname, addr->ifa_name))) |
| { |
| current = calloc(1, sizeof(http_addrlist_t)); |
| |
| memcpy(&(current->addr), addr->ifa_broadaddr, |
| sizeof(struct sockaddr_in)); |
| |
| if (!last) |
| first = current; |
| else |
| last->next = current; |
| |
| last = current; |
| } |
| |
| freeifaddrs(addrs); |
| |
| return (first); |
| } |
| |
| |
| /* |
| * 'list_device()' - List a device we found... |
| */ |
| |
| static void |
| list_device(snmp_cache_t *cache) /* I - Cached device */ |
| { |
| if (cache->uri) |
| cupsBackendReport("network", cache->uri, cache->make_and_model, |
| cache->info, cache->id, cache->location); |
| } |
| |
| |
| /* |
| * 'password_cb()' - Handle authentication requests. |
| * |
| * All we do right now is return NULL, indicating that no authentication |
| * is possible. |
| */ |
| |
| static const char * /* O - Password (NULL) */ |
| password_cb(const char *prompt) /* I - Prompt message */ |
| { |
| (void)prompt; /* Anti-compiler-warning-code */ |
| |
| return (NULL); |
| } |
| |
| |
| /* |
| * 'probe_device()' - Probe a device to discover whether it is a printer. |
| * |
| * TODO: Try using the Port Monitor MIB to discover the correct protocol |
| * to use - first need a commercially-available printer that supports |
| * it, though... |
| */ |
| |
| static void |
| probe_device(snmp_cache_t *device) /* I - Device */ |
| { |
| char uri[1024], /* Full device URI */ |
| *uriptr, /* Pointer into URI */ |
| *format; /* Format string for device */ |
| device_uri_t *device_uri; /* Current DeviceURI match */ |
| |
| |
| debug_printf("DEBUG: %.3f Probing %s...\n", run_time(), device->addrname); |
| |
| #ifdef __APPLE__ |
| /* |
| * If the printer supports Bonjour/mDNS, don't report it from the SNMP backend. |
| */ |
| |
| if (!try_connect(&(device->address), device->addrname, 5353)) |
| { |
| debug_printf("DEBUG: %s supports mDNS, not reporting!\n", device->addrname); |
| return; |
| } |
| #endif /* __APPLE__ */ |
| |
| /* |
| * Lookup the device in the match table... |
| */ |
| |
| for (device_uri = (device_uri_t *)cupsArrayFirst(DeviceURIs); |
| device_uri; |
| device_uri = (device_uri_t *)cupsArrayNext(DeviceURIs)) |
| if (device->make_and_model && |
| !regexec(&(device_uri->re), device->make_and_model, 0, NULL, 0)) |
| { |
| /* |
| * Found a match, add the URIs... |
| */ |
| |
| for (format = (char *)cupsArrayFirst(device_uri->uris); |
| format; |
| format = (char *)cupsArrayNext(device_uri->uris)) |
| { |
| for (uriptr = uri; *format && uriptr < (uri + sizeof(uri) - 1);) |
| if (*format == '%' && format[1] == 's') |
| { |
| /* |
| * Insert hostname/address... |
| */ |
| |
| strlcpy(uriptr, device->addrname, sizeof(uri) - (size_t)(uriptr - uri)); |
| uriptr += strlen(uriptr); |
| format += 2; |
| } |
| else |
| *uriptr++ = *format++; |
| |
| *uriptr = '\0'; |
| |
| update_cache(device, uri, NULL, NULL); |
| } |
| |
| return; |
| } |
| |
| /* |
| * Then try the standard ports... |
| */ |
| |
| if (!try_connect(&(device->address), device->addrname, 9100)) |
| { |
| debug_printf("DEBUG: %s supports AppSocket!\n", device->addrname); |
| |
| snprintf(uri, sizeof(uri), "socket://%s", device->addrname); |
| update_cache(device, uri, NULL, NULL); |
| } |
| else if (!try_connect(&(device->address), device->addrname, 515)) |
| { |
| debug_printf("DEBUG: %s supports LPD!\n", device->addrname); |
| |
| snprintf(uri, sizeof(uri), "lpd://%s/", device->addrname); |
| update_cache(device, uri, NULL, NULL); |
| } |
| } |
| |
| |
| /* |
| * 'read_snmp_conf()' - Read the snmp.conf file. |
| */ |
| |
| static void |
| read_snmp_conf(const char *address) /* I - Single address to probe */ |
| { |
| cups_file_t *fp; /* File pointer */ |
| char filename[1024], /* Filename */ |
| line[1024], /* Line from file */ |
| *value; /* Value on line */ |
| int linenum; /* Line number */ |
| const char *cups_serverroot; /* CUPS_SERVERROOT env var */ |
| const char *debug; /* CUPS_DEBUG_LEVEL env var */ |
| const char *runtime; /* CUPS_MAX_RUN_TIME env var */ |
| |
| |
| /* |
| * Initialize the global address and community lists... |
| */ |
| |
| Addresses = cupsArrayNew(NULL, NULL); |
| Communities = cupsArrayNew(NULL, NULL); |
| |
| if (address) |
| add_array(Addresses, address); |
| |
| if ((debug = getenv("CUPS_DEBUG_LEVEL")) != NULL) |
| DebugLevel = atoi(debug); |
| |
| if ((runtime = getenv("CUPS_MAX_RUN_TIME")) != NULL) |
| MaxRunTime = atoi(runtime); |
| |
| /* |
| * Find the snmp.conf file... |
| */ |
| |
| if ((cups_serverroot = getenv("CUPS_SERVERROOT")) == NULL) |
| cups_serverroot = CUPS_SERVERROOT; |
| |
| snprintf(filename, sizeof(filename), "%s/snmp.conf", cups_serverroot); |
| |
| if ((fp = cupsFileOpen(filename, "r")) != NULL) |
| { |
| /* |
| * Read the snmp.conf file... |
| */ |
| |
| linenum = 0; |
| |
| while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) |
| { |
| if (!value) |
| fprintf(stderr, "ERROR: Missing value on line %d of %s!\n", linenum, |
| filename); |
| else if (!_cups_strcasecmp(line, "Address")) |
| { |
| if (!address) |
| add_array(Addresses, value); |
| } |
| else if (!_cups_strcasecmp(line, "Community")) |
| add_array(Communities, value); |
| else if (!_cups_strcasecmp(line, "DebugLevel")) |
| DebugLevel = atoi(value); |
| else if (!_cups_strcasecmp(line, "DeviceURI")) |
| { |
| if (*value != '\"') |
| fprintf(stderr, |
| "ERROR: Missing double quote for regular expression on " |
| "line %d of %s!\n", linenum, filename); |
| else |
| add_device_uri(value); |
| } |
| else if (!_cups_strcasecmp(line, "HostNameLookups")) |
| HostNameLookups = !_cups_strcasecmp(value, "on") || |
| !_cups_strcasecmp(value, "yes") || |
| !_cups_strcasecmp(value, "true") || |
| !_cups_strcasecmp(value, "double"); |
| else if (!_cups_strcasecmp(line, "MaxRunTime")) |
| MaxRunTime = atoi(value); |
| else |
| fprintf(stderr, "ERROR: Unknown directive %s on line %d of %s!\n", |
| line, linenum, filename); |
| } |
| |
| cupsFileClose(fp); |
| } |
| |
| /* |
| * Use defaults if parameters are undefined... |
| */ |
| |
| if (cupsArrayCount(Addresses) == 0) |
| { |
| /* |
| * If we have no addresses, exit immediately... |
| */ |
| |
| fprintf(stderr, |
| "DEBUG: No address specified and no Address line in %s...\n", |
| filename); |
| exit(0); |
| } |
| |
| if (cupsArrayCount(Communities) == 0) |
| { |
| fputs("INFO: Using default SNMP Community public\n", stderr); |
| add_array(Communities, "public"); |
| } |
| } |
| |
| |
| /* |
| * 'read_snmp_response()' - Read and parse a SNMP response... |
| */ |
| |
| static void |
| read_snmp_response(int fd) /* I - SNMP socket file descriptor */ |
| { |
| char addrname[256]; /* Source address name */ |
| cups_snmp_t packet; /* Decoded packet */ |
| snmp_cache_t key, /* Search key */ |
| *device; /* Matching device */ |
| |
| |
| /* |
| * Read the response data... |
| */ |
| |
| if (!_cupsSNMPRead(fd, &packet, -1.0)) |
| { |
| fprintf(stderr, "ERROR: Unable to read data from socket: %s\n", |
| strerror(errno)); |
| return; |
| } |
| |
| if (HostNameLookups) |
| httpAddrLookup(&(packet.address), addrname, sizeof(addrname)); |
| else |
| httpAddrString(&(packet.address), addrname, sizeof(addrname)); |
| |
| debug_printf("DEBUG: %.3f Received data from %s...\n", run_time(), addrname); |
| |
| /* |
| * Look for the response status code in the SNMP message header... |
| */ |
| |
| if (packet.error) |
| { |
| fprintf(stderr, "ERROR: Bad SNMP packet from %s: %s\n", addrname, |
| packet.error); |
| |
| return; |
| } |
| |
| debug_printf("DEBUG: community=\"%s\"\n", packet.community); |
| debug_printf("DEBUG: request-id=%d\n", packet.request_id); |
| debug_printf("DEBUG: error-status=%d\n", packet.error_status); |
| |
| if (packet.error_status && packet.request_id != DEVICE_TYPE) |
| return; |
| |
| /* |
| * Find a matching device in the cache... |
| */ |
| |
| key.addrname = addrname; |
| device = (snmp_cache_t *)cupsArrayFind(Devices, &key); |
| |
| /* |
| * Process the message... |
| */ |
| |
| switch (packet.request_id) |
| { |
| case DEVICE_TYPE : |
| /* |
| * Got the device type response... |
| */ |
| |
| if (device) |
| { |
| debug_printf("DEBUG: Discarding duplicate device type for \"%s\"...\n", |
| addrname); |
| return; |
| } |
| |
| /* |
| * Add the device and request the device data... |
| */ |
| |
| add_cache(&(packet.address), addrname, NULL, NULL, NULL); |
| |
| _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1, |
| packet.community, CUPS_ASN1_GET_REQUEST, |
| DEVICE_DESCRIPTION, DescriptionOID); |
| _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1, |
| packet.community, CUPS_ASN1_GET_REQUEST, |
| DEVICE_ID, DeviceIdOID); |
| _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1, |
| packet.community, CUPS_ASN1_GET_REQUEST, |
| DEVICE_URI, UriOID); |
| _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1, |
| packet.community, CUPS_ASN1_GET_REQUEST, |
| DEVICE_LOCATION, LocationOID); |
| _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1, |
| packet.community, CUPS_ASN1_GET_REQUEST, |
| DEVICE_PRODUCT, LexmarkProductOID); |
| _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1, |
| packet.community, CUPS_ASN1_GET_REQUEST, |
| DEVICE_PRODUCT, LexmarkProductOID2); |
| _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1, |
| packet.community, CUPS_ASN1_GET_REQUEST, |
| DEVICE_ID, LexmarkDeviceIdOID); |
| _cupsSNMPWrite(fd, &(packet.address), CUPS_SNMP_VERSION_1, |
| packet.community, CUPS_ASN1_GET_REQUEST, |
| DEVICE_PRODUCT, XeroxProductOID); |
| break; |
| |
| case DEVICE_DESCRIPTION : |
| if (device && packet.object_type == CUPS_ASN1_OCTET_STRING) |
| { |
| /* |
| * Update an existing cache entry... |
| */ |
| |
| char make_model[256]; /* Make and model */ |
| |
| |
| if (strchr((char *)packet.object_value.string.bytes, ':') && |
| strchr((char *)packet.object_value.string.bytes, ';')) |
| { |
| /* |
| * Description is the IEEE-1284 device ID... |
| */ |
| |
| char *ptr; /* Pointer into device ID */ |
| |
| for (ptr = (char *)packet.object_value.string.bytes; *ptr; ptr ++) |
| if (*ptr == '\n') |
| *ptr = ';'; /* A lot of bad printers put a newline */ |
| if (!device->id) |
| device->id = strdup((char *)packet.object_value.string.bytes); |
| |
| backendGetMakeModel((char *)packet.object_value.string.bytes, |
| make_model, sizeof(make_model)); |
| |
| if (device->info) |
| free(device->info); |
| |
| device->info = strdup(make_model); |
| } |
| else |
| { |
| /* |
| * Description is plain text... |
| */ |
| |
| fix_make_model(make_model, (char *)packet.object_value.string.bytes, |
| sizeof(make_model)); |
| |
| if (device->info) |
| free(device->info); |
| |
| device->info = strdup((char *)packet.object_value.string.bytes); |
| } |
| |
| if (!device->make_and_model) |
| device->make_and_model = strdup(make_model); |
| } |
| break; |
| |
| case DEVICE_ID : |
| if (device && packet.object_type == CUPS_ASN1_OCTET_STRING && |
| (!device->id || |
| strlen(device->id) < packet.object_value.string.num_bytes)) |
| { |
| /* |
| * Update an existing cache entry... |
| */ |
| |
| char make_model[256]; /* Make and model */ |
| char *ptr; /* Pointer into device ID */ |
| |
| for (ptr = (char *)packet.object_value.string.bytes; *ptr; ptr ++) |
| if (*ptr == '\n') |
| *ptr = ';'; /* A lot of bad printers put a newline */ |
| if (device->id) |
| free(device->id); |
| |
| device->id = strdup((char *)packet.object_value.string.bytes); |
| |
| /* |
| * Convert the ID to a make and model string... |
| */ |
| |
| backendGetMakeModel((char *)packet.object_value.string.bytes, |
| make_model, sizeof(make_model)); |
| if (device->make_and_model) |
| free(device->make_and_model); |
| |
| device->make_and_model = strdup(make_model); |
| } |
| break; |
| |
| case DEVICE_LOCATION : |
| if (device && packet.object_type == CUPS_ASN1_OCTET_STRING && |
| !device->location) |
| device->location = strdup((char *)packet.object_value.string.bytes); |
| break; |
| |
| case DEVICE_PRODUCT : |
| if (device && packet.object_type == CUPS_ASN1_OCTET_STRING && |
| !device->id) |
| { |
| /* |
| * Update an existing cache entry... |
| */ |
| |
| if (!device->info) |
| device->info = strdup((char *)packet.object_value.string.bytes); |
| |
| if (device->make_and_model) |
| free(device->make_and_model); |
| |
| device->make_and_model = strdup((char *)packet.object_value.string.bytes); |
| } |
| break; |
| |
| case DEVICE_URI : |
| if (device && packet.object_type == CUPS_ASN1_OCTET_STRING && |
| !device->uri && packet.object_value.string.num_bytes > 3) |
| { |
| /* |
| * Update an existing cache entry... |
| */ |
| |
| char scheme[32], /* URI scheme */ |
| userpass[256], /* Username:password in URI */ |
| hostname[256], /* Hostname in URI */ |
| resource[1024]; /* Resource path in URI */ |
| int port; /* Port number in URI */ |
| |
| if (!strncmp((char *)packet.object_value.string.bytes, "lpr:", 4)) |
| { |
| /* |
| * We want "lpd://..." for the URI... |
| */ |
| |
| packet.object_value.string.bytes[2] = 'd'; |
| } |
| |
| if (httpSeparateURI(HTTP_URI_CODING_ALL, |
| (char *)packet.object_value.string.bytes, |
| scheme, sizeof(scheme), |
| userpass, sizeof(userpass), |
| hostname, sizeof(hostname), &port, |
| resource, sizeof(resource)) >= HTTP_URI_OK) |
| device->uri = strdup((char *)packet.object_value.string.bytes); |
| } |
| break; |
| } |
| } |
| |
| |
| /* |
| * 'run_time()' - Return the total running time... |
| */ |
| |
| static double /* O - Number of seconds */ |
| run_time(void) |
| { |
| struct timeval curtime; /* Current time */ |
| |
| |
| gettimeofday(&curtime, NULL); |
| |
| return (curtime.tv_sec - StartTime.tv_sec + |
| 0.000001 * (curtime.tv_usec - StartTime.tv_usec)); |
| } |
| |
| |
| /* |
| * 'scan_devices()' - Scan for devices using SNMP. |
| */ |
| |
| static void |
| scan_devices(int ipv4, /* I - SNMP IPv4 socket */ |
| int ipv6) /* I - SNMP IPv6 socket */ |
| { |
| int fd, /* File descriptor for this address */ |
| busy; /* Are we busy processing something? */ |
| char *address, /* Current address */ |
| *community; /* Current community */ |
| fd_set input; /* Input set for select() */ |
| struct timeval timeout; /* Timeout for select() */ |
| time_t endtime; /* End time for scan */ |
| http_addrlist_t *addrs, /* List of addresses */ |
| *addr; /* Current address */ |
| snmp_cache_t *device; /* Current device */ |
| char temp[1024]; /* Temporary address string */ |
| |
| |
| gettimeofday(&StartTime, NULL); |
| |
| /* |
| * First send all of the broadcast queries... |
| */ |
| |
| for (address = (char *)cupsArrayFirst(Addresses); |
| address; |
| address = (char *)cupsArrayNext(Addresses)) |
| { |
| if (!strcmp(address, "@LOCAL")) |
| addrs = get_interface_addresses(NULL); |
| else if (!strncmp(address, "@IF(", 4)) |
| { |
| char ifname[255]; /* Interface name */ |
| |
| strlcpy(ifname, address + 4, sizeof(ifname)); |
| if (ifname[0]) |
| ifname[strlen(ifname) - 1] = '\0'; |
| |
| addrs = get_interface_addresses(ifname); |
| } |
| else |
| addrs = httpAddrGetList(address, AF_UNSPEC, NULL); |
| |
| if (!addrs) |
| { |
| fprintf(stderr, "ERROR: Unable to scan \"%s\"!\n", address); |
| continue; |
| } |
| |
| for (community = (char *)cupsArrayFirst(Communities); |
| community; |
| community = (char *)cupsArrayNext(Communities)) |
| { |
| debug_printf("DEBUG: Scanning for devices in \"%s\" via \"%s\"...\n", |
| community, address); |
| |
| for (addr = addrs; addr; addr = addr->next) |
| { |
| #ifdef AF_INET6 |
| if (httpAddrFamily(&(addr->addr)) == AF_INET6) |
| fd = ipv6; |
| else |
| #endif /* AF_INET6 */ |
| fd = ipv4; |
| |
| debug_printf("DEBUG: Sending get request to %s...\n", |
| httpAddrString(&(addr->addr), temp, sizeof(temp))); |
| |
| _cupsSNMPWrite(fd, &(addr->addr), CUPS_SNMP_VERSION_1, community, |
| CUPS_ASN1_GET_REQUEST, DEVICE_TYPE, DeviceTypeOID); |
| } |
| } |
| |
| httpAddrFreeList(addrs); |
| } |
| |
| /* |
| * Then read any responses that come in over the next 3 seconds... |
| */ |
| |
| endtime = time(NULL) + MaxRunTime; |
| |
| FD_ZERO(&input); |
| |
| while (time(NULL) < endtime) |
| { |
| timeout.tv_sec = 2; |
| timeout.tv_usec = 0; |
| |
| FD_SET(ipv4, &input); |
| if (ipv6 >= 0) |
| FD_SET(ipv6, &input); |
| |
| fd = ipv4 > ipv6 ? ipv4 : ipv6; |
| if (select(fd + 1, &input, NULL, NULL, &timeout) < 0) |
| { |
| fprintf(stderr, "ERROR: %.3f select() for %d/%d failed: %s\n", run_time(), |
| ipv4, ipv6, strerror(errno)); |
| break; |
| } |
| |
| busy = 0; |
| |
| if (FD_ISSET(ipv4, &input)) |
| { |
| read_snmp_response(ipv4); |
| busy = 1; |
| } |
| |
| if (ipv6 >= 0 && FD_ISSET(ipv6, &input)) |
| { |
| read_snmp_response(ipv6); |
| busy = 1; |
| } |
| |
| if (!busy) |
| { |
| /* |
| * List devices with complete information... |
| */ |
| |
| int sent_something = 0; |
| |
| for (device = (snmp_cache_t *)cupsArrayFirst(Devices); |
| device; |
| device = (snmp_cache_t *)cupsArrayNext(Devices)) |
| if (!device->sent && device->info && device->make_and_model) |
| { |
| if (device->uri) |
| list_device(device); |
| else |
| probe_device(device); |
| |
| device->sent = sent_something = 1; |
| } |
| |
| if (!sent_something) |
| break; |
| } |
| } |
| |
| debug_printf("DEBUG: %.3f Scan complete!\n", run_time()); |
| } |
| |
| |
| /* |
| * 'try_connect()' - Try connecting on a port... |
| */ |
| |
| static int /* O - 0 on success or -1 on error */ |
| try_connect(http_addr_t *addr, /* I - Socket address */ |
| const char *addrname, /* I - Hostname or IP address */ |
| int port) /* I - Port number */ |
| { |
| int fd; /* Socket */ |
| int status; /* Connection status */ |
| |
| |
| debug_printf("DEBUG: %.3f Trying %s://%s:%d...\n", run_time(), |
| port == 515 ? "lpd" : "socket", addrname, port); |
| |
| if ((fd = socket(httpAddrFamily(addr), SOCK_STREAM, 0)) < 0) |
| { |
| fprintf(stderr, "ERROR: Unable to create socket: %s\n", |
| strerror(errno)); |
| return (-1); |
| } |
| |
| _httpAddrSetPort(addr, port); |
| |
| alarm(1); |
| |
| status = connect(fd, (void *)addr, (socklen_t)httpAddrLength(addr)); |
| |
| close(fd); |
| alarm(0); |
| |
| return (status); |
| } |
| |
| |
| /* |
| * 'update_cache()' - Update a cached device... |
| */ |
| |
| static void |
| update_cache(snmp_cache_t *device, /* I - Device */ |
| const char *uri, /* I - Device URI */ |
| const char *id, /* I - Device ID */ |
| const char *make_model) /* I - Device make and model */ |
| { |
| if (device->uri) |
| free(device->uri); |
| |
| device->uri = strdup(uri); |
| |
| if (id) |
| { |
| if (device->id) |
| free(device->id); |
| |
| device->id = strdup(id); |
| } |
| |
| if (make_model) |
| { |
| if (device->make_and_model) |
| free(device->make_and_model); |
| |
| device->make_and_model = strdup(make_model); |
| } |
| |
| list_device(device); |
| } |