/*
 * "$Id: snmp.c 6181 2007-01-03 18:51:27Z mike $"
 *
 *   SNMP discovery backend for the Common UNIX Printing System (CUPS).
 *
 *   Copyright 2006 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
 *   "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
 *
 *   This file is subject to the Apple OS-Developed Software exception.
 *
 * Contents:
 *
 *   main()                    - Discover printers via SNMP.
 *   add_array()               - Add a string to an array.
 *   add_cache()               - Add a cached device...
 *   alarm_handler()           - Handle alarm signals...
 *   asn1_decode_snmp()        - Decode a SNMP packet.
 *   asn1_debug()              - Decode an ASN1-encoded message.
 *   asn1_encode_snmp()        - Encode a SNMP packet.
 *   asn1_get_integer()        - Get an integer value.
 *   asn1_get_length()         - Get a value length.
 *   asn1_get_oid()            - Get an OID value.
 *   asn1_get_packed()         - Get a packed integer value.
 *   asn1_get_string()         - Get a string value.
 *   asn1_get_type()           - Get a value type.
 *   asn1_set_integer()        - Set an integer value.
 *   asn1_set_length()         - Set a value length.
 *   asn1_set_oid()            - Set an OID value.
 *   asn1_set_packed()         - Set a packed integer value.
 *   asn1_size_integer()       - Figure out the number of bytes needed for an
 *                               integer value.
 *   asn1_size_length()        - Figure out the number of bytes needed for a
 *                               length value.
 *   asn1_size_oid()           - Figure out the numebr of bytes needed for an
 *                               OID value.
 *   asn1_size_packed()        - Figure out the number of bytes needed for a
 *                               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.
 *   free_cache()              - Free the array of cached devices.
 *   get_interface_addresses() - Get the broadcast address(es) associated
 *                               with an interface.
 *   hex_debug()               - Output hex debugging data...
 *   list_device()             - List a device we found...
 *   open_snmp_socket()        - Open the SNMP broadcast socket.
 *   password_cb()             - Handle authentication requests.
 *   probe_device()            - Probe a device to discover whether it is a
 *                               printer.
 *   read_snmp_conf()          - Read the snmp.conf file.
 *   read_snmp_response()      - Read and parse a SNMP response...
 *   run_time()                - Return the total running time...
 *   scan_devices()            - Scan for devices using SNMP.
 *   send_snmp_query()         - Send an SNMP query packet.
 *   try_connect()             - Try connecting on a port...
 *   update_cache()            - Update a cached device...
 */

/*
 * Include necessary headers.
 */

#include <cups/http-private.h>
#include "backend-private.h"
#include <cups/array.h>
#include <cups/file.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.
 *
 * The current focus is on printers with internal network cards, although
 * the code also works with many external print servers as well.  Future
 * versions will support scanning for vendor-specific SNMP OIDs and the
 * new PWG Port Monitor MIB and not just the Host MIB OIDs.
 *
 * 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
 *     HostNameLookups on
 *     HostNameLookups off
 *
 * The default is to use:
 *
 *     Address @LOCAL
 *     Community public
 *     DebugLevel 0
 *     HostNameLookups off
 *
 * This backend is known to work with the following network printers and
 * print servers:
 *
 *     Axis OfficeBasic, 5400, 5600
 *     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)
 */

/*
 * Constants...
 */

#define SNMP_PORT		161	/* SNMP well-known port */
#define SNMP_MAX_OID		64	/* Maximum number of OID numbers */
#define SNMP_MAX_PACKET		1472	/* Maximum size of SNMP packet */
#define SNMP_MAX_STRING		512	/* Maximum size of string */
#define SNMP_VERSION_1		0	/* SNMPv1 */

#define ASN1_END_OF_CONTENTS	0x00	/* End-of-contents */
#define ASN1_BOOLEAN		0x01	/* BOOLEAN */
#define ASN1_INTEGER		0x02	/* INTEGER or ENUMERATION */
#define ASN1_BIT_STRING		0x03	/* BIT STRING */
#define ASN1_OCTET_STRING	0x04	/* OCTET STRING */
#define ASN1_NULL_VALUE		0x05	/* NULL VALUE */
#define ASN1_OID		0x06	/* OBJECT IDENTIFIER */
#define ASN1_SEQUENCE		0x30	/* SEQUENCE */
#define ASN1_GET_REQUEST	0xa0	/* Get-Request-PDU */
#define ASN1_GET_RESPONSE	0xa2	/* Get-Response-PDU */


/*
 * Types...
 */

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 */
		*make_and_model;	/* device-make-and-model */
} snmp_cache_t;

typedef struct snmp_packet_s		/**** SNMP packet ****/
{
  const char	*error;			/* Encode/decode error */
  int		version;		/* Version number */
  char		community[SNMP_MAX_STRING];
					/* Community name */
  int		request_type;		/* Request type */
  int		request_id;		/* request-id value */
  int		error_status;		/* error-status value */
  int		error_index;		/* error-index value */
  int		object_name[SNMP_MAX_OID];
					/* object-name value */
  int		object_type;		/* object-value type */
  union
  {
    int		boolean;		/* Boolean value */
    int		integer;		/* Integer value */
    int		oid[SNMP_MAX_OID];	/* OID value */
    char	string[SNMP_MAX_STRING];/* String value */
  }		object_value;		/* object-value value */
} snmp_packet_t;


