Commit 2c5ae21c authored by Marek Kašík's avatar Marek Kašík

Adds authentication support of CUPS backend

Adds authentication support of CUPS backend against CUPS server.
Print dialog is now capable to ask user for password and pass it
to the CUPS server. It is also possible to authenticate user
through Kerberos (GSS-API) (#384940).
parent 6e121ee8
......@@ -98,6 +98,7 @@ VOID:POINTER,UINT
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
......
......@@ -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 @@ struct _GtkPrintBackendPrivate
guint printer_list_requested : 1;
guint printer_list_done : 1;
GtkPrintBackendStatus status;
char *hostname;
char *username;
char *password;
};
enum {
......@@ -57,6 +61,7 @@ enum {
PRINTER_ADDED,
PRINTER_REMOVED,
PRINTER_STATUS_CHANGED,
REQUEST_PASSWORD,
LAST_SIGNAL
};
......@@ -353,6 +358,10 @@ static void fallback_printer_get_hard_margins (GtkPrinter
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 @@ gtk_print_backend_class_init (GtkPrintBackendClass *class)
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 @@ gtk_print_backend_class_init (GtkPrintBackendClass *class)
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 @@ gtk_print_backend_init (GtkPrintBackend *backend)
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 @@ gtk_print_backend_print_stream (GtkPrintBackend *backend,
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 ("<span weight=\"bold\" size=\"large\">%s</span>", 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)
{
......
......@@ -120,14 +120,22 @@ struct _GtkPrintBackendClass
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 @@ void gtk_print_backend_print_stream (GtkPrintBackend *pri
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 */
......
......@@ -757,7 +757,10 @@ load_print_backends (GtkPrintUnixDialog *dialog)
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
......
......@@ -39,10 +39,12 @@ static void _post_send (GtkCupsRequest *request);
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 @@ static GtkCupsRequestStateFunc post_states[] = {
_post_write_request,
_post_write_data,
_post_check,
_post_auth,
_post_read_response
};
......@@ -76,6 +79,7 @@ static GtkCupsRequestStateFunc get_states[] = {
_connect,
_get_send,
_get_check,
_get_auth,
_get_read_data
};
......@@ -101,12 +105,13 @@ gtk_cups_result_set_error (GtkCupsResult *result,
}
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 @@ gtk_cups_request_new (http_t *connection,
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 @@ gtk_cups_request_new (http_t *connection,
"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,6 +234,13 @@ gtk_cups_request_free (GtkCupsRequest *request)
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);
......@@ -290,6 +326,23 @@ gtk_cups_request_ipp_add_strings (GtkCupsRequest *request,
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
......@@ -786,12 +839,83 @@ _post_write_data (GtkCupsRequest *request)
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 @@ _post_check (GtkCupsRequest *request)
}
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 @@ _post_check (GtkCupsRequest *request)
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,8 +1172,12 @@ _get_send (GtkCupsRequest *request)
}
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 @@ _get_send (GtkCupsRequest *request)
request->attempts++;
return;
}
if (httpCheck (request->http))
request->last_status = httpUpdate (request->http);
request->attempts = 0;
......@@ -1024,18 +1228,90 @@ _get_check (GtkCupsRequest *request)
}
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,