You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1357 lines
32 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
* 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);
}