Blob Blame History Raw
diff -up cups-filters-1.0.38/utils/cups-browsed.c.browsepoll-notifications cups-filters-1.0.38/utils/cups-browsed.c
--- cups-filters-1.0.38/utils/cups-browsed.c.browsepoll-notifications	2013-08-14 10:24:38.000000000 +0100
+++ cups-filters-1.0.38/utils/cups-browsed.c	2013-10-01 17:12:54.257126326 +0100
@@ -100,6 +100,16 @@ typedef struct allow_s {
   http_addr_t mask;
 } allow_t;
 
+/* Data structure for a BrowsePoll server */
+typedef struct browsepoll_s {
+  char *server;
+  int port;
+  int major;
+  int minor;
+  gboolean can_subscribe;
+  int subscription_id;
+} browsepoll_t;
+
 cups_array_t *remote_printers;
 static cups_array_t *netifs;
 static cups_array_t *browseallow;
@@ -118,7 +128,7 @@ static unsigned int BrowseRemoteProtocol
 static unsigned int BrowseInterval = 60;
 static unsigned int BrowseTimeout = 300;
 static uint16_t BrowsePort = 631;
-static char **BrowsePoll = NULL;
+static browsepoll_t **BrowsePoll = NULL;
 static size_t NumBrowsePoll = 0;
 static char *DomainSocket = NULL;
 
@@ -234,6 +244,16 @@ void debug_printf(const char *format, ..
   }
 }
 
+static const char *
+password_callback (const char *prompt,
+		   http_t *http,
+		   const char *method,
+		   const char *resource,
+		   void *user_data)
+{
+  return NULL;
+}
+
 static remote_printer_t *
 create_local_queue (const char *name,
 		    const char *uri,
@@ -1469,66 +1489,22 @@ send_browse_data (gpointer data)
   return FALSE;
 }
 
-gboolean
-browse_poll (gpointer data)
+static void
+browse_poll_get_printers (browsepoll_t *context, http_t *conn)
 {
   static const char * const rattrs[] = { "printer-uri-supported" };
-  char *server = strdup (data);
   ipp_t *request, *response = NULL;
   ipp_attribute_t *attr;
-  http_t *conn;
-  int port = BrowsePort;
-  char *colon,*slash;
-  int major = 0;
-  int minor = 0;
-
-  slash = strchr (server, '/');
-  if (slash) {
-    *slash++ = '\0';
-    if (!strcmp(slash, "version=1.0")) {
-      major = 1;
-      minor = 0;
-    } else if (!strcmp(slash, "version=1.1")) {
-      major = 1;
-      minor = 1;
-    } else if (!strcmp(slash, "version=2.0")) {
-      major = 2;
-      minor = 0;
-    } else if (!strcmp(slash, "version=2.1")) {
-      major = 2;
-      minor = 1;
-    } else if (!strcmp(slash, "version=2.2")) {
-      major = 2;
-      minor = 2;
-    } else {
-      debug_printf ("ignoring unknown server option: %s\n", slash);
-    }
-  }
-
-  debug_printf ("cups-browsed: browse polling %s\n", server);
-
-  colon = strchr (server, ':');
-  if (colon) {
-    char *endptr;
-    unsigned long n;
-    *colon++ = '\0';
-    n = strtoul (colon, &endptr, 10);
-    if (endptr != colon && n < INT_MAX)
-      port = (int) n;
-  }
-
-  res_init ();
-  conn = httpConnectEncrypt (server, port, HTTP_ENCRYPT_IF_REQUESTED);
 
-  if (conn == NULL) {
-    debug_printf("cups-browsed: browse poll failed to connect to %s\n", server);
-    goto fail;
-  }
+  debug_printf ("cups-browsed [BrowsePoll %s:%d]: CUPS-Get-Printers\n",
+		context->server, context->port);
 
   request = ippNewRequest(CUPS_GET_PRINTERS);
-  if (major > 0) {
-    debug_printf("cups-browsed: setting IPP version %d.%d\n", major, minor);
-    ippSetVersion (request, major, minor);
+  if (context->major > 0) {
+    debug_printf("cups-browsed [BrowsePoll %s:%d]: setting IPP version %d.%d\n",
+		 context->server, context->port, context->major,
+		 context->minor);
+    ippSetVersion (request, context->major, context->minor);
   }
 
   ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
@@ -1550,8 +1526,8 @@ browse_poll (gpointer data)
 
   response = cupsDoRequest(conn, request, "/");
   if (cupsLastError() > IPP_OK_CONFLICT) {
-    debug_printf("cups-browsed: browse poll failed for server %s: %s\n",
-		 server, cupsLastErrorString ());
+    debug_printf("cups-browsed [BrowsePoll %s:%d]: failed: %s\n",
+		 context->server, context->port, cupsLastErrorString ());
     goto fail;
   }
 
@@ -1579,7 +1555,7 @@ browse_poll (gpointer data)
     }
 
     if (uri)
-      found_cups_printer (server, uri, info);
+      found_cups_printer (context->server, uri, info);
 
     if (!attr)
       break;
@@ -1590,12 +1566,226 @@ browse_poll (gpointer data)
 fail:
   if (response)
     ippDelete(response);
+}
 
