/*
- * "$Id: snmp.c 6180 2007-01-03 18:19:32Z mike $"
+ * "$Id: snmp.c 6649 2007-07-11 21:46:42Z mike $"
*
* SNMP discovery backend for the Common UNIX Printing System (CUPS).
*
- * Copyright 2006 by Easy Software Products, all rights reserved.
+ * Copyright 2007 by Apple Inc.
+ * Copyright 2006-2007 by Easy Software Products, all rights reserved.
*
* These coded instructions, statements, and computer programs are the
- * property of Easy Software Products and are protected by Federal
- * copyright law. Distribution and use rights are outlined in the file
+ * property of Apple Inc. and are protected by Federal copyright
+ * law. Distribution and use rights are outlined in the file "LICENSE.txt"
* "LICENSE" which should have been included with this file. If this
- * file is missing or damaged please contact Easy Software Products
- * at:
- *
- * Attn: CUPS Licensing Information
- * Easy Software Products
- * 44141 Airport View Drive, Suite 204
- * Hollywood, Maryland 20636 USA
- *
- * Voice: (301) 373-9600
- * EMail: cups-info@cups.org
- * WWW: http://www.cups.org
+ * file is missing or damaged, see the license at "http://www.cups.org/".
*
* This file is subject to the Apple OS-Developed Software exception.
*
* main() - Discover printers via SNMP.
* add_array() - Add a string to an array.
* add_cache() - Add a cached device...
+ * add_device_uri() - Add a device URI to the cache.
* alarm_handler() - Handle alarm signals...
* asn1_decode_snmp() - Decode a SNMP packet.
* asn1_debug() - Decode an ASN1-encoded message.
* packed integer value.
* compare_cache() - Compare two cache entries.
* debug_printf() - Display some debugging information.
- * do_request() - Do a non-blocking IPP request.
* fix_make_model() - Fix common problems in the make-and-model
* string.
* free_array() - Free an array of strings.
#include "backend-private.h"
#include <cups/array.h>
#include <cups/file.h>
+#include <regex.h>
/*
* This backend implements SNMP printer discovery. It uses a broadcast-
- * based approach to get SNMP response packets from potential printers
- * and then interrogates each responder by trying to connect on port
- * 631, 9100, and 515.
+ * based approach to get SNMP response packets from potential printers,
+ * tries a mDNS lookup (Mac OS X only at present), 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. Future
* Address @IF(name)
* Community name
* DebugLevel N
+ * DeviceURI "regex pattern" uri
* HostNameLookups on
* HostNameLookups off
+ * MaxRunTime N
*
* The default is to use:
*
* Community public
* DebugLevel 0
* HostNameLookups off
+ * MaxRunTime 10
*
* This backend is known to work with the following network printers and
* print servers:
* Types...
*/
+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 */
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 asn1_decode_snmp(unsigned char *buffer, size_t len,
snmp_packet_t *packet);
static int asn1_size_packed(int integer);
static int compare_cache(snmp_cache_t *a, snmp_cache_t *b);
static void debug_printf(const char *format, ...);
-static ipp_t *do_request(http_t *http, ipp_t *request,
- const char *resource);
static void fix_make_model(char *make_model,
const char *old_make_model,
int make_model_size);
static cups_array_t *Communities = NULL;
static cups_array_t *Devices = NULL;
static int DebugLevel = 0;
-static int DeviceTypeOID[] = { 1, 3, 6, 1, 2, 1, 25, 3,
- 2, 1, 2, 1, 0 };
static int DeviceDescOID[] = { 1, 3, 6, 1, 2, 1, 25, 3,
2, 1, 3, 1, 0 };
-static unsigned DeviceTypeRequest;
static unsigned DeviceDescRequest;
+static int DeviceTypeOID[] = { 1, 3, 6, 1, 2, 1, 25, 3,
+ 2, 1, 2, 1, 0 };
+static unsigned DeviceTypeRequest;
+static cups_array_t *DeviceURIs = NULL;
static int HostNameLookups = 0;
static int MaxRunTime = 10;
static struct timeval StartTime;
if (argc > 2)
{
- fputs("Usage: snmp [host-or-ip-address]\n", stderr);
+ fputs(_("Usage: snmp [host-or-ip-address]\n"), stderr);
return (1);
}
}
+/*
+ * '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...
*/
}
-/*
- * 'do_request()' - Do a non-blocking IPP request.
- */
-
-static ipp_t * /* O - Response data or NULL */
-do_request(http_t *http, /* I - HTTP connection to server */
- ipp_t *request, /* I - IPP request */
- const char *resource) /* I - HTTP resource for POST */
-{
- ipp_t *response; /* IPP response data */
- http_status_t status; /* Status of HTTP request */
- ipp_state_t state; /* State of IPP processing */
-
-
- /*
- * Setup the HTTP variables needed...
- */
-
- httpClearFields(http);
- httpSetLength(http, ippLength(request));
- httpSetField(http, HTTP_FIELD_CONTENT_TYPE, "application/ipp");
-
- /*
- * Do the POST request...
- */
-
- debug_printf("DEBUG: %.3f POST %s...\n", run_time(), resource);
-
- if (httpPost(http, resource))
- {
- if (httpReconnect(http))
- {
- _cupsSetError(IPP_DEVICE_ERROR, "Unable to reconnect");
- return (NULL);
- }
- else if (httpPost(http, resource))
- {
- _cupsSetError(IPP_GONE, "Unable to POST");
- return (NULL);
- }
- }
-
- /*
- * Send the IPP data...
- */
-
- request->state = IPP_IDLE;
- status = HTTP_CONTINUE;
-
- while ((state = ippWrite(http, request)) != IPP_DATA)
- if (state == IPP_ERROR)
- {
- status = HTTP_ERROR;
- break;
- }
- else if (httpCheck(http))
- {
- if ((status = httpUpdate(http)) != HTTP_CONTINUE)
- break;
- }
-
- /*
- * Get the server's return status...
- */
-
- debug_printf("DEBUG: %.3f Getting response...\n", run_time());
-
- while (status == HTTP_CONTINUE)
- if (httpWait(http, 1000))
- status = httpUpdate(http);
- else
- {
- status = HTTP_ERROR;
- http->error = ETIMEDOUT;
- }
-
- if (status != HTTP_OK)
- {
- /*
- * Flush any error message...
- */
-
- httpFlush(http);
- response = NULL;
- }
- else
- {
- /*
- * Read the response...
- */
-
- response = ippNew();
-
- while ((state = ippRead(http, response)) != IPP_DATA)
- if (state == IPP_ERROR)
- {
- /*
- * Delete the response...
- */
-
- ippDelete(response);
- response = NULL;
-
- _cupsSetError(IPP_SERVICE_UNAVAILABLE, strerror(errno));
- break;
- }
- }
-
- /*
- * Delete the original request and return the response...
- */
-
- ippDelete(request);
-
- if (response)
- {
- ipp_attribute_t *attr; /* status-message attribute */
-
-
- attr = ippFindAttribute(response, "status-message", IPP_TAG_TEXT);
-
- _cupsSetError(response->request.status.status_code,
- attr ? attr->values[0].string.text :
- ippErrorString(response->request.status.status_code));
- }
- else if (status != HTTP_OK)
- {
- switch (status)
- {
- case HTTP_NOT_FOUND :
- _cupsSetError(IPP_NOT_FOUND, httpStatus(status));
- break;
-
- case HTTP_UNAUTHORIZED :
- _cupsSetError(IPP_NOT_AUTHORIZED, httpStatus(status));
- break;
-
- case HTTP_FORBIDDEN :
- _cupsSetError(IPP_FORBIDDEN, httpStatus(status));
- break;
-
- case HTTP_BAD_REQUEST :
- _cupsSetError(IPP_BAD_REQUEST, httpStatus(status));
- break;
-
- case HTTP_REQUEST_TOO_LARGE :
- _cupsSetError(IPP_REQUEST_VALUE, httpStatus(status));
- break;
-
- case HTTP_NOT_IMPLEMENTED :
- _cupsSetError(IPP_OPERATION_NOT_SUPPORTED, httpStatus(status));
- break;
-
- case HTTP_NOT_SUPPORTED :
- _cupsSetError(IPP_VERSION_NOT_SUPPORTED, httpStatus(status));
- break;
-
- default :
- _cupsSetError(IPP_SERVICE_UNAVAILABLE, httpStatus(status));
- break;
- }
- }
-
- return (response);
-}
-
-
/*
* 'fix_make_model()' - Fix common problems in the make-and-model string.
*/
static void
probe_device(snmp_cache_t *device) /* I - Device */
{
- int i, j; /* Looping vars */
- http_t *http; /* HTTP connection for IPP */
- char uri[1024]; /* Full device URI */
+ char uri[1024], /* Full device URI */
+ *uriptr, /* Pointer into URI */
+ *format; /* Format string for device */
+ device_uri_t *device_uri; /* Current DeviceURI match */
- /*
- * Try connecting via IPP first...
- */
-
debug_printf("DEBUG: %.3f Probing %s...\n", run_time(), device->addrname);
- if (device->make_and_model &&
- (!strncasecmp(device->make_and_model, "Epson", 5) ||
- !strncasecmp(device->make_and_model, "HP ", 3) ||
- !strncasecmp(device->make_and_model, "Hewlett", 7) ||
- !strncasecmp(device->make_and_model, "Kyocera", 7) ||
- !strncasecmp(device->make_and_model, "Lexmark", 7) ||
- !strncasecmp(device->make_and_model, "Tektronix", 9) ||
- !strncasecmp(device->make_and_model, "Xerox", 5)))
- {
- /*
- * Epson, HP, Kyocera, Lexmark, Tektronix, and Xerox printers often lock
- * up on IPP probes, so exclude them from the IPP connection test...
- */
+#ifdef __APPLE__
+ /*
+ * TODO: Try an mDNS query first, and then fallback on direct probes...
+ */
- http = NULL;
- }
- else
+ if (!try_connect(&(device->address), device->addrname, 5353))
{
- /*
- * Otherwise, try connecting for up to 1 second...
- */
-
- alarm(1);
- http = httpConnect(device->addrname, 631);
- alarm(0);
+ debug_printf("DEBUG: %s supports mDNS, not reporting!\n", device->addrname);
+ return;
}
+#endif /* __APPLE__ */
- if (http)
- {
- /*
- * IPP is supported...
- */
-
- ipp_t *request, /* IPP request */
- *response; /* IPP response */
- ipp_attribute_t *model, /* printer-make-and-model attribute */
- *info, /* printer-info attribute */
- *supported; /* printer-uri-supported attribute */
- char make_model[256],/* Make and model string to use */
- temp[256]; /* Temporary make/model string */
- int num_uris; /* Number of good URIs */
- static const char * const resources[] =
- { /* Common resource paths for IPP */
- "/ipp",
- /*"/ipp/port2",*/
- /*"/ipp/port3",*/
- /*"/EPSON_IPP_Printer",*/
- "/LPT1",
- "/LPT2",
- "/COM1",
- "/"
- };
-
-
- /*
- * Use non-blocking IO...
- */
-
- httpBlocking(http, 0);
-
- /*
- * Loop through a list of common resources that covers 99% of the
- * IPP-capable printers on the market today...
- */
+ /*
+ * Lookup the device in the match table...
+ */
- for (i = 0, num_uris = 0;
- i < (int)(sizeof(resources) / sizeof(resources[0]));
- i ++)
+ for (device_uri = (device_uri_t *)cupsArrayFirst(DeviceURIs);
+ device_uri;
+ device_uri = (device_uri_t *)cupsArrayNext(DeviceURIs))
+ if (!regexec(&(device_uri->re), device->make_and_model, 0, NULL, 0))
{
/*
- * Stop early if we are out of time...
+ * Found a match, add the URIs...
*/
- if (MaxRunTime > 0 && run_time() >= MaxRunTime)
- break;
-
- /*
- * Don't look past /ipp if we have found a working URI...
- */
-
- if (num_uris > 0 && strncmp(resources[i], "/ipp", 4))
- break;
-
- httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
- device->addrname, 631, resources[i]);
-
- request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
-
- ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
- NULL, uri);
-
- response = do_request(http, request, resources[i]);
-
- debug_printf("DEBUG: %.3f %s %s (%s)\n", run_time(), uri,
- ippErrorString(cupsLastError()), cupsLastErrorString());
-
- if (response && response->request.status.status_code == IPP_OK)
+ for (format = (char *)cupsArrayFirst(device_uri->uris);
+ format;
+ format = (char *)cupsArrayNext(device_uri->uris))
{
- model = ippFindAttribute(response, "printer-make-and-model",
- IPP_TAG_TEXT);
- info = ippFindAttribute(response, "printer-info", IPP_TAG_TEXT);
- supported = ippFindAttribute(response, "printer-uri-supported",
- IPP_TAG_URI);
-
- if (!supported)
- {
- fprintf(stderr, "ERROR: Missing printer-uri-supported from %s!\n",
- device->addrname);
-
- httpClose(http);
- return;
- }
-
- debug_printf("DEBUG: printer-info=\"%s\"\n",
- info ? info->values[0].string.text : "(null)");
- debug_printf("DEBUG: printer-make-and-model=\"%s\"\n",
- model ? model->values[0].string.text : "(null)");
-
- /*
- * Don't advertise this port if the printer actually only supports
- * a more generic version...
- */
-
- if (!strncmp(resources[i], "/ipp/", 5))
- {
- for (j = 0; j < supported->num_values; j ++)
- if (strstr(supported->values[j].string.text, "/ipp/"))
- break;
-
- if (j >= supported->num_values)
+ for (uriptr = uri; *format && uriptr < (uri + sizeof(uri) - 1);)
+ if (*format == '%' && format[1] == 's')
{
- ippDelete(response);
- break;
- }
- }
-
- /*
- * Don't use the printer-info attribute if it does not contain the
- * IEEE-1284 device ID data...
- */
-
- if (info &&
- (!strchr(info->values[0].string.text, ':') ||
- !strchr(info->values[0].string.text, ';')))
- info = NULL;
-
- /*
- * Don't use the printer-make-and-model if it contains a generic
- * string like "Ricoh IPP Printer"...
- */
-
- if (model && strstr(model->values[0].string.text, "IPP Printer"))
- model = NULL;
-
- /*
- * If we don't have a printer-make-and-model string from the printer
- * but do have the 1284 device ID string, generate a make-and-model
- * string from the device ID info...
- */
-
- if (model)
- strlcpy(temp, model->values[0].string.text, sizeof(temp));
- else if (info)
- backendGetMakeModel(info->values[0].string.text, temp, sizeof(temp));
- else
- temp[0] = '\0';
+ /*
+ * Insert hostname/address...
+ */
- fix_make_model(make_model, temp, sizeof(make_model));
-
- /*
- * Update the current device or add a new printer to the cache...
- */
+ strlcpy(uriptr, device->addrname, sizeof(uri) - (uriptr - uri));
+ uriptr += strlen(uriptr);
+ format += 2;
+ }
+ else
+ *uriptr++ = *format++;
- if (num_uris == 0)
- update_cache(device, uri,
- info ? info->values[0].string.text : NULL,
- make_model[0] ? make_model : NULL);
- else
- add_cache(&(device->address), device->addrname, uri,
- info ? info->values[0].string.text : NULL,
- make_model[0] ? make_model : NULL);
+ *uriptr = '\0';
- num_uris ++;
+ update_cache(device, uri, NULL, NULL);
}
- ippDelete(response);
-
- if (num_uris > 0 && cupsLastError() != IPP_OK)
- break;
- }
-
- httpClose(http);
-
- if (num_uris > 0)
return;
- }
+ }
/*
- * OK, now try the standard ports...
+ * Then try the standard ports...
*/
if (!try_connect(&(device->address), device->addrname, 9100))
add_array(Communities, value);
else if (!strcasecmp(line, "DebugLevel"))
DebugLevel = atoi(value);
+ else if (!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 (!strcasecmp(line, "HostNameLookups"))
HostNameLookups = !strcasecmp(value, "on") ||
!strcasecmp(value, "yes") ||
if (cupsArrayCount(Addresses) == 0)
{
- fputs("INFO: Using default SNMP Address @LOCAL\n", stderr);
- add_array(Addresses, "@LOCAL");
+ /*
+ * 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)
if (asn1_decode_snmp(buffer, bytes, &packet))
{
- fprintf(stderr, "ERROR: Bad SNMP packet from %s: %s\n", addrname,
- packet.error);
+ fprintf(stderr, "ERROR: Bad SNMP packet from %s: %s\n",
+ addrname, packet.error);
asn1_debug(buffer, bytes, 0);
hex_debug(buffer, bytes);
if (bytes < 0)
{
- fprintf(stderr, "ERROR: Unable to send SNMP query: %s\n", packet.error);
+ fprintf(stderr, "ERROR: Unable to send SNMP query: %s\n",
+ packet.error);
return;
}
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
- fprintf(stderr, "ERROR: Unable to create socket: %s\n", strerror(errno));
+ fprintf(stderr, "ERROR: Unable to create socket: %s\n",
+ strerror(errno));
return (-1);
}
/*
- * End of "$Id: snmp.c 6180 2007-01-03 18:19:32Z mike $".
+ * End of "$Id: snmp.c 6649 2007-07-11 21:46:42Z mike $".
*/