/* GTK - The GIMP Toolkit
* gtkprintbackendcups.h: Default implementation of GtkPrintBackend
* for the Common Unix Print System (CUPS)
* Copyright (C) 2006, 2007 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see .
*/
#include "config.h"
#include
#include
#include
#include
#include
#include
/* Cups 1.6 deprecates ppdFindAttr(), ppdFindCustomOption(),
* ppdFirstCustomParam(), and ppdNextCustomParam() among others.
* The replacement is to use the Job Ticket API, but that requires
* a larger refactoring of this backend.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "gtkprintbackendcups.h"
#include "gtkprintercups.h"
#include "gtkcupsutils.h"
#include "gtkcupssecretsutils.h"
#include
#ifdef HAVE_COLORD
#include
#endif
typedef struct _GtkPrintBackendCupsClass GtkPrintBackendCupsClass;
#define GTK_PRINT_BACKEND_CUPS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_PRINT_BACKEND_CUPS, GtkPrintBackendCupsClass))
#define GTK_IS_PRINT_BACKEND_CUPS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_PRINT_BACKEND_CUPS))
#define GTK_PRINT_BACKEND_CUPS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_PRINT_BACKEND_CUPS, GtkPrintBackendCupsClass))
#define _CUPS_MAX_ATTEMPTS 10
#define _CUPS_MAX_CHUNK_SIZE 8192
#define AVAHI_IF_UNSPEC -1
#define AVAHI_PROTO_INET 0
#define AVAHI_PROTO_INET6 1
#define AVAHI_PROTO_UNSPEC -1
#define AVAHI_BUS "org.freedesktop.Avahi"
#define AVAHI_SERVER_IFACE "org.freedesktop.Avahi.Server"
#define AVAHI_SERVICE_BROWSER_IFACE "org.freedesktop.Avahi.ServiceBrowser"
#define AVAHI_SERVICE_RESOLVER_IFACE "org.freedesktop.Avahi.ServiceResolver"
#define PRINTER_NAME_ALLOWED_CHARACTERS "abcdefghijklmnopqrtsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
/* define this to see warnings about ignored ppd options */
#undef PRINT_IGNORED_OPTIONS
#define _CUPS_MAP_ATTR_INT(attr, v, a) {if (!g_ascii_strcasecmp (attr->name, (a))) v = attr->values[0].integer;}
#define _CUPS_MAP_ATTR_STR(attr, v, a) {if (!g_ascii_strcasecmp (attr->name, (a))) v = attr->values[0].string.text;}
typedef void (* GtkPrintCupsResponseCallbackFunc) (GtkPrintBackend *print_backend,
GtkCupsResult *result,
gpointer user_data);
typedef enum
{
DISPATCH_SETUP,
DISPATCH_REQUEST,
DISPATCH_SEND,
DISPATCH_CHECK,
DISPATCH_READ,
DISPATCH_ERROR
} GtkPrintCupsDispatchState;
typedef struct
{
GSource source;
http_t *http;
GtkCupsRequest *request;
GtkCupsPollState poll_state;
GPollFD *data_poll;
GtkPrintBackendCups *backend;
GtkPrintCupsResponseCallbackFunc callback;
gpointer callback_data;
} GtkPrintCupsDispatchWatch;
struct _GtkPrintBackendCupsClass
{
GtkPrintBackendClass parent_class;
};
struct _GtkPrintBackendCups
{
GtkPrintBackend parent_instance;
char *default_printer;
guint list_printers_poll;
guint list_printers_pending : 1;
int list_printers_attempts;
guint got_default_printer : 1;
guint default_printer_poll;
GtkCupsConnectionTest *cups_connection_test;
int reading_ppds;
GList *requests;
GHashTable *auth;
char *username;
gboolean authentication_lock;
#ifdef HAVE_COLORD
CdClient *colord_client;
#endif
GDBusConnection *dbus_connection;
char *avahi_default_printer;
guint avahi_service_browser_subscription_id;
guint avahi_service_browser_subscription_ids[2];
char *avahi_service_browser_paths[2];
GCancellable *avahi_cancellable;
gboolean secrets_service_available;
guint secrets_service_watch_id;
GCancellable *secrets_service_cancellable;
};
static GObjectClass *backend_parent_class;
static void gtk_print_backend_cups_finalize (GObject *object);
static void gtk_print_backend_cups_dispose (GObject *object);
static void cups_get_printer_list (GtkPrintBackend *print_backend);
static void cups_get_default_printer (GtkPrintBackendCups *print_backend);
static void cups_get_local_default_printer (GtkPrintBackendCups *print_backend);
static void cups_request_execute (GtkPrintBackendCups *print_backend,
GtkCupsRequest *request,
GtkPrintCupsResponseCallbackFunc callback,
gpointer user_data,
GDestroyNotify notify);
static void cups_printer_get_settings_from_options (GtkPrinter *printer,
GtkPrinterOptionSet *options,
GtkPrintSettings *settings);
static gboolean cups_printer_mark_conflicts (GtkPrinter *printer,
GtkPrinterOptionSet *options);
static GtkPrinterOptionSet *cups_printer_get_options (GtkPrinter *printer,
GtkPrintSettings *settings,
GtkPageSetup *page_setup,
GtkPrintCapabilities capabilities);
static void cups_printer_prepare_for_print (GtkPrinter *printer,
GtkPrintJob *print_job,
GtkPrintSettings *settings,
GtkPageSetup *page_setup);
static GList * cups_printer_list_papers (GtkPrinter *printer);
static GtkPageSetup * cups_printer_get_default_page_size (GtkPrinter *printer);
static void cups_printer_request_details (GtkPrinter *printer);
static gboolean cups_request_default_printer (GtkPrintBackendCups *print_backend);
static gboolean cups_request_ppd (GtkPrinter *printer);
static gboolean cups_printer_get_hard_margins (GtkPrinter *printer,
double *top,
double *bottom,
double *left,
double *right);
static gboolean cups_printer_get_hard_margins_for_paper_size (GtkPrinter *printer,
GtkPaperSize *paper_size,
double *top,
double *bottom,
double *left,
double *right);
static GtkPrintCapabilities cups_printer_get_capabilities (GtkPrinter *printer);
static void set_option_from_settings (GtkPrinterOption *option,
GtkPrintSettings *setting);
static void cups_begin_polling_info (GtkPrintBackendCups *print_backend,
GtkPrintJob *job,
int job_id);
static gboolean cups_job_info_poll_timeout (gpointer user_data);
static void gtk_print_backend_cups_print_stream (GtkPrintBackend *backend,
GtkPrintJob *job,
GIOChannel *data_io,
GtkPrintJobCompleteFunc callback,
gpointer user_data,
GDestroyNotify dnotify);
static cairo_surface_t * cups_printer_create_cairo_surface (GtkPrinter *printer,
GtkPrintSettings *settings,
double width,
double height,
GIOChannel *cache_io);
static void gtk_print_backend_cups_set_password (GtkPrintBackend *backend,
char **auth_info_required,
char **auth_info,
gboolean store_auth_info);
void overwrite_and_free (gpointer data);
static gboolean is_address_local (const char *address);
static gboolean request_auth_info (gpointer data);
static void lookup_auth_info (gpointer data);
static void avahi_request_printer_list (GtkPrintBackendCups *cups_backend);
static void secrets_service_appeared_cb (GDBusConnection *connection,
const char *name,
const char *name_owner,
gpointer user_data);
static void secrets_service_vanished_cb (GDBusConnection *connection,
const char *name,
gpointer user_data);
G_DEFINE_DYNAMIC_TYPE(GtkPrintBackendCups, gtk_print_backend_cups, GTK_TYPE_PRINT_BACKEND)
void
g_io_module_load (GIOModule *module)
{
g_type_module_use (G_TYPE_MODULE (module));
gtk_print_backend_cups_register_type (G_TYPE_MODULE (module));
gtk_printer_cups_register_type (G_TYPE_MODULE (module));
g_io_extension_point_implement (GTK_PRINT_BACKEND_EXTENSION_POINT_NAME,
GTK_TYPE_PRINT_BACKEND_CUPS,
"cups",
10);
}
void
g_io_module_unload (GIOModule *module)
{
}
char **
g_io_module_query (void)
{
char *eps[] = {
(char *)GTK_PRINT_BACKEND_EXTENSION_POINT_NAME,
NULL
};
return g_strdupv (eps);
}
/*
* GtkPrintBackendCups
*/
/**
* gtk_print_backend_cups_new:
*
* Creates a new #GtkPrintBackendCups object. #GtkPrintBackendCups
* implements the #GtkPrintBackend interface with direct access to
* the filesystem using Unix/Linux API calls
*
* Returns: the new #GtkPrintBackendCups object
*/
GtkPrintBackend *
gtk_print_backend_cups_new (void)
{
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: Creating a new CUPS print backend object\n"));
return g_object_new (GTK_TYPE_PRINT_BACKEND_CUPS, NULL);
}
static void
gtk_print_backend_cups_class_init (GtkPrintBackendCupsClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
GtkPrintBackendClass *backend_class = GTK_PRINT_BACKEND_CLASS (class);
backend_parent_class = g_type_class_peek_parent (class);
gobject_class->finalize = gtk_print_backend_cups_finalize;
gobject_class->dispose = gtk_print_backend_cups_dispose;
backend_class->request_printer_list = cups_get_printer_list;
backend_class->print_stream = gtk_print_backend_cups_print_stream;
backend_class->printer_request_details = cups_printer_request_details;
backend_class->printer_create_cairo_surface = cups_printer_create_cairo_surface;
backend_class->printer_get_options = cups_printer_get_options;
backend_class->printer_mark_conflicts = cups_printer_mark_conflicts;
backend_class->printer_get_settings_from_options = cups_printer_get_settings_from_options;
backend_class->printer_prepare_for_print = cups_printer_prepare_for_print;
backend_class->printer_list_papers = cups_printer_list_papers;
backend_class->printer_get_default_page_size = cups_printer_get_default_page_size;
backend_class->printer_get_hard_margins = cups_printer_get_hard_margins;
backend_class->printer_get_hard_margins_for_paper_size = cups_printer_get_hard_margins_for_paper_size;
backend_class->printer_get_capabilities = cups_printer_get_capabilities;
backend_class->set_password = gtk_print_backend_cups_set_password;
}
static void
gtk_print_backend_cups_class_finalize (GtkPrintBackendCupsClass *class)
{
}
static gboolean
option_is_ipp_option (GtkPrinterOption *option)
{
gpointer data = g_object_get_data (G_OBJECT (option), "is-ipp-option");
if (data != NULL)
return GPOINTER_TO_UINT (data) != 0;
else
return FALSE;
}
static void
option_set_is_ipp_option (GtkPrinterOption *option,
gboolean is_ipp_option)
{
g_object_set_data (G_OBJECT (option),
"is-ipp-option",
GUINT_TO_POINTER (is_ipp_option ? 1 : 0));
}
static cairo_status_t
_cairo_write_to_cups (void *closure,
const unsigned char *data,
unsigned int length)
{
GIOChannel *io = (GIOChannel *)closure;
gsize written;
GError *error;
error = NULL;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: Writing %i byte chunk to temp file\n", length));
while (length > 0)
{
g_io_channel_write_chars (io, (char *)data, length, &written, &error);
if (error != NULL)
{
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: Error writing to temp file, %s\n",
error->message));
g_error_free (error);
return CAIRO_STATUS_WRITE_ERROR;
}
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: Wrote %"G_GSIZE_FORMAT" bytes to temp file\n", written));
data += written;
length -= written;
}
return CAIRO_STATUS_SUCCESS;
}
static cairo_surface_t *
cups_printer_create_cairo_surface (GtkPrinter *printer,
GtkPrintSettings *settings,
double width,
double height,
GIOChannel *cache_io)
{
cairo_surface_t *surface;
ppd_file_t *ppd_file = NULL;
ppd_attr_t *ppd_attr = NULL;
ppd_attr_t *ppd_attr_res = NULL;
ppd_attr_t *ppd_attr_screen_freq = NULL;
ppd_attr_t *ppd_attr_res_screen_freq = NULL;
char *res_string = NULL;
int level = 2;
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
if (gtk_printer_accepts_pdf (printer))
surface = cairo_pdf_surface_create_for_stream (_cairo_write_to_cups, cache_io, width, height);
else
surface = cairo_ps_surface_create_for_stream (_cairo_write_to_cups, cache_io, width, height);
ppd_file = gtk_printer_cups_get_ppd (GTK_PRINTER_CUPS (printer));
if (ppd_file != NULL)
{
ppd_attr = ppdFindAttr (ppd_file, "LanguageLevel", NULL);
if (ppd_attr != NULL)
level = atoi (ppd_attr->value);
if (gtk_print_settings_get_resolution (settings) == 0)
{
ppd_attr_res = ppdFindAttr (ppd_file, "DefaultResolution", NULL);
if (ppd_attr_res != NULL)
{
int res, res_x, res_y;
if (sscanf (ppd_attr_res->value, "%dx%ddpi", &res_x, &res_y) == 2)
{
if (res_x > 0 && res_y > 0)
gtk_print_settings_set_resolution_xy (settings, res_x, res_y);
}
else if (sscanf (ppd_attr_res->value, "%ddpi", &res) == 1)
{
if (res > 0)
gtk_print_settings_set_resolution (settings, res);
}
}
}
res_string = g_strdup_printf ("%ddpi",
gtk_print_settings_get_resolution (settings));
ppd_attr_res_screen_freq = ppdFindAttr (ppd_file, "ResScreenFreq", res_string);
g_free (res_string);
if (ppd_attr_res_screen_freq == NULL)
{
res_string = g_strdup_printf ("%dx%ddpi",
gtk_print_settings_get_resolution_x (settings),
gtk_print_settings_get_resolution_y (settings));
ppd_attr_res_screen_freq = ppdFindAttr (ppd_file, "ResScreenFreq", res_string);
g_free (res_string);
}
ppd_attr_screen_freq = ppdFindAttr (ppd_file, "ScreenFreq", NULL);
if (ppd_attr_res_screen_freq != NULL && atof (ppd_attr_res_screen_freq->value) > 0.0)
gtk_print_settings_set_printer_lpi (settings, atof (ppd_attr_res_screen_freq->value));
else if (ppd_attr_screen_freq != NULL && atof (ppd_attr_screen_freq->value) > 0.0)
gtk_print_settings_set_printer_lpi (settings, atof (ppd_attr_screen_freq->value));
}
if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_PS)
{
if (level == 2)
cairo_ps_surface_restrict_to_level (surface, CAIRO_PS_LEVEL_2);
if (level == 3)
cairo_ps_surface_restrict_to_level (surface, CAIRO_PS_LEVEL_3);
}
cairo_surface_set_fallback_resolution (surface,
2.0 * gtk_print_settings_get_printer_lpi (settings),
2.0 * gtk_print_settings_get_printer_lpi (settings));
G_GNUC_END_IGNORE_DEPRECATIONS
return surface;
}
typedef struct {
GtkPrintJobCompleteFunc callback;
GtkPrintJob *job;
gpointer user_data;
GDestroyNotify dnotify;
http_t *http;
} CupsPrintStreamData;
static void
cups_free_print_stream_data (CupsPrintStreamData *data)
{
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
if (data->dnotify)
data->dnotify (data->user_data);
g_object_unref (data->job);
if (data->http != NULL)
httpClose (data->http);
g_free (data);
}
static void
cups_print_cb (GtkPrintBackendCups *print_backend,
GtkCupsResult *result,
gpointer user_data)
{
GError *error = NULL;
CupsPrintStreamData *ps = user_data;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
if (gtk_cups_result_is_error (result))
error = g_error_new_literal (gtk_print_error_quark (),
GTK_PRINT_ERROR_INTERNAL_ERROR,
gtk_cups_result_get_error_string (result));
if (ps->callback)
ps->callback (ps->job, ps->user_data, error);
if (error == NULL)
{
int job_id = 0;
ipp_attribute_t *attr; /* IPP job-id attribute */
ipp_t *response = gtk_cups_result_get_response (result);
if ((attr = ippFindAttribute (response, "job-id", IPP_TAG_INTEGER)) != NULL)
job_id = ippGetInteger (attr, 0);
if (!gtk_print_job_get_track_print_status (ps->job) || job_id == 0)
gtk_print_job_set_status (ps->job, GTK_PRINT_STATUS_FINISHED);
else
{
gtk_print_job_set_status (ps->job, GTK_PRINT_STATUS_PENDING);
cups_begin_polling_info (print_backend, ps->job, job_id);
}
}
else
gtk_print_job_set_status (ps->job, GTK_PRINT_STATUS_FINISHED_ABORTED);
if (error)
g_error_free (error);
}
typedef struct {
GtkCupsRequest *request;
GtkPageSetup *page_setup;
GtkPrinterCups *printer;
} CupsOptionsData;
#define UNSIGNED_FLOAT_REGEX "([0-9]+([.,][0-9]*)?|[.,][0-9]+)([e][+-]?[0-9]+)?"
#define SIGNED_FLOAT_REGEX "[+-]?"UNSIGNED_FLOAT_REGEX
#define SIGNED_INTEGER_REGEX "[+-]?([0-9]+)"
static void
add_cups_options (const char *key,
const char *value,
gpointer user_data)
{
CupsOptionsData *data = (CupsOptionsData *) user_data;
GtkCupsRequest *request = data->request;
GtkPrinterCups *printer = data->printer;
gboolean custom_value = FALSE;
char *new_value = NULL;
int i;
if (!key || !value)
return;
if (!g_str_has_prefix (key, "cups-"))
return;
if (strcmp (value, "gtk-ignore-value") == 0)
return;
key = key + strlen ("cups-");
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
if (printer && printer->ppd_file && !g_str_has_prefix (value, "Custom."))
{
ppd_coption_t *coption;
gboolean found = FALSE;
gboolean custom_values_enabled = FALSE;
coption = ppdFindCustomOption (printer->ppd_file, key);
if (coption && coption->option)
{
for (i = 0; i < coption->option->num_choices; i++)
{
/* Are custom values enabled ? */
if (g_str_equal (coption->option->choices[i].choice, "Custom"))
custom_values_enabled = TRUE;
/* Is the value among available choices ? */
if (g_str_equal (coption->option->choices[i].choice, value))
found = TRUE;
}
if (custom_values_enabled && !found)
{
/* Check syntax of the invalid choice to see whether
it could be a custom value */
if (g_str_equal (key, "PageSize") ||
g_str_equal (key, "PageRegion"))
{
/* Handle custom page sizes... */
if (g_regex_match_simple ("^" UNSIGNED_FLOAT_REGEX "x" UNSIGNED_FLOAT_REGEX "(cm|mm|m|in|ft|pt)?$", value, G_REGEX_CASELESS, 0))
custom_value = TRUE;
else
{
if (data->page_setup != NULL)
{
custom_value = TRUE;
new_value =
g_strdup_printf ("Custom.%.2fx%.2fmm",
gtk_paper_size_get_width (gtk_page_setup_get_paper_size (data->page_setup), GTK_UNIT_MM),
gtk_paper_size_get_height (gtk_page_setup_get_paper_size (data->page_setup), GTK_UNIT_MM));
}
}
}
else
{
/* Handle other custom options... */
ppd_cparam_t *cparam;
cparam = (ppd_cparam_t *) cupsArrayFirst (coption->params);
if (cparam != NULL)
{
switch (cparam->type)
{
case PPD_CUSTOM_CURVE :
case PPD_CUSTOM_INVCURVE :
case PPD_CUSTOM_REAL :
if (g_regex_match_simple ("^" SIGNED_FLOAT_REGEX "$", value, G_REGEX_CASELESS, 0))
custom_value = TRUE;
break;
case PPD_CUSTOM_POINTS :
if (g_regex_match_simple ("^" SIGNED_FLOAT_REGEX "(cm|mm|m|in|ft|pt)?$", value, G_REGEX_CASELESS, 0))
custom_value = TRUE;
break;
case PPD_CUSTOM_INT :
if (g_regex_match_simple ("^" SIGNED_INTEGER_REGEX "$", value, G_REGEX_CASELESS, 0))
custom_value = TRUE;
break;
case PPD_CUSTOM_PASSCODE :
case PPD_CUSTOM_PASSWORD :
case PPD_CUSTOM_STRING :
custom_value = TRUE;
break;
case PPD_CUSTOM_UNKNOWN:
default :
custom_value = FALSE;
}
}
}
}
}
}
G_GNUC_END_IGNORE_DEPRECATIONS
/* Add "Custom." prefix to custom values if not already added. */
if (custom_value)
{
if (new_value == NULL)
new_value = g_strdup_printf ("Custom.%s", value);
gtk_cups_request_encode_option (request, key, new_value);
g_free (new_value);
}
else
gtk_cups_request_encode_option (request, key, value);
}
static void
gtk_print_backend_cups_print_stream (GtkPrintBackend *print_backend,
GtkPrintJob *job,
GIOChannel *data_io,
GtkPrintJobCompleteFunc callback,
gpointer user_data,
GDestroyNotify dnotify)
{
GtkPrinterCups *cups_printer;
CupsPrintStreamData *ps;
CupsOptionsData *options_data;
GtkPageSetup *page_setup;
GtkCupsRequest *request = NULL;
GtkPrintSettings *settings;
const char *title;
char printer_absolute_uri[HTTP_MAX_URI];
http_t *http = NULL;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
cups_printer = GTK_PRINTER_CUPS (gtk_print_job_get_printer (job));
settings = gtk_print_job_get_settings (job);
if (cups_printer->avahi_browsed)
{
http = httpConnect2 (cups_printer->hostname, cups_printer->port,
NULL, AF_UNSPEC,
HTTP_ENCRYPTION_IF_REQUESTED,
1, 30000,
NULL);
if (http)
{
request = gtk_cups_request_new_with_username (http,
GTK_CUPS_POST,
IPP_PRINT_JOB,
data_io,
cups_printer->hostname,
cups_printer->device_uri,
GTK_PRINT_BACKEND_CUPS (print_backend)->username);
g_snprintf (printer_absolute_uri, HTTP_MAX_URI, "%s", cups_printer->printer_uri);
}
else
{
GError *error = NULL;
GTK_NOTE (PRINTING,
g_warning ("CUPS Backend: Error connecting to %s:%d",
cups_printer->hostname,
cups_printer->port));
error = g_error_new (gtk_print_error_quark (),
GTK_CUPS_ERROR_GENERAL,
"Error connecting to %s",
cups_printer->hostname);
gtk_print_job_set_status (job, GTK_PRINT_STATUS_FINISHED_ABORTED);
if (callback)
{
callback (job, user_data, error);
}
g_clear_error (&error);
return;
}
}
else
{
request = gtk_cups_request_new_with_username (NULL,
GTK_CUPS_POST,
IPP_PRINT_JOB,
data_io,
NULL,
cups_printer->device_uri,
GTK_PRINT_BACKEND_CUPS (print_backend)->username);
httpAssembleURIf (HTTP_URI_CODING_ALL,
printer_absolute_uri,
sizeof (printer_absolute_uri),
"ipp",
NULL,
"localhost",
ippPort (),
"/printers/%s",
gtk_printer_get_name (gtk_print_job_get_printer (job)));
}
gtk_cups_request_set_ipp_version (request,
cups_printer->ipp_version_major,
cups_printer->ipp_version_minor);
gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION,
IPP_TAG_URI, "printer-uri",
NULL, printer_absolute_uri);
title = gtk_print_job_get_title (job);
if (title) {
char *title_truncated = NULL;
size_t title_bytes = strlen (title);
if (title_bytes >= IPP_MAX_NAME)
{
char *end;
end = g_utf8_find_prev_char (title, title + IPP_MAX_NAME - 1);
title_truncated = g_utf8_substring (title,
0,
g_utf8_pointer_to_offset (title, end));
}
gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION,
IPP_TAG_NAME, "job-name",
NULL,
title_truncated ? title_truncated : title);
g_free (title_truncated);
}
g_object_get (job,
"page-setup", &page_setup,
NULL);
options_data = g_new0 (CupsOptionsData, 1);
options_data->request = request;
options_data->printer = cups_printer;
options_data->page_setup = page_setup;
gtk_print_settings_foreach (settings, add_cups_options, options_data);
g_clear_object (&page_setup);
g_free (options_data);
ps = g_new0 (CupsPrintStreamData, 1);
ps->callback = callback;
ps->user_data = user_data;
ps->dnotify = dnotify;
ps->job = g_object_ref (job);
ps->http = http;
request->need_auth_info = FALSE;
request->auth_info_required = NULL;
/* Check if auth_info_required is set and if it should be handled.
* The cups libraries handle the ticket exchange for "negotiate". */
if (cups_printer->auth_info_required != NULL &&
g_strv_length (cups_printer->auth_info_required) == 1 &&
g_strcmp0 (cups_printer->auth_info_required[0], "negotiate") == 0)
{
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: Ignoring auth-info-required \"%s\"\n",
cups_printer->auth_info_required[0]));
}
else if (cups_printer->auth_info_required != NULL)
{
request->need_auth_info = TRUE;
request->auth_info_required = g_strdupv (cups_printer->auth_info_required);
}
cups_request_execute (GTK_PRINT_BACKEND_CUPS (print_backend),
request,
(GtkPrintCupsResponseCallbackFunc) cups_print_cb,
ps,
(GDestroyNotify)cups_free_print_stream_data);
}
void overwrite_and_free (gpointer data)
{
char *password = (char *) data;
if (password != NULL)
{
memset (password, 0, strlen (password));
g_free (password);
}
}
static void
gtk_print_backend_cups_init (GtkPrintBackendCups *backend_cups)
{
int i;
backend_cups->list_printers_poll = FALSE;
backend_cups->got_default_printer = FALSE;
backend_cups->list_printers_pending = FALSE;
backend_cups->list_printers_attempts = 0;
backend_cups->reading_ppds = 0;
backend_cups->requests = NULL;
backend_cups->auth = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, overwrite_and_free);
backend_cups->authentication_lock = FALSE;
backend_cups->default_printer_poll = 0;
backend_cups->cups_connection_test = NULL;
backend_cups->username = NULL;
#ifdef HAVE_COLORD
backend_cups->colord_client = cd_client_new ();
#endif
backend_cups->dbus_connection = NULL;
backend_cups->avahi_default_printer = NULL;
backend_cups->avahi_service_browser_subscription_id = 0;
for (i = 0; i < 2; i++)
{
backend_cups->avahi_service_browser_paths[i] = NULL;
backend_cups->avahi_service_browser_subscription_ids[i] = 0;
}
cups_get_local_default_printer (backend_cups);
backend_cups->secrets_service_available = FALSE;
backend_cups->secrets_service_cancellable = g_cancellable_new ();
backend_cups->secrets_service_watch_id =
gtk_cups_secrets_service_watch (secrets_service_appeared_cb,
secrets_service_vanished_cb,
backend_cups);
}
static void
gtk_print_backend_cups_finalize (GObject *object)
{
GtkPrintBackendCups *backend_cups;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: finalizing CUPS backend module\n"));
backend_cups = GTK_PRINT_BACKEND_CUPS (object);
g_free (backend_cups->default_printer);
backend_cups->default_printer = NULL;
gtk_cups_connection_test_free (backend_cups->cups_connection_test);
backend_cups->cups_connection_test = NULL;
g_hash_table_destroy (backend_cups->auth);
g_free (backend_cups->username);
#ifdef HAVE_COLORD
g_object_unref (backend_cups->colord_client);
#endif
g_clear_object (&backend_cups->avahi_cancellable);
g_clear_pointer (&backend_cups->avahi_default_printer, g_free);
g_clear_object (&backend_cups->dbus_connection);
g_clear_object (&backend_cups->secrets_service_cancellable);
if (backend_cups->secrets_service_watch_id != 0)
{
g_bus_unwatch_name (backend_cups->secrets_service_watch_id);
}
backend_parent_class->finalize (object);
}
static void
gtk_print_backend_cups_dispose (GObject *object)
{
GtkPrintBackendCups *backend_cups;
int i;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
backend_cups = GTK_PRINT_BACKEND_CUPS (object);
if (backend_cups->list_printers_poll > 0)
g_source_remove (backend_cups->list_printers_poll);
backend_cups->list_printers_poll = 0;
backend_cups->list_printers_attempts = 0;
if (backend_cups->default_printer_poll > 0)
g_source_remove (backend_cups->default_printer_poll);
backend_cups->default_printer_poll = 0;
g_cancellable_cancel (backend_cups->avahi_cancellable);
for (i = 0; i < 2; i++)
{
if (backend_cups->avahi_service_browser_subscription_ids[i] > 0)
{
g_dbus_connection_signal_unsubscribe (backend_cups->dbus_connection,
backend_cups->avahi_service_browser_subscription_ids[i]);
backend_cups->avahi_service_browser_subscription_ids[i] = 0;
}
if (backend_cups->avahi_service_browser_paths[i])
{
g_dbus_connection_call (backend_cups->dbus_connection,
AVAHI_BUS,
backend_cups->avahi_service_browser_paths[i],
AVAHI_SERVICE_BROWSER_IFACE,
"Free",
NULL,
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
NULL,
NULL);
g_clear_pointer (&backend_cups->avahi_service_browser_paths[i], g_free);
}
}
if (backend_cups->avahi_service_browser_subscription_id > 0)
{
g_dbus_connection_signal_unsubscribe (backend_cups->dbus_connection,
backend_cups->avahi_service_browser_subscription_id);
backend_cups->avahi_service_browser_subscription_id = 0;
}
backend_parent_class->dispose (object);
}
static gboolean
is_address_local (const char *address)
{
if (address[0] == '/' ||
strcmp (address, "127.0.0.1") == 0 ||
strcmp (address, "[::1]") == 0)
return TRUE;
else
return FALSE;
}
static void
gtk_print_backend_cups_set_password (GtkPrintBackend *backend,
char **auth_info_required,
char **auth_info,
gboolean store_auth_info)
{
GtkPrintBackendCups *cups_backend = GTK_PRINT_BACKEND_CUPS (backend);
GList *l;
char dispatch_hostname[HTTP_MAX_URI];
char *username = NULL;
char *hostname = NULL;
char *password = NULL;
int length;
int i;
length = g_strv_length (auth_info_required);
if (auth_info != NULL)
for (i = 0; i < length; i++)
{
if (g_strcmp0 (auth_info_required[i], "username") == 0)
username = g_strdup (auth_info[i]);
else if (g_strcmp0 (auth_info_required[i], "hostname") == 0)
hostname = g_strdup (auth_info[i]);
else if (g_strcmp0 (auth_info_required[i], "password") == 0)
password = g_strdup (auth_info[i]);
}
if (hostname != NULL && username != NULL && password != NULL)
{
char *key = g_strconcat (username, "@", hostname, NULL);
g_hash_table_insert (cups_backend->auth, key, g_strdup (password));
GTK_NOTE (PRINTING,
g_print ("CUPS backend: caching password for %s\n", key));
}
g_free (cups_backend->username);
cups_backend->username = g_strdup (username);
for (l = cups_backend->requests; l; l = l->next)
{
GtkPrintCupsDispatchWatch *dispatch = l->data;
httpGetHostname (dispatch->request->http, dispatch_hostname, sizeof (dispatch_hostname));
if (is_address_local (dispatch_hostname))
strcpy (dispatch_hostname, "localhost");
if (dispatch->request->need_auth_info)
{
if (auth_info != NULL)
{
dispatch->request->auth_info = g_new0 (char *, length + 1);
for (i = 0; i < length; i++)
dispatch->request->auth_info[i] = g_strdup (auth_info[i]);
}
/* Save the password if the user requested it */
if (password != NULL && store_auth_info)
{
const char *printer_uri =
gtk_cups_request_ipp_get_string (dispatch->request,
IPP_TAG_URI,
"printer-uri");
gtk_cups_secrets_service_store (auth_info, auth_info_required,
printer_uri);
}
dispatch->backend->authentication_lock = FALSE;
dispatch->request->need_auth_info = FALSE;
}
else if (dispatch->request->password_state == GTK_CUPS_PASSWORD_REQUESTED || auth_info == NULL)
{
overwrite_and_free (dispatch->request->password);
dispatch->request->password = g_strdup (password);
g_free (dispatch->request->username);
dispatch->request->username = g_strdup (username);
dispatch->request->password_state = GTK_CUPS_PASSWORD_HAS;
dispatch->backend->authentication_lock = FALSE;
}
}
}
static gboolean
request_password (gpointer data)
{
GtkPrintCupsDispatchWatch *dispatch = data;
const char *username;
char *password;
char *prompt = NULL;
char *key = NULL;
char hostname[HTTP_MAX_URI];
char **auth_info_required;
char **auth_info_default;
char **auth_info_display;
gboolean *auth_info_visible;
int length = 3;
int i;
if (dispatch->backend->authentication_lock)
return G_SOURCE_REMOVE;
httpGetHostname (dispatch->request->http, hostname, sizeof (hostname));
if (is_address_local (hostname))
strcpy (hostname, "localhost");
if (dispatch->backend->username != NULL)
username = dispatch->backend->username;
else
username = cupsUser ();
auth_info_required = g_new0 (char *, length + 1);
auth_info_required[0] = g_strdup ("hostname");
auth_info_required[1] = g_strdup ("username");
auth_info_required[2] = g_strdup ("password");
auth_info_default = g_new0 (char *, length + 1);
auth_info_default[0] = g_strdup (hostname);
auth_info_default[1] = g_strdup (username);
auth_info_display = g_new0 (char *, length + 1);
auth_info_display[1] = g_strdup (_("Username:"));
auth_info_display[2] = g_strdup (_("Password:"));
auth_info_visible = g_new0 (gboolean, length + 1);
auth_info_visible[1] = TRUE;
key = g_strconcat (username, "@", hostname, NULL);
password = g_hash_table_lookup (dispatch->backend->auth, key);
if (password && dispatch->request->password_state != GTK_CUPS_PASSWORD_NOT_VALID)
{
GTK_NOTE (PRINTING,
g_print ("CUPS backend: using stored password for %s\n", key));
overwrite_and_free (dispatch->request->password);
dispatch->request->password = g_strdup (password);
g_free (dispatch->request->username);
dispatch->request->username = g_strdup (username);
dispatch->request->password_state = GTK_CUPS_PASSWORD_HAS;
}
else
{
const char *job_title = gtk_cups_request_ipp_get_string (dispatch->request, IPP_TAG_NAME, "job-name");
const char *printer_uri = gtk_cups_request_ipp_get_string (dispatch->request, IPP_TAG_URI, "printer-uri");
char *printer_name = NULL;
if (printer_uri != NULL && strrchr (printer_uri, '/') != NULL)
printer_name = g_strdup (strrchr (printer_uri, '/') + 1);
if (dispatch->request->password_state == GTK_CUPS_PASSWORD_NOT_VALID)
g_hash_table_remove (dispatch->backend->auth, key);
dispatch->request->password_state = GTK_CUPS_PASSWORD_REQUESTED;
dispatch->backend->authentication_lock = TRUE;
switch ((guint)ippGetOperation (dispatch->request->ipp_request))
{
case IPP_PRINT_JOB:
if (job_title != NULL && printer_name != NULL)
prompt = g_strdup_printf ( _("Authentication is required to print document “%s” on printer %s"), job_title, printer_name);
else
prompt = g_strdup_printf ( _("Authentication is required to print a document on %s"), hostname);
break;
case IPP_GET_JOB_ATTRIBUTES:
if (job_title != NULL)
prompt = g_strdup_printf ( _("Authentication is required to get attributes of job “%s”"), job_title);
else
prompt = g_strdup ( _("Authentication is required to get attributes of a job"));
break;
case IPP_GET_PRINTER_ATTRIBUTES:
if (printer_name != NULL)
prompt = g_strdup_printf ( _("Authentication is required to get attributes of printer %s"), printer_name);
else
prompt = g_strdup ( _("Authentication is required to get attributes of a printer"));
break;
case CUPS_GET_DEFAULT:
prompt = g_strdup_printf ( _("Authentication is required to get default printer of %s"), hostname);
break;
case CUPS_GET_PRINTERS:
prompt = g_strdup_printf ( _("Authentication is required to get printers from %s"), hostname);
break;
default:
/* work around gcc warning about 0 not being a value for this enum */
if (ippGetOperation (dispatch->request->ipp_request) == 0)
prompt = g_strdup_printf ( _("Authentication is required to get a file from %s"), hostname);
else
prompt = g_strdup_printf ( _("Authentication is required on %s"), hostname);
break;
}
g_free (printer_name);
g_signal_emit_by_name (dispatch->backend, "request-password",
auth_info_required, auth_info_default,
auth_info_display, auth_info_visible, prompt,
FALSE); /* Cups password is only cached not stored. */
g_free (prompt);
}
for (i = 0; i < length; i++)
{
g_free (auth_info_required[i]);
g_free (auth_info_default[i]);
g_free (auth_info_display[i]);
}
g_free (auth_info_required);
g_free (auth_info_default);
g_free (auth_info_display);
g_free (auth_info_visible);
g_free (key);
return G_SOURCE_REMOVE;
}
static void
cups_dispatch_add_poll (GSource *source)
{
GtkPrintCupsDispatchWatch *dispatch;
GtkCupsPollState poll_state;
dispatch = (GtkPrintCupsDispatchWatch *) source;
poll_state = gtk_cups_request_get_poll_state (dispatch->request);
/* Remove the old source if the poll state changed. */
if (poll_state != dispatch->poll_state && dispatch->data_poll != NULL)
{
g_source_remove_poll (source, dispatch->data_poll);
g_free (dispatch->data_poll);
dispatch->data_poll = NULL;
}
if (dispatch->request->http != NULL)
{
if (dispatch->data_poll == NULL)
{
dispatch->data_poll = g_new0 (GPollFD, 1);
dispatch->poll_state = poll_state;
if (poll_state == GTK_CUPS_HTTP_READ)
dispatch->data_poll->events = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI;
else if (poll_state == GTK_CUPS_HTTP_WRITE)
dispatch->data_poll->events = G_IO_OUT | G_IO_ERR;
else
dispatch->data_poll->events = 0;
dispatch->data_poll->fd = httpGetFd (dispatch->request->http);
g_source_add_poll (source, dispatch->data_poll);
}
}
}
static gboolean
check_auth_info (gpointer user_data)
{
GtkPrintCupsDispatchWatch *dispatch;
dispatch = (GtkPrintCupsDispatchWatch *) user_data;
if (!dispatch->request->need_auth_info)
{
if (dispatch->request->auth_info == NULL)
{
dispatch->callback (GTK_PRINT_BACKEND (dispatch->backend),
gtk_cups_request_get_result (dispatch->request),
dispatch->callback_data);
g_source_destroy ((GSource *) dispatch);
}
else
{
int length;
int i;
length = g_strv_length (dispatch->request->auth_info_required);
gtk_cups_request_ipp_add_strings (dispatch->request,
IPP_TAG_JOB,
IPP_TAG_TEXT,
"auth-info",
length,
NULL,
(const char * const *) dispatch->request->auth_info);
g_source_attach ((GSource *) dispatch, NULL);
g_source_unref ((GSource *) dispatch);
for (i = 0; i < length; i++)
overwrite_and_free (dispatch->request->auth_info[i]);
g_free (dispatch->request->auth_info);
dispatch->request->auth_info = NULL;
}
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
}
static void
lookup_auth_info_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task;
GtkPrintCupsDispatchWatch *dispatch;
char **auth_info;
GError *error = NULL;
int i;
task = (GTask *) res;
dispatch = user_data;
auth_info = g_task_propagate_pointer (task, &error);
if (auth_info == NULL)
{
if (error != NULL)
{
GTK_NOTE (PRINTING,
g_print ("Failed to look up auth info: %s\n", error->message));
g_error_free (error);
}
else
{
/* Error note should have been shown by the function causing this */
GTK_NOTE (PRINTING, g_print ("Failed to look up auth info.\n"));
}
dispatch->backend->authentication_lock = FALSE;
g_object_unref (task);
request_auth_info (dispatch);
return;
}
gtk_print_backend_cups_set_password (GTK_PRINT_BACKEND (dispatch->backend),
dispatch->request->auth_info_required, auth_info,
FALSE);
for (i = 0; auth_info[i] != NULL; i++)
{
overwrite_and_free (auth_info[i]);
auth_info[i] = NULL;
}
g_clear_pointer (auth_info, g_free);
g_object_unref (task);
}
static void
lookup_auth_info (gpointer user_data)
{
GtkPrintCupsDispatchWatch *dispatch;
gsize length,
i;
gboolean need_secret_auth_info = FALSE;
const char *printer_uri;
dispatch = user_data;
if (dispatch->backend->authentication_lock)
return;
length = g_strv_length (dispatch->request->auth_info_required);
for (i = 0; i < length; i++)
{
if (g_strcmp0 (dispatch->request->auth_info_required[i], "password") == 0)
{
need_secret_auth_info = TRUE;
break;
}
}
g_idle_add (check_auth_info, user_data);
if (dispatch->backend->secrets_service_available && need_secret_auth_info)
{
dispatch->backend->authentication_lock = TRUE;
printer_uri = gtk_cups_request_ipp_get_string (dispatch->request,
IPP_TAG_URI,
"printer-uri");
gtk_cups_secrets_service_query_task (dispatch->backend,
dispatch->backend->secrets_service_cancellable,
lookup_auth_info_cb,
dispatch,
printer_uri,
dispatch->request->auth_info_required);
return;
}
request_auth_info (user_data);
}
static gboolean
request_auth_info (gpointer user_data)
{
GtkPrintCupsDispatchWatch *dispatch;
const char *job_title;
const char *printer_uri;
char *prompt = NULL;
char *printer_name = NULL;
int length;
int i;
gboolean *auth_info_visible = NULL;
char **auth_info_default = NULL;
char **auth_info_display = NULL;
dispatch = (GtkPrintCupsDispatchWatch *) user_data;
if (dispatch->backend->authentication_lock)
return FALSE;
job_title = gtk_cups_request_ipp_get_string (dispatch->request, IPP_TAG_NAME, "job-name");
printer_uri = gtk_cups_request_ipp_get_string (dispatch->request, IPP_TAG_URI, "printer-uri");
length = g_strv_length (dispatch->request->auth_info_required);
auth_info_visible = g_new0 (gboolean, length);
auth_info_default = g_new0 (char *, length + 1);
auth_info_display = g_new0 (char *, length + 1);
for (i = 0; i < length; i++)
{
if (g_strcmp0 (dispatch->request->auth_info_required[i], "domain") == 0)
{
auth_info_display[i] = g_strdup (_("Domain:"));
auth_info_default[i] = g_strdup ("WORKGROUP");
auth_info_visible[i] = TRUE;
}
else if (g_strcmp0 (dispatch->request->auth_info_required[i], "username") == 0)
{
auth_info_display[i] = g_strdup (_("Username:"));
if (dispatch->backend->username != NULL)
auth_info_default[i] = g_strdup (dispatch->backend->username);
else
auth_info_default[i] = g_strdup (cupsUser ());
auth_info_visible[i] = TRUE;
}
else if (g_strcmp0 (dispatch->request->auth_info_required[i], "password") == 0)
{
auth_info_display[i] = g_strdup (_("Password:"));
auth_info_visible[i] = FALSE;
}
}
if (printer_uri != NULL && strrchr (printer_uri, '/') != NULL)
printer_name = g_strdup (strrchr (printer_uri, '/') + 1);
dispatch->backend->authentication_lock = TRUE;
if (job_title != NULL)
{
if (printer_name != NULL)
prompt = g_strdup_printf ( _("Authentication is required to print document “%s” on printer %s"), job_title, printer_name);
else
prompt = g_strdup_printf ( _("Authentication is required to print document “%s”"), job_title);
}
else
{
if (printer_name != NULL)
prompt = g_strdup_printf ( _("Authentication is required to print this document on printer %s"), printer_name);
else
prompt = g_strdup ( _("Authentication is required to print this document"));
}
g_signal_emit_by_name (dispatch->backend, "request-password",
dispatch->request->auth_info_required,
auth_info_default,
auth_info_display,
auth_info_visible,
prompt,
dispatch->backend->secrets_service_available);
for (i = 0; i < length; i++)
{
g_free (auth_info_default[i]);
g_free (auth_info_display[i]);
}
g_free (auth_info_default);
g_free (auth_info_display);
g_free (printer_name);
g_free (prompt);
return FALSE;
}
static gboolean
cups_dispatch_watch_check (GSource *source)
{
GtkPrintCupsDispatchWatch *dispatch;
GtkCupsPollState poll_state;
gboolean result;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s