/*
 * Private CUPS API to set the last error...
 */

extern void	_cupsSetError(ipp_status_t status, const char *message);


/*
 * 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 void		alarm_handler(int sig);
static int		asn1_decode_snmp(unsigned char *buffer, size_t len,
			                 snmp_packet_t *packet);
static void		asn1_debug(unsigned char *buffer, size_t len,
			           int indent);
static int		asn1_encode_snmp(unsigned char *buffer, size_t len,
			                 snmp_packet_t *packet);
static int		asn1_get_integer(unsigned char **buffer,
			                 unsigned char *bufend,
			                 int length);
static int		asn1_get_oid(unsigned char **buffer,
			             unsigned char *bufend,
				     int length, int *oid, int oidsize);
static int		asn1_get_packed(unsigned char **buffer,
			                unsigned char *bufend);
static char		*asn1_get_string(unsigned char **buffer,
			                 unsigned char *bufend,
			                 int length, char *string,
			                 int strsize);
static int		asn1_get_length(unsigned char **buffer,
			                unsigned char *bufend);
static int		asn1_get_type(unsigned char **buffer,
			              unsigned char *bufend);
static void		asn1_set_integer(unsigned char **buffer,
			                 int integer);
static void		asn1_set_length(unsigned char **buffer,
			                int length);
static void		asn1_set_oid(unsigned char **buffer,
			             const int *oid);
static void		asn1_set_packed(unsigned char **buffer,
			                int integer);
static int		asn1_size_integer(int integer);
static int		asn1_size_length(int length);
static int		asn1_size_oid(const int *oid);
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 void		free_array(cups_array_t *a);
static void		free_cache(void);
static http_addrlist_t	*get_interface_addresses(const char *ifname);
static void		hex_debug(unsigned char *buffer, size_t len);
static void		list_device(snmp_cache_t *cache);
static int		open_snmp_socket(void);
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 fd);
static void		send_snmp_query(int fd, http_addr_t *addr, int version,
			                const char *community,
					const unsigned request_id,
					const int *oid);
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 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		HostNameLookups = 0;
static int		MaxRunTime = 10;
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		fd;			/* SNMP 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)
  {
    fputs("Usage: snmp [host-or-ip-address]\n", stderr);
    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 ((fd = open_snmp_socket()) < 0)
    return (1);

 /*
  * Read the configuration file and any cache data...
  */

  read_snmp_conf(argv[1]);

  Devices = cupsArrayNew((cups_array_func_t)compare_cache, NULL);

 /*
  * Scan for devices...
  */

  scan_devices(fd);

 /*
  * Close, free, and return with no errors...
  */

  close(fd);

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


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


/*
 * 'asn1_decode_snmp()' - Decode a SNMP packet.
 */

static int				/* O - 0 on success, -1 on error */
asn1_decode_snmp(unsigned char *buffer,	/* I - Buffer */
                 size_t        len,	/* I - Size of buffer */
                 snmp_packet_t *packet)	/* I - SNMP packet */
{
  unsigned char	*bufptr,		/* Pointer into the data */
		*bufend;		/* End of data */
  int		length;			/* Length of value */


 /*
  * Initialize the decoding...
  */

  memset(packet, 0, sizeof(snmp_packet_t));

  bufptr = buffer;
  bufend = buffer + len;

  if (asn1_get_type(&bufptr, bufend) != ASN1_SEQUENCE)
    packet->error = "Packet does not start with SEQUENCE";
  else if (asn1_get_length(&bufptr, bufend) == 0)
    packet->error = "SEQUENCE uses indefinite length";
  else if (asn1_get_type(&bufptr, bufend) != ASN1_INTEGER)
    packet->error = "No version number";
  else if ((length = asn1_get_length(&bufptr, bufend)) == 0)
    packet->error = "Version uses indefinite length";
  else if ((packet->version = asn1_get_integer(&bufptr, bufend, length))
               != SNMP_VERSION_1)
    packet->error = "Bad SNMP version number";
  else if (asn1_get_type(&bufptr, bufend) != ASN1_OCTET_STRING)
    packet->error = "No community name";
  else if ((length = asn1_get_length(&bufptr, bufend)) == 0)
    packet->error = "Community name uses indefinite length";
  else
  {
    asn1_get_string(&bufptr, bufend, length, packet->community,
                    sizeof(packet->community));

    if ((packet->request_type = asn1_get_type(&bufptr, bufend))
            != ASN1_GET_RESPONSE)
      packet->error = "Packet does not contain a Get-Response-PDU";
    else if (asn1_get_length(&bufptr, bufend) == 0)
      packet->error = "Get-Response-PDU uses indefinite length";
    else if (asn1_get_type(&bufptr, bufend) != ASN1_INTEGER)
      packet->error = "No request-id";
    else if ((length = asn1_get_length(&bufptr, bufend)) == 0)
      packet->error = "request-id uses indefinite length";
    else
    {
      packet->request_id = asn1_get_integer(&bufptr, bufend, length);

      if (asn1_get_type(&bufptr, bufend) != ASN1_INTEGER)
	packet->error = "No error-status";
      else if ((length = asn1_get_length(&bufptr, bufend)) == 0)
	packet->error = "error-status uses indefinite length";
      else
      {
	packet->error_status = asn1_get_integer(&bufptr, bufend, length);

	if (asn1_get_type(&bufptr, bufend) != ASN1_INTEGER)
	  packet->error = "No error-index";
	else if ((length = asn1_get_length(&bufptr, bufend)) == 0)
	  packet->error = "error-index uses indefinite length";
	else
	{
	  packet->error_index = asn1_get_integer(&bufptr, bufend, length);

          if (asn1_get_type(&bufptr, bufend) != ASN1_SEQUENCE)
	    packet->error = "No variable-bindings SEQUENCE";
	  else if (asn1_get_length(&bufptr, bufend) == 0)
	    packet->error = "variable-bindings uses indefinite length";
	  else if (asn1_get_type(&bufptr, bufend) != ASN1_SEQUENCE)
	    packet->error = "No VarBind SEQUENCE";
	  else if (asn1_get_length(&bufptr, bufend) == 0)
	    packet->error = "VarBind uses indefinite length";
	  else if (asn1_get_type(&bufptr, bufend) != ASN1_OID)
	    packet->error = "No name OID";
	  else if ((length = asn1_get_length(&bufptr, bufend)) == 0)
	    packet->error = "Name OID uses indefinite length";
          else
	  {
	    asn1_get_oid(&bufptr, bufend, length, packet->object_name,
	                 SNMP_MAX_OID);

            packet->object_type = asn1_get_type(&bufptr, bufend);

	    if ((length = asn1_get_length(&bufptr, bufend)) == 0 &&
	        packet->object_type != ASN1_NULL_VALUE &&
	        packet->object_type != ASN1_OCTET_STRING)
	      packet->error = "Value uses indefinite length";
	    else
	    {
	      switch (packet->object_type)
	      {
	        case ASN1_BOOLEAN :
		    packet->object_value.boolean =
		        asn1_get_integer(&bufptr, bufend, length);
	            break;

	        case ASN1_INTEGER :
		    packet->object_value.integer =
		        asn1_get_integer(&bufptr, bufend, length);
	            break;

		case ASN1_NULL_VALUE :
		    break;

	        case ASN1_OCTET_STRING :
		    asn1_get_string(&bufptr, bufend, length,
		                    packet->object_value.string,
				    SNMP_MAX_STRING);
	            break;

	        case ASN1_OID :
		    asn1_get_oid(&bufptr, bufend, length,
		                 packet->object_value.oid, SNMP_MAX_OID);
	            break;

                default :
		    packet->error = "Unsupported value type";
		    break;
	      }
	    }
          }
	}
      }
    }
  }

  return (packet->error ? -1 : 0);
}


/*
 * 'asn1_debug()' - Decode an ASN1-encoded message.
 */

static void
asn1_debug(unsigned char *buffer,	/* I - Buffer */
           size_t        len,		/* I - Length of buffer */
           int           indent)	/* I - Indentation */
{
  int		i;			/* Looping var */
  unsigned char	*bufend;		/* End of buffer */
  int		integer;		/* Number value */
  int		oid[SNMP_MAX_OID];	/* OID value */
  char		string[SNMP_MAX_STRING];/* String value */
  unsigned char	value_type;		/* Type of value */
  int		value_length;		/* Length of value */


  bufend = buffer + len;

  while (buffer < bufend)
  {
   /*
    * Get value type...
    */

    value_type   = asn1_get_type(&buffer, bufend);
    value_length = asn1_get_length(&buffer, bufend);

    switch (value_type)
    {
      case ASN1_BOOLEAN :
          integer = asn1_get_integer(&buffer, bufend, value_length);

          fprintf(stderr, "DEBUG: %*sBOOLEAN %d bytes %d\n", indent, "",
	          value_length, integer);
          break;

      case ASN1_INTEGER :
          integer = asn1_get_integer(&buffer, bufend, value_length);

          fprintf(stderr, "DEBUG: %*sINTEGER %d bytes %d\n", indent, "",
	          value_length, integer);
          break;

      case ASN1_OCTET_STRING :
          fprintf(stderr, "DEBUG: %*sOCTET STRING %d bytes \"%s\"\n", indent, "",
	          value_length, asn1_get_string(&buffer, bufend,
		                                value_length, string,
						sizeof(string)));
          break;

      case ASN1_NULL_VALUE :
          fprintf(stderr, "DEBUG: %*sNULL VALUE %d bytes\n", indent, "",
	          value_length);

	  buffer += value_length;
          break;

      case ASN1_OID :
          asn1_get_oid(&buffer, bufend, value_length, oid, SNMP_MAX_OID);

          fprintf(stderr, "DEBUG: %*sOID %d bytes ", indent, "",
	          value_length);
	  for (i = 0; oid[i]; i ++)
	    fprintf(stderr, ".%d", oid[i]);
	  putc('\n', stderr);
          break;

      case ASN1_SEQUENCE :
          fprintf(stderr, "DEBUG: %*sSEQUENCE %d bytes\n", indent, "",
	          value_length);
          asn1_debug(buffer, value_length, indent + 4);

	  buffer += value_length;
          break;

      case ASN1_GET_REQUEST :
          fprintf(stderr, "DEBUG: %*sGet-Request-PDU %d bytes\n", indent, "",
	          value_length);
          asn1_debug(buffer, value_length, indent + 4);

	  buffer += value_length;
          break;

      case ASN1_GET_RESPONSE :
          fprintf(stderr, "DEBUG: %*sGet-Response-PDU %d bytes\n", indent, "",
	          value_length);
          asn1_debug(buffer, value_length, indent + 4);

	  buffer += value_length;
          break;

      default :
          fprintf(stderr, "DEBUG: %*sUNKNOWN(%x) %d bytes\n", indent, "",
	          value_type, value_length);

	  buffer += value_length;
          break;
    }
  }
}
          

