[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#401405: Further information and patch



I've been browsing the sources a bit, and the primary case is a bit clearer 
now. In backend/usb-unix.c, in open_device and list_devices is a loop that is 
iterating over all possible USB printers and trying to match them against the 
given URI. It always tries "/dev/usblp%d", "/dev/usb/lp%d" 
and "/dev/usb/usblb%d" for any given index and the error handling here is 
broken - it only reports the errno value of the last attempt to open a 
printer, and since I don't have 16 printers that is always ENOENT. The fact 
that it got an EACCESS while opening /dev/usb/lp0 is totally left out.
I think the algorithm to better diagnose errors goes like this:
1. If all open() calls set errno=ENOENT, signal that no printers were found. 
This could also be done using stat(), no need to call open().
2. If any printers were present but could not be opened, the fact and the 
reason should go into a logfile. If the search does not find the requested 
printer, the fact that not all printers could be probed should be returned 
and a hint to look into the logfile.
3. If you could probe them all, just return whether the requested printer was 
found.

I'm not sure how this should be handled in the case of list_devices, either it 
should simply print five question marks for the five fields it otherwise 
fills with the URI and other data or an error message. I took the easy way 
out using the error message.

I have attached the modified usb-unix.c. Changes/Notes:
- It now correctly gives me an access denied error in the webfrontend and 
works as described above.
- When invoking the backend without arguments, it now either probes the 
printer for its ID or gives an errormessage.
- I marked a bogus check for EBUSY.
- In the whole CUPS sourcecode I found cases where sprintf() was used 
(potential buffer overflows) or snprintf() used wrong (snprintf() can leave 
an unterminated string when the buffer is too small). I'd suggest using an 
xsnprintf()-wrapper, similarly to xalloc() either formatting the string into 
the supplied buffer or invoking exit() - the code isn't prepared to handle 
that error anyway, so there's no way to continue. This last point would merit 
a whole code audit.
- I find it also astonishing how many magic numbers are used, as if using a 
buffer of size 255 or 1024 would magically make your code correct...
- All driver backends seem to take similar arguments, except one of 
them. 'ipp' takes a variable number of files to print. In fact I wonder why 
there is no central main() function for commandline handling etc that then 
only calls the implementations from the particular backend. There's a lot of 
code duplication going on.
- There's a weird mixture of tabs and spaces in the files. I only preserved 
the two spaces indentation depth and didn't use any tabs, but I'd say it 
would be worth running the whole code through an autoindenter.
- There is some code in cups-deviced.c that I don't understand. When invoking 
the backend, it code first checks if either "group" or "others" have 
read/write/execute permissions on the backend and if yes, it calls seteuid() 
with the user ID of 'lp'. So, by just removing read and execute permissions 
on the backend, we can make cupsd call it with root permissions (it then 
fails without meaningful error when invoking foomatic-rip). I find this idea 
flawed. Also, quote from the (default) configfile:
  # Note: the server must be run initially as root to support the
  # default IPP port of 631.  It changes users whenever an external
  # program is run...
IOW, this comment is simply a lie and keeping root privileges is a possible 
security risk. Well, not that anyone sane would be running CUPS in a hostile 
network anyway...

Okay, so much for that. Sorry for drifting of into ranting towards the end, 
but I hope the info was at least helpful. Question on that: is there any 
packaged version of 1.3 already? I would have preferred hacking on that one 
and not on a released version...

Also, you (the maintainers) probably have a connection to the upstream CUPS 
developers, could you ask them if they could enable bug-reporting without 
creating yet another account to log into their website? I find this extremely 
annoying...

so much for today, thank you all for your work

Uli
/*
 * "$Id: usb-unix.c 6111 2006-11-15 20:28:39Z mike $"
 *
 *   USB port backend for the Common UNIX Printing System (CUPS).
 *
 *   This file is included from "usb.c" when compiled on UNIX/Linux.
 *
 *   Copyright 1997-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:
 *
 *   print_device() - Print a file to a USB device.
 *   list_devices() - List all USB devices.
 *   open_device()  - Open a USB device...
 */

/*
 * Include necessary headers.
 */

#include "ieee1284.c"
#include <sys/select.h>
#include <assert.h>

/*
 * Local functions...
 */

int	open_device(const char *uri, int *use_bc);

/*
 * 'print_device()' - Print a file to a USB device.
 */

int					/* O - Exit status */
print_device(const char *uri,		/* I - Device URI */
             const char *hostname,	/* I - Hostname/manufacturer */
             const char *resource,	/* I - Resource/modelname */
	     const char *options,	/* I - Device options/serial number */
	     int        print_fd,	/* I - File descriptor to print */
	     int        copies,		/* I - Copies to print */
	     int	argc,		/* I - Number of command-line arguments (6 or 7) */
	     char	*argv[])	/* I - Command-line arguments */
{
  int		use_bc;			/* Use backchannel path? */
  int		device_fd;		/* USB device */
  size_t	tbytes;			/* Total number of bytes written */
  struct termios opts;			/* Parallel port options */


  (void)argc;
  (void)argv;


 /*
  * Open the USB port device...
  */

  fputs("STATE: +connecting-to-device\n", stderr);

  do
  {
   /*
    * Disable backchannel data when printing to Canon or Minolta USB
    * printers - apparently these printers will return the IEEE-1284
    * device ID over and over and over when they get a read request...
    */

    use_bc = strcasecmp(hostname, "Canon") && !strstr(hostname, "Minolta");

    if ((device_fd = open_device(uri, &use_bc)) == -1)
    {
      if (getenv("CLASS") != NULL)
      {
       /*
        * If the CLASS environment variable is set, the job was submitted
        * to a class and not to a specific queue.  In this case, we want
        * to abort immediately so that the job can be requeued on the next
        * available printer in the class.
        */

        fputs("INFO: Unable to open USB device, queuing on next printer in class...\n",
              stderr);

       /*
        * Sleep 5 seconds to keep the job from requeuing too rapidly...
        */

        sleep(5);

        return (CUPS_BACKEND_FAILED);
      }

      /* TODO: the check for EBUSY is unnecessary, open_device loops when any
      probe attempt returned EBUSY... */
      if (errno == EBUSY)
      {
        fputs("INFO: USB port busy; will retry in 30 seconds...\n", stderr);
        sleep(30);
      }
      else if (errno == ENXIO || errno == EIO || errno == ENOENT ||
               errno == ENODEV)
      {
        fputs("INFO: Printer not connected; will retry in 30 seconds...\n", stderr);
        sleep(30);
      }
      else
      {
        fprintf(stderr, "ERROR: Unable to find or open USB device \"%s\": %s\n",
                uri, strerror(errno));
        return (CUPS_BACKEND_FAILED);
      }
    }
  }
  while (device_fd < 0);

  fputs("STATE: -connecting-to-device\n", stderr);

 /*
  * Set any options provided...
  */

  tcgetattr(device_fd, &opts);

  opts.c_lflag &= ~(ICANON | ECHO | ISIG);	/* Raw mode */

  /**** No options supported yet ****/

  tcsetattr(device_fd, TCSANOW, &opts);

 /*
  * Finally, send the print file...
  */

  tbytes = 0;

  while (copies > 0 && tbytes >= 0)
  {
    copies --;

    if (print_fd != 0)
    {
      fputs("PAGE: 1 1\n", stderr);
      lseek(print_fd, 0, SEEK_SET);
    }

    tbytes = backendRunLoop(print_fd, device_fd, use_bc);

    if (print_fd != 0 && tbytes >= 0)
      fprintf(stderr, "INFO: Sent print file, " CUPS_LLFMT " bytes...\n",
	      CUPS_LLCAST tbytes);
  }

 /*
  * Close the USB port and return...
  */

  close(device_fd);

  return (tbytes < 0 ? CUPS_BACKEND_FAILED : CUPS_BACKEND_OK);
}


/*
 * 'list_devices()' - List all USB devices.
 */

void
list_devices(void)
{
#ifdef __linux
  int	i;				/* Looping var */
  int	fd;				/* File descriptor */
  char	device[255],			/* Device filename */
        device_id[1024],		/* Device ID string */
        device_uri[1024],		/* Device URI string */
        make_model[1024];		/* Make and model */

 /*
  * Linux has a long history of changing the standard filenames used
  * for USB printer devices.  We get the honor of trying them all...
  */
  char const* paths[3] = {
    "/dev/usblp%d",
    "/dev/usb/lp%d",
    "/dev/usb/usblb%d"
  };
  size_t const num_paths = (sizeof paths)/(sizeof *paths);

  // number of printers to search
  size_t const num_printers = 16;

  for( i=0; i!=num_paths*num_printers; ++i)
  {
    // assemble devicename
    sprintf( device, paths[i%num_paths], i/num_paths);

    // open device, skip the rest if it doesn't exist
    fd = open( device, O_RDWR | O_EXCL);
    if(fd < 0)
    {
      /* Unless the device is just not there, log the error. */
      if(errno!=ENOENT)
        fprintf( stderr, "INFO: failed to open device '%s': %s\n",
                 device, strerror(errno));
      continue;
    }


    if (!backendGetDeviceID(fd, device_id, sizeof(device_id),
                            make_model, sizeof(make_model),
                            "usb", device_uri, sizeof(device_uri)))
    {
        printf("direct %s \"%s\" \"%s USB #%d\" \"%s\"\n", device_uri,
                make_model, make_model, i/num_paths + 1, device_id);
    }
    else
    {
        fprintf( stderr, "INFO: failed to read device ID from '%s'\n", device);
    }

    close(fd);
  }
#elif defined(__sgi)
#elif defined(__sun) && defined(ECPPIOC_GETDEVID)
  int	i;			/* Looping var */
  int	fd;			/* File descriptor */
  char	device[255],		/* Device filename */
	device_id[1024],	/* Device ID string */
	device_uri[1024],	/* Device URI string */
	make_model[1024];	/* Make and model */


 /*
  * Open each USB device...
  */

  for (i = 0; i < 8; i ++)
  {
    sprintf(device, "/dev/usb/printer%d", i);

    if ((fd = open(device, O_WRONLY | O_EXCL)) >= 0)
    {
      if (!backendGetDeviceID(fd, device_id, sizeof(device_id),
                              make_model, sizeof(make_model),
			      "usb", device_uri, sizeof(device_uri)))
	printf("direct %s \"%s\" \"%s USB #%d\" \"%s\"\n", device_uri,
	       make_model, make_model, i + 1, device_id);

      close(fd);
    }
  }
#elif defined(__hpux)
#elif defined(__osf)
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
  int   i;                      /* Looping var */
  char  device[255];            /* Device filename */


  for (i = 0; i < 8; i ++)
  {
    sprintf(device, "/dev/ulpt%d", i);
    if (!access(device, 0))
      printf("direct usb:%s \"Unknown\" \"USB Printer #%d\"\n", device, i + 1);

    sprintf(device, "/dev/unlpt%d", i);
    if (!access(device, 0))
      printf("direct usb:%s \"Unknown\" \"USB Printer #%d (no reset)\"\n", device, i + 1);
  }
#endif
}


/*
 * 'open_device()' - Open a USB device...
 */

int					/* O - File descriptor or -1 on error */
open_device(const char *uri,		/* I - Device URI */
            int        *use_bc)		/* O - Set to 0 for unidirectional */
{
  int	fd;				/* File descriptor */


 /*
  * The generic implementation just treats the URI as a device filename...
  * Specific operating systems may also support using the device serial
  * number and/or make/model.
  */

  if (!strncmp(uri, "usb:/dev/", 9))
#ifdef __linux
  {
   /*
    * Do not allow direct devices anymore...
    */

    errno = ENODEV;
    return (-1);
  }
  else if (!strncmp(uri, "usb://", 6))
  {
   /*
    * For Linux, try looking up the device serial number or model...
    */

    int		i;			/* Looping var */
    int		busy;			/* Are any ports busy? */
    char	device[255],		/* Device filename */
		device_id[1024],	/* Device ID string */
		make_model[1024],	/* Make and model */
		device_uri[1024];	/* Device URI string */
    int failure_errno; /* reason for open() failure */
    int printers_found; /* number of device nodes */
    int printers_probed; /* number of printers probed for their ID */


   /*
    * Find the correct USB device...
    */

    do
    {
      /*
       * Linux has a long history of changing the standard filenames used
       * for USB printer devices.  We get the honor of trying them all...
       */
      char const* paths[3] = {
        "/dev/usblp%d",
        "/dev/usb/lp%d",
        "/dev/usb/usblb%d"
      };
      size_t const num_paths = (sizeof paths)/(sizeof *paths);

      // number of printers to search
      size_t const num_printers = 16;

      busy = 0;
      failure_errno = 0;
      printers_found = 0;
      printers_probed = 0;
      for( i=0; i!=num_paths*num_printers; ++i) {
        // assemble devicename
        sprintf( device, paths[i%num_paths], i/num_paths);

        // open device, skip the rest if it doesn't exist
        fd = open( device, O_RDWR | O_EXCL);
        if(fd < 0 && errno==ENOENT)
          continue;

        ++printers_found;

        if (fd >= 0)
        {
          backendGetDeviceID(fd, device_id, sizeof(device_id),
                             make_model, sizeof(make_model),
                             "usb", device_uri, sizeof(device_uri));
          ++printers_probed;
        }
        else
        {
         /*
          * If the open failed because it was busy, flag it so we retry
          * as needed...
          */

          if (errno == EBUSY)
            busy = 1;
          else if (failure_errno==0)
            /* remember first failure that is neither because the printer
            is busy (EBUSY) nor because it doesn't exist (ENOENT). */
            failure_errno = errno;

          device_uri[0] = '\0';
        }

        if (!strcmp(uri, device_uri))
        {
         /*
          * Yes, return this file descriptor...
          */

          fprintf(stderr, "DEBUG: Printer using device file \"%s\"...\n", device);

          return (fd);
        }

       /*
        * This wasn't the one...
        */

        if (fd >= 0)
          close(fd);
      }

     /*
      * If we get here and at least one of the printer ports showed up
      * as "busy", then sleep for a bit and retry...
      */

      if (busy)
      {
        fputs("INFO: USB printer is busy; will retry in 5 seconds...\n",
              stderr);
        sleep(5);
      }
    }
    while (busy);

   /*
    * Couldn't find the printer
    *
    * If no printers were found, signal ENOENT. Otherwise, the reason for the
    * failure should be recorded in failure_errno.
    */

    if(printers_found==0)
      errno = ENODEV;
    else
    {
      assert(failure_errno!=0);
      errno = failure_errno;
    }

    return (-1);
  }
#elif defined(__sun) && defined(ECPPIOC_GETDEVID)
  {
   /*
    * Do not allow direct devices anymore...
    */

    errno = ENODEV;
    return (-1);
  }
  else if (!strncmp(uri, "usb://", 6))
  {
   /*
    * For Solaris, try looking up the device serial number or model...
    */

    int		i;			/* Looping var */
    int		busy;			/* Are any ports busy? */
    char	device[255],		/* Device filename */
		device_id[1024],	/* Device ID string */
		make_model[1024],	/* Make and model */
		device_uri[1024];	/* Device URI string */


   /*
    * Find the correct USB device...
    */

    do
    {
      for (i = 0, busy = 0; i < 8; i ++)
      {
	sprintf(device, "/dev/usb/printer%d", i);

	if ((fd = open(device, O_WRONLY | O_EXCL)) >= 0)
	  backendGetDeviceID(fd, device_id, sizeof(device_id),
                             make_model, sizeof(make_model),
			     "usb", device_uri, sizeof(device_uri));
	else
	{
	 /*
	  * If the open failed because it was busy, flag it so we retry
	  * as needed...
	  */

	  if (errno == EBUSY)
	    busy = 1;

	  device_uri[0] = '\0';
        }

        if (!strcmp(uri, device_uri))
	{
	 /*
	  * Yes, return this file descriptor...
	  */

          fputs("DEBUG: Setting use_bc to 0!\n", stderr);

          *use_bc = 0;

	  return (fd);
	}

       /*
	* This wasn't the one...
	*/

        if (fd >= 0)
	  close(fd);
      }

     /*
      * If we get here and at least one of the printer ports showed up
      * as "busy", then sleep for a bit and retry...
      */

      if (busy)
      {
	fputs("INFO: USB printer is busy; will retry in 5 seconds...\n",
	      stderr);
	sleep(5);
      }
    }
    while (busy);

   /*
    * Couldn't find the printer, return "no such device or address"...
    */

    errno = ENODEV;

    return (-1);
  }
#else
  {
    if ((fd = open(uri + 4, O_RDWR | O_EXCL)) < 0)
    {
      fd      = open(uri + 4, O_WRONLY | O_EXCL);
      *use_bc = 0;
    }

    return (fd);
  }
#endif /* __linux */
  else
  {
    errno = ENODEV;
    return (-1);
  }
}


/*
 * End of "$Id: usb-unix.c 6111 2006-11-15 20:28:39Z mike $".
 */

Reply to: