Index: gtk/gtkmarshalers.list =================================================================== --- gtk/gtkmarshalers.list (revision 22586) +++ gtk/gtkmarshalers.list (working copy) @@ -97,6 +97,7 @@ VOID:STRING VOID:STRING,BOXED VOID:STRING,STRING +VOID:STRING,STRING,STRING VOID:STRING,INT,POINTER VOID:STRING,UINT,FLAGS VOID:STRING,UINT,FLAGS,UINT Index: gtk/gtkprintbackend.c =================================================================== --- gtk/gtkprintbackend.c (revision 22586) +++ gtk/gtkprintbackend.c (working copy) @@ -25,6 +25,7 @@ #include "gtkintl.h" #include "gtkmodules.h" +#include "gtkmarshalers.h" #include "gtkprivate.h" #include "gtkprintbackend.h" #include "gtkprinter-private.h" @@ -49,6 +50,9 @@ guint printer_list_requested : 1; guint printer_list_done : 1; GtkPrintBackendStatus status; + char *hostname; + char *username; + char *password; }; enum { @@ -57,6 +61,7 @@ PRINTER_ADDED, PRINTER_REMOVED, PRINTER_STATUS_CHANGED, + REQUEST_PASSWORD, LAST_SIGNAL }; @@ -353,6 +358,10 @@ static GList * fallback_printer_list_papers (GtkPrinter *printer); static GtkPageSetup * fallback_printer_get_default_page_size (GtkPrinter *printer); static GtkPrintCapabilities fallback_printer_get_capabilities (GtkPrinter *printer); +static void request_password (GtkPrintBackend *backend, + const gchar *hostname, + const gchar *username, + const gchar *prompt); static void gtk_print_backend_class_init (GtkPrintBackendClass *class) @@ -372,6 +381,7 @@ class->printer_list_papers = fallback_printer_list_papers; class->printer_get_default_page_size = fallback_printer_get_default_page_size; class->printer_get_capabilities = fallback_printer_get_capabilities; + class->request_password = request_password; g_object_class_install_property (object_class, PROP_STATUS, @@ -425,6 +435,14 @@ NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GTK_TYPE_PRINTER); + signals[REQUEST_PASSWORD] = + g_signal_new (I_("request-password"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkPrintBackendClass, request_password), + NULL, NULL, + _gtk_marshal_VOID__STRING_STRING_STRING, + G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); } static void @@ -437,6 +455,9 @@ priv->printers = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref); + priv->hostname = NULL; + priv->username = NULL; + priv->password = NULL; } static void @@ -640,6 +661,167 @@ dnotify); } +void +gtk_print_backend_set_password (GtkPrintBackend *backend, + const gchar *hostname, + const gchar *username, + const gchar *password) +{ + g_return_if_fail (GTK_IS_PRINT_BACKEND (backend)); + + if (GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password) + GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password (backend, hostname, username, password); +} + +static void +store_password (GtkEntry *entry, + GtkPrintBackend *backend) +{ + GtkPrintBackendPrivate *priv = backend->priv; + + if (priv->password != NULL) + { + memset (priv->password, 0, strlen (priv->password)); + g_free (priv->password); + } + + priv->password = g_strdup (gtk_entry_get_text (entry)); +} + +static void +store_username (GtkEntry *entry, + GtkPrintBackend *backend) +{ + GtkPrintBackendPrivate *priv = backend->priv; + + g_free (priv->username); + priv->username = g_strdup (gtk_entry_get_text (entry)); +} + +static void +password_dialog_response (GtkWidget *dialog, + gint response_id, + GtkPrintBackend *backend) +{ + GtkPrintBackendPrivate *priv = backend->priv; + + if (response_id == GTK_RESPONSE_OK) + gtk_print_backend_set_password (backend, priv->hostname, priv->username, priv->password); + else + gtk_print_backend_set_password (backend, priv->hostname, priv->username, NULL); + + if (priv->password != NULL) + { + memset (priv->password, 0, strlen (priv->password)); + g_free (priv->password); + priv->password = NULL; + } + + g_free (priv->username); + priv->username = NULL; + + gtk_widget_destroy (dialog); + + g_object_unref (backend); +} + +static void +request_password (GtkPrintBackend *backend, + const gchar *hostname, + const gchar *username, + const gchar *prompt) +{ + GtkPrintBackendPrivate *priv = backend->priv; + GtkWidget *dialog, *username_box, *password_box, *main_box, *label, *icon, *vbox, + *password_prompt, *username_prompt, + *password_entry, *username_entry; + gchar *markup; + + dialog = gtk_dialog_new_with_buttons ( _("Authentication"), NULL, GTK_DIALOG_MODAL, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_OK, + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); + + main_box = gtk_hbox_new (FALSE, 0); + + /* Left */ + icon = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION, GTK_ICON_SIZE_DIALOG); + gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0.0); + gtk_misc_set_padding (GTK_MISC (icon), 6, 6); + + + /* Right */ + vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_set_size_request (GTK_WIDGET (vbox), 320, -1); + + /* Right - 1. */ + label = gtk_label_new (NULL); + markup = g_markup_printf_escaped ("%s", prompt); + gtk_label_set_markup (GTK_LABEL (label), markup); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_widget_set_size_request (GTK_WIDGET (label), 320, -1); + g_free (markup); + + + /* Right - 2. */ + username_box = gtk_hbox_new (TRUE, 0); + + username_prompt = gtk_label_new (_("Username:")); + gtk_misc_set_alignment (GTK_MISC (username_prompt), 0.0, 0.5); + + username_entry = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (username_entry), username); + + + /* Right - 3. */ + password_box = gtk_hbox_new (TRUE, 0); + + password_prompt = gtk_label_new (_("Password:")); + gtk_misc_set_alignment (GTK_MISC (password_prompt), 0.0, 0.5); + + password_entry = gtk_entry_new (); + gtk_entry_set_visibility (GTK_ENTRY (password_entry), FALSE); + gtk_entry_set_activates_default (GTK_ENTRY (password_entry), TRUE); + + + /* Packing */ + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), main_box, TRUE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (main_box), icon, FALSE, FALSE, 6); + gtk_box_pack_start (GTK_BOX (main_box), vbox, FALSE, FALSE, 6); + + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 6); + gtk_box_pack_start (GTK_BOX (vbox), username_box, FALSE, TRUE, 6); + gtk_box_pack_start (GTK_BOX (vbox), password_box, FALSE, TRUE, 6); + + gtk_box_pack_start (GTK_BOX (username_box), username_prompt, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (username_box), username_entry, TRUE, TRUE, 0); + + gtk_box_pack_start (GTK_BOX (password_box), password_prompt, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (password_box), password_entry, TRUE, TRUE, 0); + + + gtk_widget_grab_focus (password_entry); + + priv->hostname = g_strdup (hostname); + priv->username = g_strdup (username); + + g_signal_connect (password_entry, "changed", + G_CALLBACK (store_password), backend); + + g_signal_connect (username_entry, "changed", + G_CALLBACK (store_username), backend); + + g_object_ref (backend); + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (password_dialog_response), backend); + + gtk_widget_show_all (dialog); +} + void gtk_print_backend_destroy (GtkPrintBackend *print_backend) { Index: gtk/gtkprintbackend.h =================================================================== --- gtk/gtkprintbackend.h (revision 22586) +++ gtk/gtkprintbackend.h (working copy) @@ -120,14 +120,22 @@ GtkPrinter *printer); void (*printer_status_changed) (GtkPrintBackend *backend, GtkPrinter *printer); + void (*request_password) (GtkPrintBackend *backend, + const gchar *hostname, + const gchar *username, + const gchar *prompt); + /* not a signal */ + void (*set_password) (GtkPrintBackend *backend, + const gchar *hostname, + const gchar *username, + const gchar *password); + /* Padding for future expansion */ void (*_gtk_reserved1) (void); void (*_gtk_reserved2) (void); void (*_gtk_reserved3) (void); void (*_gtk_reserved4) (void); - void (*_gtk_reserved5) (void); - void (*_gtk_reserved6) (void); }; GType gtk_print_backend_get_type (void) G_GNUC_CONST; @@ -144,6 +152,10 @@ GDestroyNotify dnotify); GList * gtk_print_backend_load_modules (void); void gtk_print_backend_destroy (GtkPrintBackend *print_backend); +void gtk_print_backend_set_password (GtkPrintBackend *backend, + const gchar *hostname, + const gchar *username, + const gchar *password); /* Backend-only functions for GtkPrintBackend */ Index: gtk/gtkprintunixdialog.c =================================================================== --- gtk/gtkprintunixdialog.c (revision 22586) +++ gtk/gtkprintunixdialog.c (working copy) @@ -757,7 +757,10 @@ priv->print_backends = gtk_print_backend_load_modules (); for (node = priv->print_backends; node != NULL; node = node->next) - printer_list_initialize (dialog, GTK_PRINT_BACKEND (node->data)); + { + GtkPrintBackend *backend = node->data; + printer_list_initialize (dialog, backend); + } } static void Index: modules/printbackends/cups/gtkcupsutils.c =================================================================== --- modules/printbackends/cups/gtkcupsutils.c (revision 22586) +++ modules/printbackends/cups/gtkcupsutils.c (working copy) @@ -39,10 +39,12 @@ static void _post_write_request (GtkCupsRequest *request); static void _post_write_data (GtkCupsRequest *request); static void _post_check (GtkCupsRequest *request); +static void _post_auth (GtkCupsRequest *request); static void _post_read_response (GtkCupsRequest *request); static void _get_send (GtkCupsRequest *request); static void _get_check (GtkCupsRequest *request); +static void _get_auth (GtkCupsRequest *request); static void _get_read_data (GtkCupsRequest *request); struct _GtkCupsResult @@ -69,6 +71,7 @@ _post_write_request, _post_write_data, _post_check, + _post_auth, _post_read_response }; @@ -76,6 +79,7 @@ _connect, _get_send, _get_check, + _get_auth, _get_read_data }; @@ -101,12 +105,13 @@ } GtkCupsRequest * -gtk_cups_request_new (http_t *connection, - GtkCupsRequestType req_type, - gint operation_id, - GIOChannel *data_io, - const char *server, - const char *resource) +gtk_cups_request_new_with_username (http_t *connection, + GtkCupsRequestType req_type, + gint operation_id, + GIOChannel *data_io, + const char *server, + const char *resource, + const char *username) { GtkCupsRequest *request; cups_lang_t *language; @@ -123,6 +128,8 @@ request->type = req_type; request->state = GTK_CUPS_REQUEST_START; + request->password_state = GTK_CUPS_PASSWORD_NONE; + if (server) request->server = g_strdup (server); else @@ -171,15 +178,37 @@ "attributes-natural-language", NULL, language->language); - gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_NAME, - "requesting-user-name", - NULL, cupsUser ()); - + if (username != NULL) + gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requesting-user-name", + NULL, username); + else + gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requesting-user-name", + NULL, cupsUser ()); + cupsLangFree (language); return request; } +GtkCupsRequest * +gtk_cups_request_new (http_t *connection, + GtkCupsRequestType req_type, + gint operation_id, + GIOChannel *data_io, + const char *server, + const char *resource) +{ + return gtk_cups_request_new_with_username (connection, + req_type, + operation_id, + data_io, + server, + resource, + NULL); +} + static void gtk_cups_result_free (GtkCupsResult *result) { @@ -205,7 +234,14 @@ g_free (request->server); g_free (request->resource); + if (request->password != NULL) + { + memset (request->password, 0, strlen (request->password)); + g_free (request->password); + } + g_free (request->username); + gtk_cups_result_free (request->result); g_free (request); @@ -290,8 +326,25 @@ values); } +const char * +gtk_cups_request_ipp_get_string (GtkCupsRequest *request, + ipp_tag_t tag, + const char *name) +{ + ipp_attribute_t *attribute = NULL; + if (request != NULL && request->ipp_request != NULL) + attribute = ippFindAttribute (request->ipp_request, + name, + tag); + if (attribute != NULL && attribute->values != NULL) + return attribute->values[0].string.text; + else + return NULL; +} + + typedef struct { const char *name; @@ -786,12 +839,83 @@ return; } } - else + else if (http_status == HTTP_UNAUTHORIZED) { + request->state = GTK_CUPS_POST_CHECK; + request->poll_state = GTK_CUPS_HTTP_READ; + + request->attempts = 0; + return; + } + else + { request->attempts++; } } +static void +_post_auth (GtkCupsRequest *request) +{ + if (request->password_state == GTK_CUPS_PASSWORD_HAS) + { + if (request->password == NULL) + { + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_AUTH, + 0, + 1, + "Canceled by user"); + } + else + request->state = GTK_CUPS_POST_CHECK; + } +} + +static void +_get_auth (GtkCupsRequest *request) +{ + if (request->password_state == GTK_CUPS_PASSWORD_HAS) + { + if (request->password == NULL) + { + request->state = GTK_CUPS_GET_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_AUTH, + 0, + 1, + "Canceled by user"); + } + else + request->state = GTK_CUPS_GET_CHECK; + } +} + +/* Very ugly hack: cups has a stupid synchronous password callback + * that doesn't even take the request or user data parameters, so + * we have to use a static variable to pass the password to it. + * Not threadsafe ! + * The callback sets cups_password to NULL to signal that the + * password has been used. + */ +static char *cups_password; +static char *cups_username; + +static const char * +passwordCB (const char *prompt) +{ + char *pwd = cups_password; + cups_password = NULL; + + cupsSetUser (cups_username); + + return pwd; +} + static void _post_check (GtkCupsRequest *request) { @@ -810,18 +934,91 @@ } else if (http_status == HTTP_UNAUTHORIZED) { - /* TODO: callout for auth */ - g_warning ("NOT IMPLEMENTED: We need to prompt for authorization"); - request->state = GTK_CUPS_POST_DONE; - request->poll_state = GTK_CUPS_HTTP_IDLE; + int auth_result = -1; + httpFlush (request->http); + + if (request->password_state == GTK_CUPS_PASSWORD_APPLIED) + { + request->password_state = GTK_CUPS_PASSWORD_NOT_VALID; + request->state = GTK_CUPS_POST_AUTH; + request->need_password = TRUE; + + return; + } + + /* Negotiate */ + if (strncmp (httpGetField (request->http, HTTP_FIELD_WWW_AUTHENTICATE), "Negotiate", 9) == 0) + { + auth_result = cupsDoAuthentication (request->http, "POST", request->resource); + } + /* Basic, BasicDigest, Digest and PeerCred */ + else + { + if (request->password_state == GTK_CUPS_PASSWORD_NONE) + { + cups_password = g_strdup (""); + cups_username = request->username; + cupsSetPasswordCB (passwordCB); + + /* This call success for PeerCred authentication */ + auth_result = cupsDoAuthentication (request->http, "POST", request->resource); + + if (auth_result != 0) + { + /* move to AUTH state to let the backend + * ask for a password + */ + request->state = GTK_CUPS_POST_AUTH; + request->need_password = TRUE; + + return; + } + } + else + { + cups_password = request->password; + cups_username = request->username; + + auth_result = cupsDoAuthentication (request->http, "POST", request->resource); + + if (cups_password != NULL) + return; + + if (request->password != NULL) + { + memset (request->password, 0, strlen (request->password)); + g_free (request->password); + request->password = NULL; + } + + request->password_state = GTK_CUPS_PASSWORD_APPLIED; + } + } + + if (auth_result || + httpReconnect (request->http)) + { + /* if the password has been used, reset password_state + * so that we ask for a new one next time around + */ + if (cups_password == NULL) + request->password_state = GTK_CUPS_PASSWORD_NONE; + + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_AUTH, + 0, + 0, + "Not authorized"); + return; + } - /* TODO: create a not implemented error code */ - gtk_cups_result_set_error (request->result, - GTK_CUPS_ERROR_GENERAL, - 0, - 0, - "Can't prompt for authorization"); - return; + if (request->data_io != NULL) + g_io_channel_seek_position (request->data_io, 0, G_SEEK_SET, NULL); + + request->state = GTK_CUPS_POST_CONNECT; + request->poll_state = GTK_CUPS_HTTP_WRITE; } else if (http_status == HTTP_ERROR) { @@ -883,7 +1080,7 @@ http_errno, "HTTP Error in POST %s", g_strerror (http_errno)); - request->poll_state = GTK_CUPS_HTTP_IDLE; + request->poll_state = GTK_CUPS_HTTP_IDLE; httpFlush (request->http); return; @@ -975,9 +1172,13 @@ } httpClearFields (request->http); +#ifdef HAVE_HTTPGETAUTHSTRING + httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, httpGetAuthString (request->http)); +#else #ifdef HAVE_HTTP_AUTHSTRING httpSetField (request->http, HTTP_FIELD_AUTHORIZATION, request->http->authstring); #endif +#endif if (httpGet (request->http, request->resource)) { @@ -997,6 +1198,9 @@ request->attempts++; return; } + + if (httpCheck (request->http)) + request->last_status = httpUpdate (request->http); request->attempts = 0; @@ -1024,18 +1228,90 @@ } else if (http_status == HTTP_UNAUTHORIZED) { - /* TODO: callout for auth */ - g_warning ("NOT IMPLEMENTED: We need to prompt for authorization in a non blocking manner"); - request->state = GTK_CUPS_GET_DONE; - request->poll_state = GTK_CUPS_HTTP_IDLE; + int auth_result = -1; + httpFlush (request->http); - /* TODO: should add a status or error code for not implemented */ - gtk_cups_result_set_error (request->result, - GTK_CUPS_ERROR_GENERAL, - 0, - 0, - "Can't prompt for authorization"); - return; + if (request->password_state == GTK_CUPS_PASSWORD_APPLIED) + { + request->password_state = GTK_CUPS_PASSWORD_NOT_VALID; + request->state = GTK_CUPS_GET_AUTH; + request->need_password = TRUE; + + return; + } + + /* Negotiate */ + if (strncmp (httpGetField (request->http, HTTP_FIELD_WWW_AUTHENTICATE), "Negotiate", 9) == 0) + { + auth_result = cupsDoAuthentication (request->http, "GET", request->resource); + } + /* Basic, BasicDigest, Digest and PeerCred */ + else + { + if (request->password_state == GTK_CUPS_PASSWORD_NONE) + { + cups_password = g_strdup (""); + cups_username = request->username; + cupsSetPasswordCB (passwordCB); + + /* This call success for PeerCred authentication */ + auth_result = cupsDoAuthentication (request->http, "GET", request->resource); + + if (auth_result != 0) + { + /* move to AUTH state to let the backend + * ask for a password + */ + request->state = GTK_CUPS_GET_AUTH; + request->need_password = TRUE; + + return; + } + } + else + { + cups_password = request->password; + cups_username = request->username; + + auth_result = cupsDoAuthentication (request->http, "GET", request->resource); + + if (cups_password != NULL) + return; + + if (request->password != NULL) + { + memset (request->password, 0, strlen (request->password)); + g_free (request->password); + request->password = NULL; + } + + request->password_state = GTK_CUPS_PASSWORD_APPLIED; + } + } + + if (auth_result || + httpReconnect (request->http)) + { + /* if the password has been used, reset password_state + * so that we ask for a new one next time around + */ + if (cups_password == NULL) + request->password_state = GTK_CUPS_PASSWORD_NONE; + + request->state = GTK_CUPS_GET_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + gtk_cups_result_set_error (request->result, + GTK_CUPS_ERROR_AUTH, + 0, + 0, + "Not authorized"); + return; + } + + request->state = GTK_CUPS_GET_SEND; + request->last_status = HTTP_CONTINUE; + + return; } else if (http_status == HTTP_UPGRADE_REQUIRED) { @@ -1043,7 +1319,7 @@ httpFlush (request->http); cupsSetEncryption (HTTP_ENCRYPT_REQUIRED); - request->state = GTK_CUPS_POST_CONNECT; + request->state = GTK_CUPS_GET_CONNECT; /* Reconnect... */ httpReconnect (request->http); @@ -1143,7 +1419,7 @@ if (io_status == G_IO_STATUS_ERROR) { - request->state = GTK_CUPS_POST_DONE; + request->state = GTK_CUPS_GET_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; gtk_cups_result_set_error (request->result, Index: modules/printbackends/cups/gtkprintbackendcups.c =================================================================== --- modules/printbackends/cups/gtkprintbackendcups.c (revision 22586) +++ modules/printbackends/cups/gtkprintbackendcups.c (working copy) @@ -118,6 +118,11 @@ char *default_cover_before; char *default_cover_after; int number_of_covers; + + GList *requests; + GHashTable *auth; + gchar *username; + gboolean authentication_lock; }; static GObjectClass *backend_parent_class; @@ -176,7 +181,14 @@ gdouble height, GIOChannel *cache_io); +static void gtk_print_backend_cups_set_password (GtkPrintBackend *backend, + const gchar *hostname, + const gchar *username, + const gchar *password); +void overwrite_and_free (gpointer data); +static gboolean is_address_local (const gchar *address); + static void gtk_print_backend_cups_register_type (GTypeModule *module) { @@ -271,6 +283,7 @@ 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_capabilities = cups_printer_get_capabilities; + backend_class->set_password = gtk_print_backend_cups_set_password; } static cairo_status_t @@ -511,12 +524,13 @@ cups_printer = GTK_PRINTER_CUPS (gtk_print_job_get_printer (job)); settings = gtk_print_job_get_settings (job); - request = gtk_cups_request_new (NULL, - GTK_CUPS_POST, - IPP_PRINT_JOB, - data_io, - NULL, - cups_printer->device_uri); + 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); #if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR >= 2) || CUPS_VERSION_MAJOR > 1 httpAssembleURIf (HTTP_URI_CODING_ALL, @@ -561,7 +575,17 @@ (GDestroyNotify)cups_free_print_stream_data); } +void overwrite_and_free (gpointer data) +{ + gchar *password = (gchar *) data; + if (password != NULL) + { + memset (password, 0, strlen (password)); + g_free (password); + } +} + static void gtk_print_backend_cups_init (GtkPrintBackendCups *backend_cups) { @@ -569,6 +593,10 @@ backend_cups->got_default_printer = FALSE; backend_cups->list_printers_pending = FALSE; + 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->covers = NULL; backend_cups->default_cover_before = NULL; backend_cups->default_cover_after = NULL; @@ -577,6 +605,8 @@ backend_cups->default_printer_poll = 0; backend_cups->cups_connection_test = NULL; + backend_cups->username = NULL; + cups_get_local_default_printer (backend_cups); } @@ -602,6 +632,10 @@ 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); + backend_parent_class->finalize (object); } @@ -626,8 +660,157 @@ backend_parent_class->dispose (object); } +static gboolean +is_address_local (const gchar *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, + const gchar *hostname, + const gchar *username, + const gchar *password) +{ + GtkPrintBackendCups *cups_backend = GTK_PRINT_BACKEND_CUPS (backend); + GList *l; + char dispatch_hostname[HTTP_MAX_URI]; + gchar *key; + + key = g_strconcat (username, "@", hostname, NULL); + g_hash_table_insert (cups_backend->auth, key, g_strdup (password)); + + g_free (cups_backend->username); + cups_backend->username = g_strdup (username); + + GTK_NOTE (PRINTING, + g_print ("CUPS backend: storing password for %s\n", key)); + + 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 (strcmp (hostname, dispatch_hostname) == 0) + { + 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 gchar *username; + gchar *password; + gchar *prompt = NULL; + gchar *key = NULL; + char hostname[HTTP_MAX_URI]; + + if (dispatch->backend->authentication_lock) + return FALSE; + + 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 (); + + 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 (dispatch->request->ipp_request->request.op.operation_id) + { + case 0: + prompt = g_strdup_printf ( _("Authentication is required to get a file from %s"), hostname); + break; + 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: + prompt = g_strdup_printf ( _("Authentication is required on %s"), hostname); + break; + } + + g_free (printer_name); + + g_signal_emit_by_name (dispatch->backend, "request-password", + hostname, username, prompt); + + g_free (prompt); + } + + g_free (key); + + return FALSE; +} + +static gboolean cups_dispatch_watch_check (GSource *source) { GtkPrintCupsDispatchWatch *dispatch; @@ -665,7 +848,7 @@ #endif } - if (poll_state != GTK_CUPS_HTTP_IDLE) + if (poll_state != GTK_CUPS_HTTP_IDLE && !dispatch->request->need_password) if (!(dispatch->data_poll->revents & dispatch->data_poll->events)) return FALSE; @@ -676,6 +859,13 @@ g_free (dispatch->data_poll); dispatch->data_poll = NULL; } + + if (dispatch->request->need_password && dispatch->request->password_state != GTK_CUPS_PASSWORD_REQUESTED) + { + dispatch->request->need_password = FALSE; + g_idle_add (request_password, dispatch); + result = FALSE; + } return result; } @@ -735,12 +925,39 @@ cups_dispatch_watch_finalize (GSource *source) { GtkPrintCupsDispatchWatch *dispatch; + GtkCupsResult *result; GTK_NOTE (PRINTING, g_print ("CUPS Backend: %s \n", G_STRFUNC, source)); dispatch = (GtkPrintCupsDispatchWatch *) source; + result = gtk_cups_request_get_result (dispatch->request); + if (gtk_cups_result_get_error_type (result) == GTK_CUPS_ERROR_AUTH) + { + const gchar *username; + gchar hostname[HTTP_MAX_URI]; + gchar *key; + + 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 (); + + key = g_strconcat (username, "@", hostname, NULL); + GTK_NOTE (PRINTING, + g_print ("CUPS backend: removing stored password for %s\n", key)); + g_hash_table_remove (dispatch->backend->auth, key); + g_free (key); + + if (dispatch->backend) + dispatch->backend->authentication_lock = FALSE; + } + gtk_cups_request_free (dispatch->request); if (dispatch->backend) @@ -754,6 +971,10 @@ * of print backends. See _gtk_print_backend_create for the * disabling. */ + + dispatch->backend->requests = g_list_remove (dispatch->backend->requests, dispatch); + + g_object_unref (dispatch->backend); dispatch->backend = NULL; } @@ -788,6 +1009,8 @@ dispatch->backend = g_object_ref (print_backend); dispatch->data_poll = NULL; + print_backend->requests = g_list_prepend (print_backend->requests, dispatch); + g_source_set_callback ((GSource *) dispatch, (GSourceFunc) callback, user_data, notify); g_source_attach ((GSource *) dispatch, NULL); @@ -890,12 +1113,13 @@ "job-sheets-default" }; - request = gtk_cups_request_new (NULL, - GTK_CUPS_POST, - IPP_GET_PRINTER_ATTRIBUTES, - NULL, - NULL, - NULL); + request = gtk_cups_request_new_with_username (NULL, + GTK_CUPS_POST, + IPP_GET_PRINTER_ATTRIBUTES, + NULL, + NULL, + NULL, + print_backend->username); printer_uri = g_strdup_printf ("ipp://localhost/printers/%s", printer_name); @@ -1029,12 +1253,13 @@ GtkCupsRequest *request; gchar *job_uri; - request = gtk_cups_request_new (NULL, - GTK_CUPS_POST, - IPP_GET_JOB_ATTRIBUTES, - NULL, - NULL, - NULL); + request = gtk_cups_request_new_with_username (NULL, + GTK_CUPS_POST, + IPP_GET_JOB_ATTRIBUTES, + NULL, + NULL, + NULL, + data->print_backend->username); job_uri = g_strdup_printf ("ipp://localhost/jobs/%d", data->job_id); gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_URI, @@ -1121,9 +1346,20 @@ if (gtk_cups_result_is_error (result)) { GTK_NOTE (PRINTING, - g_warning ("CUPS Backend: Error getting printer list: %s", - gtk_cups_result_get_error_string (result))); + g_warning ("CUPS Backend: Error getting printer list: %s %d %d", + gtk_cups_result_get_error_string (result), + gtk_cups_result_get_error_type (result), + gtk_cups_result_get_error_code (result))); + if (gtk_cups_result_get_error_type (result) == GTK_CUPS_ERROR_AUTH && + gtk_cups_result_get_error_code (result) == 1) + { + /* Canceled by user, stop popping up more password dialogs */ + if (cups_backend->list_printers_poll > 0) + g_source_remove (cups_backend->list_printers_poll); + cups_backend->list_printers_poll = 0; + } + goto done; } @@ -1609,12 +1845,13 @@ cups_backend->list_printers_pending = TRUE; - request = gtk_cups_request_new (NULL, - GTK_CUPS_POST, - CUPS_GET_PRINTERS, - NULL, - NULL, - NULL); + request = gtk_cups_request_new_with_username (NULL, + GTK_CUPS_POST, + CUPS_GET_PRINTERS, + NULL, + NULL, + NULL, + cups_backend->username); gtk_cups_request_ipp_add_strings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", G_N_ELEMENTS (pattrs), @@ -1776,28 +2013,30 @@ resource = g_strdup_printf ("/printers/%s.ppd", gtk_printer_cups_get_ppd_name (GTK_PRINTER_CUPS (printer))); - request = gtk_cups_request_new (data->http, - GTK_CUPS_GET, - 0, - data->ppd_io, - cups_printer->hostname, - resource); + print_backend = gtk_printer_get_backend (printer); + request = gtk_cups_request_new_with_username (data->http, + GTK_CUPS_GET, + 0, + data->ppd_io, + cups_printer->hostname, + resource, + GTK_PRINT_BACKEND_CUPS (print_backend)->username); + GTK_NOTE (PRINTING, g_print ("CUPS Backend: Requesting resource %s to be written to temp file %s\n", resource, ppd_filename)); - g_free (resource); - g_free (ppd_filename); cups_printer->reading_ppd = TRUE; - print_backend = gtk_printer_get_backend (printer); - cups_request_execute (GTK_PRINT_BACKEND_CUPS (print_backend), request, (GtkPrintCupsResponseCallbackFunc) cups_request_ppd_cb, data, (GDestroyNotify)get_ppd_data_free); + + g_free (resource); + g_free (ppd_filename); } /* Ordering matters for default preference */ @@ -2018,6 +2257,20 @@ GDK_THREADS_ENTER (); + if (gtk_cups_result_is_error (result)) + { + if (gtk_cups_result_get_error_type (result) == GTK_CUPS_ERROR_AUTH && + gtk_cups_result_get_error_code (result) == 1) + { + /* Canceled by user, stop popping up more password dialogs */ + if (print_backend->list_printers_poll > 0) + g_source_remove (print_backend->list_printers_poll); + print_backend->list_printers_poll = 0; + } + + return; + } + response = gtk_cups_result_get_response (result); if ((attr = ippFindAttribute (response, "printer-name", IPP_TAG_NAME)) != NULL) @@ -2056,12 +2309,13 @@ if (state == GTK_CUPS_CONNECTION_IN_PROGRESS || state == GTK_CUPS_CONNECTION_NOT_AVAILABLE) return TRUE; - request = gtk_cups_request_new (NULL, - GTK_CUPS_POST, - CUPS_GET_DEFAULT, - NULL, - NULL, - NULL); + request = gtk_cups_request_new_with_username (NULL, + GTK_CUPS_POST, + CUPS_GET_DEFAULT, + NULL, + NULL, + NULL, + print_backend->username); cups_request_execute (print_backend, request, Index: modules/printbackends/cups/gtkcupsutils.h =================================================================== --- modules/printbackends/cups/gtkcupsutils.h (revision 22586) +++ modules/printbackends/cups/gtkcupsutils.h (working copy) @@ -37,6 +37,7 @@ GTK_CUPS_ERROR_HTTP, GTK_CUPS_ERROR_IPP, GTK_CUPS_ERROR_IO, + GTK_CUPS_ERROR_AUTH, GTK_CUPS_ERROR_GENERAL } GtkCupsErrorType; @@ -66,6 +67,15 @@ GTK_CUPS_CONNECTION_IN_PROGRESS } GtkCupsConnectionState; +typedef enum +{ + GTK_CUPS_PASSWORD_NONE, + GTK_CUPS_PASSWORD_REQUESTED, + GTK_CUPS_PASSWORD_HAS, + GTK_CUPS_PASSWORD_APPLIED, + GTK_CUPS_PASSWORD_NOT_VALID +} GtkCupsPasswordState; + struct _GtkCupsRequest { GtkCupsRequestType type; @@ -84,7 +94,12 @@ gint state; GtkCupsPollState poll_state; - gint own_http : 1; + gchar *password; + gchar *username; + + gint own_http : 1; + gint need_password : 1; + GtkCupsPasswordState password_state; }; struct _GtkCupsConnectionTest @@ -108,6 +123,7 @@ GTK_CUPS_POST_WRITE_REQUEST, GTK_CUPS_POST_WRITE_DATA, GTK_CUPS_POST_CHECK, + GTK_CUPS_POST_AUTH, GTK_CUPS_POST_READ_RESPONSE, GTK_CUPS_POST_DONE = GTK_CUPS_REQUEST_DONE }; @@ -118,10 +134,18 @@ GTK_CUPS_GET_CONNECT = GTK_CUPS_REQUEST_START, GTK_CUPS_GET_SEND, GTK_CUPS_GET_CHECK, + GTK_CUPS_GET_AUTH, GTK_CUPS_GET_READ_DATA, GTK_CUPS_GET_DONE = GTK_CUPS_REQUEST_DONE }; +GtkCupsRequest * gtk_cups_request_new_with_username (http_t *connection, + GtkCupsRequestType req_type, + gint operation_id, + GIOChannel *data_io, + const char *server, + const char *resource, + const char *username); GtkCupsRequest * gtk_cups_request_new (http_t *connection, GtkCupsRequestType req_type, gint operation_id, @@ -141,6 +165,9 @@ int num_values, const char *charset, const char * const *values); +const char * gtk_cups_request_ipp_get_string (GtkCupsRequest *request, + ipp_tag_t tag, + const char *name); gboolean gtk_cups_request_read_write (GtkCupsRequest *request); GtkCupsPollState gtk_cups_request_get_poll_state (GtkCupsRequest *request); void gtk_cups_request_free (GtkCupsRequest *request);