/*
 * 'asn1_encode_snmp()' - Encode a SNMP packet.
 */

static int				/* O - Length on success, -1 on error */
asn1_encode_snmp(unsigned char *buffer,	/* I - Buffer */
                 size_t        bufsize,	/* I - Size of buffer */
                 snmp_packet_t *packet)	/* I - SNMP packet */
{
  unsigned char	*bufptr;		/* Pointer into buffer */
  int		total,			/* Total length */
		msglen,			/* Length of entire message */
		commlen,		/* Length of community string */
		reqlen,			/* Length of request */
		listlen,		/* Length of variable list */
		varlen,			/* Length of variable */
		namelen,		/* Length of object name OID */
		valuelen;		/* Length of object value */


 /*
  * Get the lengths of the community string, OID, and message...
  */

  namelen = asn1_size_oid(packet->object_name);

  switch (packet->object_type)
  {
    case ASN1_NULL_VALUE :
        valuelen = 0;
	break;

    case ASN1_BOOLEAN :
        valuelen = asn1_size_integer(packet->object_value.boolean);
	break;

    case ASN1_INTEGER :
        valuelen = asn1_size_integer(packet->object_value.integer);
	break;

    case ASN1_OCTET_STRING :
        valuelen = strlen(packet->object_value.string);
	break;

    case ASN1_OID :
        valuelen = asn1_size_oid(packet->object_value.oid);
	break;

    default :
        packet->error = "Unknown object type";
        return (-1);
  }

  varlen  = 1 + asn1_size_length(namelen) + namelen +
            1 + asn1_size_length(valuelen) + valuelen;
  listlen = 1 + asn1_size_length(varlen) + varlen;
  reqlen  = 2 + asn1_size_integer(packet->request_id) +
            2 + asn1_size_integer(packet->error_status) +
            2 + asn1_size_integer(packet->error_index) +
            1 + asn1_size_length(listlen) + listlen;
  commlen = strlen(packet->community);
  msglen  = 2 + asn1_size_integer(packet->version) +
            1 + asn1_size_length(commlen) + commlen +
	    1 + asn1_size_length(reqlen) + reqlen;
  total   = 1 + asn1_size_length(msglen) + msglen;

  if (total > bufsize)
  {
    packet->error = "Message too large for buffer";
    return (-1);
  }

 /*
  * Then format the message...
  */

  bufptr = buffer;

  *bufptr++ = ASN1_SEQUENCE;		/* SNMPv1 message header */
  asn1_set_length(&bufptr, msglen);

  asn1_set_integer(&bufptr, packet->version);
					/* version */

  *bufptr++ = ASN1_OCTET_STRING;	/* community */
  asn1_set_length(&bufptr, commlen);
  memcpy(bufptr, packet->community, commlen);
  bufptr += commlen;

  *bufptr++ = packet->request_type;	/* Get-Request-PDU */
  asn1_set_length(&bufptr, reqlen);

  asn1_set_integer(&bufptr, packet->request_id);

  asn1_set_integer(&bufptr, packet->error_status);

  asn1_set_integer(&bufptr, packet->error_index);

  *bufptr++ = ASN1_SEQUENCE;		/* variable-bindings */
  asn1_set_length(&bufptr, listlen);

  *bufptr++ = ASN1_SEQUENCE;		/* variable */
  asn1_set_length(&bufptr, varlen);

  asn1_set_oid(&bufptr, packet->object_name);
					/* ObjectName */

  switch (packet->object_type)
  {
    case ASN1_NULL_VALUE :
	*bufptr++ = ASN1_NULL_VALUE;	/* ObjectValue */
	*bufptr++ = 0;			/* Length */
        break;

    case ASN1_BOOLEAN :
        asn1_set_integer(&bufptr, packet->object_value.boolean);
	break;

    case ASN1_INTEGER :
        asn1_set_integer(&bufptr, packet->object_value.integer);
	break;

    case ASN1_OCTET_STRING :
        *bufptr++ = ASN1_OCTET_STRING;
	asn1_set_length(&bufptr, valuelen);
	memcpy(bufptr, packet->object_value.string, valuelen);
	bufptr += valuelen;
	break;

    case ASN1_OID :
        asn1_set_oid(&bufptr, packet->object_value.oid);
	break;
  }

  return (bufptr - buffer);
}