-  if (conn)
-    httpClose (conn);
+static void
+browse_poll_create_subscription (browsepoll_t *context, http_t *conn)
+{
+  static const char * const events[] = { "printer-added",
+					 "printer-changed",
+					 "printer-config-changed",
+					 "printer-modified",
+					 "printer-deleted",
+					 "printer-state-changed" };
+  ipp_t *request, *response = NULL;
+  ipp_attribute_t *attr;
+
+  debug_printf ("cups-browsed [BrowsePoll %s:%d]: IPP-Create-Subscription\n",
+		context->server, context->port);
+
+  request = ippNewRequest(IPP_CREATE_PRINTER_SUBSCRIPTION);
+  if (context->major > 0) {
+    debug_printf("cups-browsed [BrowsePoll %s:%d]: setting IPP version %d.%d\n",
+		 context->server, context->port, context->major,
+		 context->minor);
+    ippSetVersion (request, context->major, context->minor);
+  }
+
+  ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+		"printer-uri", NULL, "/");
+  ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
+		"notify-pull-method", NULL, "ippget");
+  ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_CHARSET,
+		"notify-charset", NULL, "utf-8");
+  ippAddString (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_NAME,
+		"requesting-user-name", NULL, cupsUser ());
+  ippAddStrings (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
+		 "notify-events", sizeof (events) / sizeof (events[0]),
+		 NULL, events);
+  ippAddInteger (request, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
+		 "notify-time-interval", BrowseInterval);
+
+  response = cupsDoRequest (conn, request, "/");
+  if (!response || ippGetStatusCode (response) > IPP_OK_CONFLICT) {
+    debug_printf("cupsd-browsed [BrowsePoll %s:%d]: failed: %s\n",
+		 context->server, context->port, cupsLastErrorString ());
+    context->subscription_id = -1;
+    context->can_subscribe = FALSE;
+    goto fail;
+  }
+
+  for (attr = ippFirstAttribute(response); attr;
+       attr = ippNextAttribute(response)) {
+    if (ippGetGroupTag (attr) == IPP_TAG_SUBSCRIPTION) {
+      if (ippGetValueTag (attr) == IPP_TAG_INTEGER &&
+	  !strcmp (ippGetName (attr), "notify-subscription-id")) {
+	context->subscription_id = ippGetInteger (attr, 0);
+	debug_printf("cups-browsed [BrowsePoll %s:%d]: subscription ID=%d\n",
+		     context->server, context->port, context->subscription_id);
+	break;
+      }
+    }
+  }
+
+  if (!attr) {
+    debug_printf("cups-browsed [BrowsePoll %s:%d]: no ID returned\n",
+		 context->server, context->port);
+    context->subscription_id = -1;
+    context->can_subscribe = FALSE;
+  }
+
+fail:
+  if (response)
+    ippDelete(response);
+}
+
+static void
+browse_poll_cancel_subscription (browsepoll_t *context)
+{
+  ipp_t *request, *response = NULL;
+  http_t *conn = httpConnectEncrypt (context->server, context->port,
+				     HTTP_ENCRYPT_IF_REQUESTED);
+
+  if (conn == NULL) {
+    debug_printf("cups-browsed [BrowsePoll %s:%d]: connection failure "
+		 "attempting to cancel\n", context->server, context->port);
+    return;
+  }
+
+  debug_printf ("cups-browsed [BrowsePoll %s:%d] IPP-Cancel-Subscription\n",
+		context->server, context->port);
+
+  request = ippNewRequest(IPP_CANCEL_SUBSCRIPTION);
+  if (context->major > 0) {
+    debug_printf("cups-browsed [BrowsePoll %s:%d]: setting IPP version %d.%d\n",
+		 context->server, context->port, context->major,
+		 context->minor);
+    ippSetVersion (request, context->major, context->minor);
+  }
+
+  ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+		"printer-uri", NULL, "/");
+  ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+		"requesting-user-name", NULL, cupsUser ());
+  ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
+		 "notify-subscription-id", context->subscription_id);
+
+  response = cupsDoRequest (conn, request, "/");
+  if (!response || ippGetStatusCode (response) > IPP_OK_CONFLICT)
+    debug_printf("cupsd-browsed [BrowsePoll %s:%d]: failed: %s\n",
+		 context->server, context->port, cupsLastErrorString ());
 