/*
 * 'asn1_get_integer()' - Get an integer value.
 */

static int				/* O  - Integer value */
asn1_get_integer(
    unsigned char **buffer,		/* IO - Pointer in buffer */
    unsigned char *bufend,		/* I  - End of buffer */
    int           length)		/* I  - Length of value */
{
  int	value;				/* Integer value */


  for (value = 0;
       length > 0 && *buffer < bufend;
       length --, (*buffer) ++)
    value = (value << 8) | **buffer;

  return (value);
}


/*
 * 'asn1_get_length()' - Get a value length.
 */

static int				/* O  - Length */
asn1_get_length(unsigned char **buffer,	/* IO - Pointer in buffer */
		unsigned char *bufend)	/* I  - End of buffer */
{
  int	length;				/* Length */


  length = **buffer;
  (*buffer) ++;

  if (length & 128)
    length = asn1_get_integer(buffer, bufend, length & 127);

  return (length);
}


/*
 * 'asn1_get_oid()' - Get an OID value.
 */

static int				/* O  - Last OID number */
asn1_get_oid(
    unsigned char **buffer,		/* IO - Pointer in buffer */
    unsigned char *bufend,		/* I  - End of buffer */
    int           length,		/* I  - Length of value */
    int           *oid,			/* I  - OID buffer */
    int           oidsize)		/* I  - Size of OID buffer */
{
  unsigned char	*valend;		/* End of value */
  int		*oidend;		/* End of OID buffer */
  int		number;			/* OID number */


  valend = *buffer + length;
  oidend = oid + oidsize - 1;

  if (valend > bufend)
    valend = bufend;

  number = asn1_get_packed(buffer, bufend);

  if (number < 80)
  {
    *oid++ = number / 40;
    number = number % 40;
    *oid++ = number;
  }
  else
  {
    *oid++ = 2;
    number -= 80;
    *oid++ = number;
  }

  while (*buffer < valend)
  {
    number = asn1_get_packed(buffer, bufend);

    if (oid < oidend)
      *oid++ = number;
  }

  *oid = 0;

  return (number);
}


/*
 * 'asn1_get_packed()' - Get a packed integer value.
 */

static int				/* O  - Value */
asn1_get_packed(
    unsigned char **buffer,		/* IO - Pointer in buffer */
    unsigned char *bufend)		/* I  - End of buffer */
{
  int	value;				/* Value */


  value = 0;

  while ((**buffer & 128) && *buffer < bufend)
  {
    value = (value << 7) | (**buffer & 127);
    (*buffer) ++;
  }

  if (*buffer < bufend)
  {
    value = (value << 7) | **buffer;
    (*buffer) ++;
  }

  return (value);
}


/*
 * 'asn1_get_string()' - Get a string value.
 */

static char *				/* O  - String */
asn1_get_string(
    unsigned char **buffer,		/* IO - Pointer in buffer */
    unsigned char *bufend,		/* I  - End of buffer */
    int           length,		/* I  - Value length */
    char          *string,		/* I  - String buffer */
    int           strsize)		/* I  - String buffer size */
{
  if (length < strsize)
  {
    memcpy(string, *buffer, length);
    string[length] = '\0';
  }
  else
  {
    memcpy(string, buffer, strsize - 1);
    string[strsize - 1] = '\0';
  }

  (*buffer) += length;

  return (string);
}


/*
 * 'asn1_get_type()' - Get a value type.
 */

static int				/* O  - Type */
asn1_get_type(unsigned char **buffer,	/* IO - Pointer in buffer */
	      unsigned char *bufend)	/* I  - End of buffer */
{
  int	type;				/* Type */


  type = **buffer;
  (*buffer) ++;

  if ((type & 31) == 31)
    type = asn1_get_packed(buffer, bufend);

  return (type);
}


/*
 * 'asn1_set_integer()' - Set an integer value.
 */

static void
asn1_set_integer(unsigned char **buffer,/* IO - Pointer in buffer */
                 int           integer)	/* I  - Integer value */
{
  **buffer = ASN1_INTEGER;
  (*buffer) ++;

  if (integer > 0x7fffff || integer < -0x800000)
  {
    **buffer = 4;
    (*buffer) ++;
    **buffer = integer >> 24;
    (*buffer) ++;
    **buffer = integer >> 16;
    (*buffer) ++;
    **buffer = integer >> 8;
    (*buffer) ++;
    **buffer = integer;
    (*buffer) ++;
  }
  else if (integer > 0x7fff || integer < -0x8000)
  {
    **buffer = 3;
    (*buffer) ++;
    **buffer = integer >> 16;
    (*buffer) ++;
    **buffer = integer >> 8;
    (*buffer) ++;
    **buffer = integer;
    (*buffer) ++;
  }
  else if (integer > 0x7f || integer < -0x80)
  {
    **buffer = 2;
    (*buffer) ++;
    **buffer = integer >> 8;
    (*buffer) ++;
    **buffer = integer;
    (*buffer) ++;
  }
  else
  {
    **buffer = 1;
    (*buffer) ++;
    **buffer = integer;
    (*buffer) ++;
  }
}


/*
 * 'asn1_set_length()' - Set a value length.
 */

static void
asn1_set_length(unsigned char **buffer,	/* IO - Pointer in buffer */
		int           length)	/* I  - Length value */
{
  if (length > 255)
  {
    **buffer = 0x82;			/* 2-byte length */
    (*buffer) ++;
    **buffer = length >> 8;
    (*buffer) ++;
    **buffer = length;
    (*buffer) ++;
  }
  else if (length > 127)
  {
    **buffer = 0x81;			/* 1-byte length */
    (*buffer) ++;
    **buffer = length;
    (*buffer) ++;
  }
  else
  {
    **buffer = length;			/* Length */
    (*buffer) ++;
  }
}


/*
 * 'asn1_set_oid()' - Set an OID value.
 */

static void
asn1_set_oid(unsigned char **buffer,	/* IO - Pointer in buffer */
             const int     *oid)	/* I  - OID value */
{
  **buffer = ASN1_OID;
  (*buffer) ++;

  asn1_set_length(buffer, asn1_size_oid(oid));

  asn1_set_packed(buffer, oid[0] * 40 + oid[1]);

  for (oid += 2; *oid; oid ++)
    asn1_set_packed(buffer, *oid);
}


/*
 * 'asn1_set_packed()' - Set a packed integer value.
 */

static void
asn1_set_packed(unsigned char **buffer,	/* IO - Pointer in buffer */
		int           integer)	/* I  - Integer value */
{
  if (integer > 0xfffffff)
  {
    **buffer = (integer >> 28) & 0x7f;
    (*buffer) ++;
  }

  if (integer > 0x1fffff)
  {
    **buffer = (integer >> 21) & 0x7f;
    (*buffer) ++;
  }

  if (integer > 0x3fff)
  {
    **buffer = (integer >> 14) & 0x7f;
    (*buffer) ++;
  }

  if (integer > 0x7f)
  {
    **buffer = (integer >> 7) & 0x7f;
    (*buffer) ++;
  }

  **buffer = integer & 0x7f;
  (*buffer) ++;
}

/*
 * 'asn1_size_integer()' - Figure out the number of bytes needed for an
 *                         integer value.
 */

static int				/* O - Size in bytes */
asn1_size_integer(int integer)		/* I - Integer value */
{
  if (integer > 0x7fffff || integer < -0x800000)
    return (4);
  else if (integer > 0x7fff || integer < -0x8000)
    return (3);
  else if (integer > 0x7f || integer < -0x80)
    return (2);
  else
    return (1);
}


/*
 * 'asn1_size_length()' - Figure out the number of bytes needed for a
 *                        length value.
 */

static int				/* O - Size in bytes */
asn1_size_length(int length)		/* I - Length value */
{
  if (length > 0xff)
    return (3);
  else if (length > 0x7f)
    return (2);
  else
    return (1);
}


/*
 * 'asn1_size_oid()' - Figure out the numebr of bytes needed for an
 *                     OID value.
 */

static int				/* O - Size in bytes */
asn1_size_oid(const int *oid)		/* I - OID value */
{
  int	length;				/* Length of value */


  for (length = asn1_size_packed(oid[0] * 40 + oid[1]), oid += 2; *oid; oid ++)
    length += asn1_size_packed(*oid);

  return (length);
}


/*
 * 'asn1_size_packed()' - Figure out the number of bytes needed for a
 *                        packed integer value.
 */

static int				/* O - Size in bytes */
asn1_size_packed(int integer)		/* I - Integer value */
{
  if (integer > 0xfffffff)
    return (5);
  else if (integer > 0x1fffff)
    return (4);
  else if (integer > 0x3fff)
    return (3);
  else if (integer > 0x7f)
    return (2);
  else
    return (1);
}


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


/*
 * '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
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 (!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 (!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, make_model_size - 3);
  }
  else if (!strncasecmp(old_make_model, "deskjet", 7))
    snprintf(make_model, make_model_size, "HP DeskJet%s", old_make_model + 7);
  else if (!strncasecmp(old_make_model, "officejet", 9))
    snprintf(make_model, make_model_size, "HP OfficeJet%s", old_make_model + 9);
  else if (!strncasecmp(old_make_model, "stylus_pro_", 11))
    snprintf(make_model, make_model_size, "EPSON Stylus Pro %s",
             old_make_model + 11);
  else
    strlcpy(make_model, old_make_model, 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);
}


/*
 * 'hex_debug()' - Output hex debugging data...
 */

static void
hex_debug(unsigned char *buffer,	/* I - Buffer */
          size_t        len)		/* I - Number of bytes */
{
  int	col;				/* Current column */


  fputs("DEBUG: Hex dump of packet:\n", stderr);

  for (col = 0; len > 0; col ++, buffer ++, len --)
  {
    if ((col & 15) == 0)
      fprintf(stderr, "DEBUG: %04X ", col);

    fprintf(stderr, " %02X", *buffer);

    if ((col & 15) == 15)
      putc('\n', stderr);
  }

  if (col & 15)
    putc('\n', stderr);
}