-  if (server)
-    free (server);
+  if (response)
+    ippDelete(response);
+}
+
+static gboolean
+browse_poll_get_notifications (browsepoll_t *context, http_t *conn)
+{
+  ipp_t *request, *response = NULL;
+  ipp_attribute_t *attr;
+  ipp_status_t status;
+  gboolean get_printers = FALSE;
+
+  debug_printf ("cups-browsed [BrowsePoll %s:%d] IPP-Get-Notifications\n",
+		context->server, context->port);
+
+  request = ippNewRequest(IPP_GET_NOTIFICATIONS);
+  if (context->major > 0) {
+    debug_printf("cups-browsed [BrowsePoll %s:%d]: setting IPP version %d.%d\n",
+		 context->server, context->port, context->major,
+		 context->minor);
+    ippSetVersion (request, context->major, context->minor);
+  }
+
+  ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI,
+		"printer-uri", NULL, "/");
+  ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+		"requesting-user-name", NULL, cupsUser ());
+  ippAddInteger (request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
+		 "notify-subscription-ids", context->subscription_id);
+
+  response = cupsDoRequest (conn, request, "/");
+  if (!response)
+    status = cupsLastError ();
+  else
+    status = ippGetStatusCode (response);
+
+  if (status == IPP_NOT_FOUND) {
+    /* Subscription lease has expired. */
+    debug_printf ("cups-browsed [BrowsePoll %s:%d] Lease expired\n",
+		  context->server, context->port);
+    browse_poll_create_subscription (context, conn);
+    get_printers = TRUE;
+  } else if (status > IPP_OK_CONFLICT) {
+    debug_printf("cupsd-browsed [BrowsePoll %s:%d]: failed: %s\n",
+		 context->server, context->port, cupsLastErrorString ());
+    context->can_subscribe = FALSE;
+    browse_poll_cancel_subscription (context);
+    context->subscription_id = -1;
+    get_printers = TRUE;
+    goto fail;
+  }
+
+  for (attr = ippFirstAttribute(response); attr;
+       attr = ippNextAttribute(response))
+    if (ippGetGroupTag (attr) == IPP_TAG_EVENT_NOTIFICATION)
+      /* There is a printer-* event here. */
+      break;
+
+  if (attr) {
+    debug_printf("cups-browsed [BrowsePoll %s:%d]: printer-* event\n",
+		 context->server, context->port);
+    get_printers = TRUE;
+  } else
+    debug_printf("cups-browsed [BrowsePoll %s:%d]: no events\n",
+		 context->server, context->port);
+
+fail:
+  if (response)
+    ippDelete(response);
+
+  return get_printers;
+}
+
+gboolean
+browse_poll (gpointer data)
+{
+  browsepoll_t *context = data;
+  http_t *conn = NULL;
+  gboolean get_printers = FALSE;
+
+  debug_printf ("cups-browsed: browse polling %s:%d\n",
+		context->server, context->port);
+
+  res_init ();
+
+  conn = httpConnectEncrypt (context->server, context->port,
+			     HTTP_ENCRYPT_IF_REQUESTED);
+  if (conn == NULL) {
+    debug_printf("cups-browsed [BrowsePoll %s:%d]: failed to connect\n",
+		 context->server, context->port);
+    goto fail;
+  }
+
+  if (context->can_subscribe) {
+    if (context->subscription_id == -1) {
+      /* The first time this callback is run we need to create the IPP
+       * subscription to watch to printer-* events. */
+      browse_poll_create_subscription (context, conn);
+      get_printers = TRUE;
+    } else
+      /* On subsequent runs, check for notifications using our
+       * subscription. */
+      get_printers = browse_poll_get_notifications (context, conn);
+  }
+  else
+    get_printers = TRUE;
+
+  if (get_printers)
+    browse_poll_get_printers (context, conn);
+
+fail:
 
   /* Call a new timeout handler so that we run again */
   g_timeout_add_seconds (BrowseInterval, browse_poll, data);
@@ -1723,14 +1913,61 @@ read_configuration (const char *filename
       else
 	BrowseLocalProtocols = BrowseRemoteProtocols = protocols;
     } else if (!strcasecmp(line, "BrowsePoll") && value) {
-      char **old = BrowsePoll;
-      BrowsePoll = realloc (BrowsePoll, (NumBrowsePoll + 1) * sizeof (char *));
+      browsepoll_t **old = BrowsePoll;
+      BrowsePoll = realloc (BrowsePoll,
+			    (NumBrowsePoll + 1) *
+			    sizeof (browsepoll_t));
       if (!BrowsePoll) {
 	debug_printf("cups-browsed: unable to realloc: ignoring BrowsePoll line\n");
 	BrowsePoll = old;
       } else {
-	debug_printf("cups-browsed: Adding BrowsePoll server: %s\n", value);
-	BrowsePoll[NumBrowsePoll++] = strdup (value);
+	char *colon, *slash;
+	browsepoll_t *b = malloc (sizeof (browsepoll_t));
+	if (!b) {
+	  debug_printf("cups-browsed: unable to malloc: ignoring BrowsePoll line\n");
+	  BrowsePoll = old;
+	} else {
+	  debug_printf("cups-browsed: Adding BrowsePoll server: %s\n", value);
+	  b->server = strdup (value);
+	  b->port = BrowsePort;
+	  b->can_subscribe = TRUE; /* first assume subscriptions work */
+	  b->subscription_id = -1;
+	  slash = strchr (b->server, '/');
+	  if (slash) {
+	    *slash++ = '\0';
+	    if (!strcmp (slash, "version=1.0")) {
+	      b->major = 1;
+	      b->minor = 0;
+	    } else if (!strcmp (slash, "version=1.1")) {
+	      b->major = 1;
+	      b->minor = 1;
+	    } else if (!strcmp (slash, "version=2.0")) {
+	      b->major = 2;
+	      b->minor = 0;
+	    } else if (!strcmp (slash, "version=2.1")) {
+	      b->major = 2;
+	      b->minor = 1;
+	    } else if (!strcmp (slash, "version=2.2")) {
+	      b->major = 2;
+	      b->minor = 2;
+	    } else {
+	      debug_printf ("ignoring unknown server option: %s\n", slash);
+	    }
+	  } else
+	    b->major = 0;
+
+	  colon = strchr (b->server, ':');
+	  if (colon) {
+	    char *endptr;
+	    unsigned long n;
+	    *colon++ = '\0';
+	    n = strtoul (colon, &endptr, 10);
+	    if (endptr != colon && n < INT_MAX)
+	      b->port = (int) n;
+	  }
+
+	  BrowsePoll[NumBrowsePoll++] = b;
+	}
       }
     } else if (!strcasecmp(line, "BrowseAllow") && value) {
       if (read_browseallow_value (value))
@@ -1972,6 +2209,10 @@ int main(int argc, char*argv[]) {
     goto fail;
   }
 
+  /* Override the default password callback so we don't end up
+   * prompting for it. */
+  cupsSetPasswordCB2 (password_callback, NULL);
+
   /* Run the main loop */
   gmainloop = g_main_loop_new (NULL, FALSE);
   recheck_timer ();
@@ -1994,7 +2235,7 @@ int main(int argc, char*argv[]) {
 	 index < NumBrowsePoll;
 	 index++) {
       debug_printf ("cups-browsed: will browse poll %s every %ds\n",
-		    BrowsePoll[index], BrowseInterval);
+		    BrowsePoll[index]->server, BrowseInterval);
       g_idle_add (browse_poll, BrowsePoll[index]);
     }
   }
@@ -2018,6 +2259,21 @@ fail:
   }
   handle_cups_queues(NULL);
 
+  if (BrowsePoll) {
+    size_t index;
+    for (index = 0;
+	 index < NumBrowsePoll;
+	 index++) {
+      if (BrowsePoll[index]->can_subscribe &&
+	  BrowsePoll[index]->subscription_id != -1)
+	browse_poll_cancel_subscription (BrowsePoll[index]);
+
+      free (BrowsePoll[index]->server);
+      free (BrowsePoll[index]);
+    }
+
+    free (BrowsePoll);
+  }
 #ifdef HAVE_AVAHI
   /* Free the data structures for Bonjour browsing */
   if (sb1)