/*
 * 'list_device()' - List a device we found...
 */

static void
list_device(snmp_cache_t *cache)	/* I - Cached device */
{
  if (cache->uri)
    printf("network %s \"%s\" \"%s %s\" \"%s\"\n",
           cache->uri,
	   cache->make_and_model ? cache->make_and_model : "Unknown",
	   cache->make_and_model ? cache->make_and_model : "Unknown",
	   cache->addrname, cache->id ? cache->id : "");
}


/*
 * 'open_snmp_socket()' - Open the SNMP broadcast socket.
 */

static int				/* O - SNMP socket file descriptor */
open_snmp_socket(void)
{
  int		fd;			/* SNMP socket file descriptor */
  int		val;			/* Socket option value */


 /*
  * Create the SNMP socket...
  */

  if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
  {
    fprintf(stderr, "ERROR: Unable to create SNMP socket - %s\n",
            strerror(errno));

    return (-1);
  }

 /*
  * Set the "broadcast" flag...
  */

  val = 1;

  if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &val, sizeof(val)))
  {
    fprintf(stderr, "ERROR: Unable to set broadcast mode - %s\n",
            strerror(errno));

    close(fd);

    return (-1);
  }

  return (fd);
}


/*
 * '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 */
{
  int		i, j;			/* Looping vars */
  http_t	*http;			/* HTTP connection for IPP */
  char		uri[1024];		/* Full device URI */


 /*
  * 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...
    */

    http = NULL;
  }
  else
  {
   /*
    * Otherwise, try connecting for up to 1 second...
    */

    alarm(1);
    http = httpConnect(device->addrname, 631);
    alarm(0);
  }

  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...
    */

    for (i = 0, num_uris = 0;
         i < (int)(sizeof(resources) / sizeof(resources[0]));
         i ++)
    {
     /*
      * Stop early if we are out of time...
      */

      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)
      {
        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)
	  {
	    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';

        fix_make_model(make_model, temp, sizeof(make_model));

       /*
        * Update the current device or add a new printer to the cache...
	*/

        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);

        num_uris ++;
      }

      ippDelete(response);

      if (num_uris > 0 && cupsLastError() != IPP_OK)
        break;
    }

    httpClose(http);

    if (num_uris > 0)
      return;
  }

 /*
  * OK, now 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 (!strcasecmp(line, "Address"))
      {
        if (!address)
          add_array(Addresses, value);
      }
      else if (!strcasecmp(line, "Community"))
        add_array(Communities, value);
      else if (!strcasecmp(line, "DebugLevel"))
        DebugLevel = atoi(value);
      else if (!strcasecmp(line, "HostNameLookups"))
        HostNameLookups = !strcasecmp(value, "on") ||
	                  !strcasecmp(value, "yes") ||
	                  !strcasecmp(value, "true") ||
	                  !strcasecmp(value, "double");
      else if (!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)
  {
    fputs("INFO: Using default SNMP Address @LOCAL\n", stderr);
    add_array(Addresses, "@LOCAL");
  }

  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 */
{
  unsigned char	buffer[SNMP_MAX_PACKET];/* Data packet */
  int		bytes;			/* Number of bytes received */
  http_addr_t	addr;			/* Source address */
  socklen_t	addrlen;		/* Source address length */
  char		addrname[256];		/* Source address name */
  snmp_packet_t	packet;			/* Decoded packet */
  snmp_cache_t	key,			/* Search key */
		*device;		/* Matching device */


 /*
  * Read the response data...
  */

  addrlen = sizeof(addr);

  if ((bytes = recvfrom(fd, buffer, sizeof(buffer), 0, (void *)&addr,
                        &addrlen)) < 0)
  {
    fprintf(stderr, "ERROR: Unable to read data from socket: %s\n",
            strerror(errno));
    return;
  }

  if (HostNameLookups)
    httpAddrLookup(&addr, addrname, sizeof(addrname));
  else
    httpAddrString(&addr, addrname, sizeof(addrname));

  debug_printf("DEBUG: %.3f Received %d bytes from %s...\n", run_time(),
               bytes, addrname);

 /*
  * Look for the response status code in the SNMP message header...
  */

  if (asn1_decode_snmp(buffer, bytes, &packet))
  {
    fprintf(stderr, "ERROR: Bad SNMP packet from %s: %s\n", addrname,
            packet.error);

    asn1_debug(buffer, bytes, 0);
    hex_debug(buffer, bytes);

    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 (DebugLevel > 1)
    asn1_debug(buffer, bytes, 0);

  if (DebugLevel > 2)
    hex_debug(buffer, bytes);

  if (packet.error_status)
    return;

 /*
  * Find a matching device in the cache...
  */

  key.addrname = addrname;
  device       = (snmp_cache_t *)cupsArrayFind(Devices, &key);

 /*
  * Process the message...
  */

  if (packet.request_id == DeviceTypeRequest)
  {
   /*
    * 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 description...
    */

    add_cache(&addr, addrname, NULL, NULL, NULL);

    send_snmp_query(fd, &addr, SNMP_VERSION_1, packet.community,
                    DeviceDescRequest, DeviceDescOID);
  }
  else if (packet.request_id == DeviceDescRequest &&
           packet.object_type == ASN1_OCTET_STRING)
  {
   /*
    * Update an existing cache entry...
    */

    char	make_model[256];	/* Make and model */


    if (!device)
    {
      debug_printf("DEBUG: Discarding device description for \"%s\"...\n",
	           addrname);
      return;
    }

   /*
    * Convert the description to a make and model string...
    */

    if (strchr(packet.object_value.string, ':') &&
        strchr(packet.object_value.string, ';'))
    {
     /*
      * Description is the IEEE-1284 device ID...
      */

      backendGetMakeModel(packet.object_value.string, make_model,
                	  sizeof(make_model));
    }
    else
    {
     /*
      * Description is plain text...
      */

      fix_make_model(make_model, packet.object_value.string,
                     sizeof(make_model));
    }

    if (device->make_and_model)
      free(device->make_and_model);

    device->make_and_model = strdup(make_model);
  }
}


/*
 * '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 fd)			/* I - SNMP socket */
{
  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 */


 /*
  * Setup the request IDs...
  */

  gettimeofday(&StartTime, NULL);

  DeviceTypeRequest = StartTime.tv_sec;
  DeviceDescRequest = StartTime.tv_sec + 1;

 /*
  * 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_INET, 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)
        send_snmp_query(fd, &(addr->addr), SNMP_VERSION_1, community,
	                DeviceTypeRequest, DeviceTypeOID);
    }

    httpAddrFreeList(addrs);
  }

 /*
  * Then read any responses that come in over the next 3 seconds...
  */

  endtime = time(NULL) + 3;

  FD_ZERO(&input);

  while (time(NULL) < endtime)
  {
    timeout.tv_sec  = 1;
    timeout.tv_usec = 0;

    FD_SET(fd, &input);
    if (select(fd + 1, &input, NULL, NULL, &timeout) < 0)
    {
      fprintf(stderr, "ERROR: %.3f select() for %d failed: %s\n", run_time(),
              fd, strerror(errno));
      break;
    }

    if (FD_ISSET(fd, &input))
      read_snmp_response(fd);
    else
      break;
  }

 /*
  * Finally, probe all of the printers we discovered to see how they are
  * connected...
  */

  for (device = (snmp_cache_t *)cupsArrayFirst(Devices);
       device;
       device = (snmp_cache_t *)cupsArrayNext(Devices))
    if (MaxRunTime > 0 && run_time() >= MaxRunTime)
      break;
    else if (!device->uri)
      probe_device(device);

  debug_printf("DEBUG: %.3f Scan complete!\n", run_time());
}


/*
 * 'send_snmp_query()' - Send an SNMP query packet.
 */

static void
send_snmp_query(
    int            fd,			/* I - SNMP socket */
    http_addr_t    *addr,		/* I - Address to send to */
    int            version,		/* I - SNMP version */
    const char     *community,		/* I - Community name */
    const unsigned request_id,		/* I - Request ID */
    const int      *oid)		/* I - OID */
{
  int		i;			/* Looping var */
  snmp_packet_t	packet;			/* SNMP message packet */
  unsigned char	buffer[SNMP_MAX_PACKET];/* SNMP message buffer */
  int		bytes;			/* Size of message */
  char		addrname[32];		/* Address name */


 /*
  * Create the SNMP message...
  */

  memset(&packet, 0, sizeof(packet));

  packet.version      = version;
  packet.request_type = ASN1_GET_REQUEST;
  packet.request_id   = request_id;
  packet.object_type  = ASN1_NULL_VALUE;
  
  strlcpy(packet.community, community, sizeof(packet.community));

  for (i = 0; oid[i]; i ++)
    packet.object_name[i] = oid[i];

  bytes = asn1_encode_snmp(buffer, sizeof(buffer), &packet);

  if (bytes < 0)
  {
    fprintf(stderr, "ERROR: Unable to send SNMP query: %s\n", packet.error);
    return;
  }

 /*
  * Send the message...
  */

  debug_printf("DEBUG: %.3f Sending %d bytes to %s...\n", run_time(),
               bytes, httpAddrString(addr, addrname, sizeof(addrname)));
  if (DebugLevel > 1)
    asn1_debug(buffer, bytes, 0);
  if (DebugLevel > 2)
    hex_debug(buffer, bytes);

  addr->ipv4.sin_port = htons(SNMP_PORT);

  if (sendto(fd, buffer, bytes, 0, (void *)addr, sizeof(addr->ipv4)) < bytes)
    fprintf(stderr, "ERROR: Unable to send %d bytes to %s: %s\n",
            bytes, addrname, strerror(errno));
}


/*
 * '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(AF_INET, SOCK_STREAM, 0)) < 0)
  {
    fprintf(stderr, "ERROR: Unable to create socket: %s\n", strerror(errno));
    return (-1);
  }

  addr->ipv4.sin_port = htons(port);

  alarm(1);

  status = connect(fd, (void *)addr, 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);
}


/*
 * End of "$Id: snmp.c 6181 2007-01-03 18:51:27Z mike $".
 */
