gtk/modules/printbackends/cups/gtkcupsutils.c
Marek Kasik 2c5ae21cdc 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).
2009-04-21 14:24:32 +02:00

1611 lines
42 KiB
C

/* GTK - The GIMP Toolkit
* gtkcupsutils.h: Statemachine implementation of POST and GET
* cups calls which can be used to create a non-blocking cups API
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <gtk/gtk.h>
#include "gtkcupsutils.h"
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <sys/socket.h>
typedef void (*GtkCupsRequestStateFunc) (GtkCupsRequest *request);
static void _connect (GtkCupsRequest *request);
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
{
gchar *error_msg;
ipp_t *ipp_response;
GtkCupsErrorType error_type;
/* some error types like HTTP_ERROR have a status and a code */
int error_status;
int error_code;
guint is_error : 1;
guint is_ipp_response : 1;
};
#define _GTK_CUPS_MAX_ATTEMPTS 10
#define _GTK_CUPS_MAX_CHUNK_SIZE 8192
static GtkCupsRequestStateFunc post_states[] = {
_connect,
_post_send,
_post_write_request,
_post_write_data,
_post_check,
_post_auth,
_post_read_response
};
static GtkCupsRequestStateFunc get_states[] = {
_connect,
_get_send,
_get_check,
_get_auth,
_get_read_data
};
static void
gtk_cups_result_set_error (GtkCupsResult *result,
GtkCupsErrorType error_type,
int error_status,
int error_code,
const char *error_msg,
...)
{
va_list args;
result->is_ipp_response = FALSE;
result->is_error = TRUE;
result->error_type = error_type;
result->error_status = error_status;
result->error_code = error_code;
va_start (args, error_msg);
result->error_msg = g_strdup_vprintf (error_msg, args);
va_end (args);
}
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 *request;
cups_lang_t *language;
request = g_new0 (GtkCupsRequest, 1);
request->result = g_new0 (GtkCupsResult, 1);
request->result->error_msg = NULL;
request->result->ipp_response = NULL;
request->result->is_error = FALSE;
request->result->is_ipp_response = FALSE;
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
request->server = g_strdup (cupsServer ());
if (resource)
request->resource = g_strdup (resource);
else
request->resource = g_strdup ("/");
if (connection != NULL)
{
request->http = connection;
request->own_http = FALSE;
}
else
{
request->http = NULL;
request->http = httpConnectEncrypt (request->server,
ippPort (),
cupsEncryption ());
if (request->http)
httpBlocking (request->http, 0);
request->own_http = TRUE;
}
request->last_status = HTTP_CONTINUE;
request->attempts = 0;
request->data_io = data_io;
request->ipp_request = ippNew ();
request->ipp_request->request.op.operation_id = operation_id;
request->ipp_request->request.op.request_id = 1;
language = cupsLangDefault ();
gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
"attributes-charset",
NULL, "utf-8");
gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
"attributes-natural-language",
NULL, language->language);
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)
{
g_free (result->error_msg);
if (result->ipp_response)
ippDelete (result->ipp_response);
g_free (result);
}
void
gtk_cups_request_free (GtkCupsRequest *request)
{
if (request->own_http)
{
if (request->http)
httpClose (request->http);
}
if (request->ipp_request)
ippDelete (request->ipp_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);
g_free (request);
}
gboolean
gtk_cups_request_read_write (GtkCupsRequest *request)
{
if (request->type == GTK_CUPS_POST)
post_states[request->state] (request);
else if (request->type == GTK_CUPS_GET)
get_states[request->state] (request);
if (request->attempts > _GTK_CUPS_MAX_ATTEMPTS &&
request->state != GTK_CUPS_REQUEST_DONE)
{
/* TODO: should add a status or error code for too many failed attempts */
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_GENERAL,
0,
0,
"Too many failed attempts");
request->state = GTK_CUPS_REQUEST_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
}
if (request->state == GTK_CUPS_REQUEST_DONE)
{
request->poll_state = GTK_CUPS_HTTP_IDLE;
return TRUE;
}
else
return FALSE;
}
GtkCupsPollState
gtk_cups_request_get_poll_state (GtkCupsRequest *request)
{
return request->poll_state;
}
GtkCupsResult *
gtk_cups_request_get_result (GtkCupsRequest *request)
{
return request->result;
}
void
gtk_cups_request_ipp_add_string (GtkCupsRequest *request,
ipp_tag_t group,
ipp_tag_t tag,
const char *name,
const char *charset,
const char *value)
{
ippAddString (request->ipp_request,
group,
tag,
name,
charset,
value);
}
void
gtk_cups_request_ipp_add_strings (GtkCupsRequest *request,
ipp_tag_t group,
ipp_tag_t tag,
const char *name,
int num_values,
const char *charset,
const char *const *values)
{
ippAddStrings (request->ipp_request,
group,
tag,
name,
num_values,
charset,
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;
ipp_tag_t value_tag;
} ipp_option_t;
static const ipp_option_t ipp_options[] = {
{ "blackplot", IPP_TAG_BOOLEAN },
{ "brightness", IPP_TAG_INTEGER },
{ "columns", IPP_TAG_INTEGER },
{ "copies", IPP_TAG_INTEGER },
{ "finishings", IPP_TAG_ENUM },
{ "fitplot", IPP_TAG_BOOLEAN },
{ "gamma", IPP_TAG_INTEGER },
{ "hue", IPP_TAG_INTEGER },
{ "job-k-limit", IPP_TAG_INTEGER },
{ "job-page-limit", IPP_TAG_INTEGER },
{ "job-priority", IPP_TAG_INTEGER },
{ "job-quota-period", IPP_TAG_INTEGER },
{ "landscape", IPP_TAG_BOOLEAN },
{ "media", IPP_TAG_KEYWORD },
{ "mirror", IPP_TAG_BOOLEAN },
{ "natural-scaling", IPP_TAG_INTEGER },
{ "number-up", IPP_TAG_INTEGER },
{ "orientation-requested", IPP_TAG_ENUM },
{ "page-bottom", IPP_TAG_INTEGER },
{ "page-left", IPP_TAG_INTEGER },
{ "page-ranges", IPP_TAG_RANGE },
{ "page-right", IPP_TAG_INTEGER },
{ "page-top", IPP_TAG_INTEGER },
{ "penwidth", IPP_TAG_INTEGER },
{ "ppi", IPP_TAG_INTEGER },
{ "prettyprint", IPP_TAG_BOOLEAN },
{ "printer-resolution", IPP_TAG_RESOLUTION },
{ "print-quality", IPP_TAG_ENUM },
{ "saturation", IPP_TAG_INTEGER },
{ "scaling", IPP_TAG_INTEGER },
{ "sides", IPP_TAG_KEYWORD },
{ "wrap", IPP_TAG_BOOLEAN }
};
static ipp_tag_t
_find_option_tag (const gchar *option)
{
int lower_bound, upper_bound, num_options;
int current_option;
ipp_tag_t result;
result = IPP_TAG_ZERO;
lower_bound = 0;
upper_bound = num_options = (int) G_N_ELEMENTS (ipp_options) - 1;
while (1)
{
int match;
current_option = (int) (((upper_bound - lower_bound) / 2) + lower_bound);
match = strcasecmp (option, ipp_options[current_option].name);
if (match == 0)
{
result = ipp_options[current_option].value_tag;
return result;
}
else if (match < 0)
{
upper_bound = current_option - 1;
}
else
{
lower_bound = current_option + 1;
}
if (upper_bound == lower_bound && upper_bound == current_option)
return result;
if (upper_bound < 0)
return result;
if (lower_bound > num_options)
return result;
if (upper_bound < lower_bound)
return result;
}
}
/*
* Note that this function uses IPP_TAG_JOB, so it is
* only suitable for IPP Group 2 attributes.
* See RFC 2911.
*/
void
gtk_cups_request_encode_option (GtkCupsRequest *request,
const gchar *option,
const gchar *value)
{
ipp_tag_t option_tag;
g_return_if_fail (option != NULL);
g_return_if_fail (value != NULL);
option_tag = _find_option_tag (option);
if (option_tag == IPP_TAG_ZERO)
{
option_tag = IPP_TAG_NAME;
if (strcasecmp (value, "true") == 0 ||
strcasecmp (value, "false") == 0)
{
option_tag = IPP_TAG_BOOLEAN;
}
}
switch (option_tag)
{
case IPP_TAG_INTEGER:
case IPP_TAG_ENUM:
ippAddInteger (request->ipp_request,
IPP_TAG_JOB,
option_tag,
option,
strtol (value, NULL, 0));
break;
case IPP_TAG_BOOLEAN:
{
char b;
if (strcasecmp (value, "true") == 0 ||
strcasecmp (value, "on") == 0 ||
strcasecmp (value, "yes") == 0)
b = 1;
else
b = 0;
ippAddBoolean (request->ipp_request,
IPP_TAG_JOB,
option,
b);
break;
}
case IPP_TAG_RANGE:
{
char *s;
int lower;
int upper;
if (*value == '-')
{
lower = 1;
s = (char *)value;
}
else
lower = strtol (value, &s, 0);
if (*s == '-')
{
if (s[1])
upper = strtol (s + 1, NULL, 0);
else
upper = 2147483647;
}
else
upper = lower;
ippAddRange (request->ipp_request,
IPP_TAG_JOB,
option,
lower,
upper);
break;
}
case IPP_TAG_RESOLUTION:
{
char *s;
int xres;
int yres;
ipp_res_t units;
xres = strtol (value, &s, 0);
if (*s == 'x')
yres = strtol (s + 1, &s, 0);
else
yres = xres;
if (strcasecmp (s, "dpc") == 0)
units = IPP_RES_PER_CM;
else
units = IPP_RES_PER_INCH;
ippAddResolution (request->ipp_request,
IPP_TAG_JOB,
option,
units,
xres,
yres);
break;
}
default:
{
char *values;
char *s;
int in_quotes;
char *next;
GPtrArray *strings;
values = g_strdup (value);
strings = NULL;
in_quotes = 0;
for (s = values, next = s; *s != '\0'; s++)
{
if (in_quotes != 2 && *s == '\'')
{
/* skip quoted value */
if (in_quotes == 0)
in_quotes = 1;
else
in_quotes = 0;
}
else if (in_quotes != 1 && *s == '\"')
{
/* skip quoted value */
if (in_quotes == 0)
in_quotes = 2;
else
in_quotes = 0;
}
else if (in_quotes == 0 && *s == ',')
{
/* found delimiter, add to value array */
*s = '\0';
if (strings == NULL)
strings = g_ptr_array_new ();
g_ptr_array_add (strings, next);
next = s + 1;
}
else if (in_quotes == 0 && *s == '\\' && s[1] != '\0')
{
/* skip escaped character */
s++;
}
}
if (strings == NULL)
{
/* single value */
ippAddString (request->ipp_request,
IPP_TAG_JOB,
option_tag,
option,
NULL,
value);
}
else
{
/* multiple values */
/* add last value */
g_ptr_array_add (strings, next);
ippAddStrings (request->ipp_request,
IPP_TAG_JOB,
option_tag,
option,
strings->len,
NULL,
(const char **) strings->pdata);
g_ptr_array_free (strings, TRUE);
}
g_free (values);
}
break;
}
}
static void
_connect (GtkCupsRequest *request)
{
request->poll_state = GTK_CUPS_HTTP_IDLE;
if (request->http == NULL)
{
request->http = httpConnectEncrypt (request->server,
ippPort (),
cupsEncryption ());
if (request->http == NULL)
request->attempts++;
if (request->http)
httpBlocking (request->http, 0);
request->own_http = TRUE;
}
else
{
request->attempts = 0;
request->state++;
/* we always write to the socket after we get
the connection */
request->poll_state = GTK_CUPS_HTTP_WRITE;
}
}
static void
_post_send (GtkCupsRequest *request)
{
gchar length[255];
struct stat data_info;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
request->poll_state = GTK_CUPS_HTTP_WRITE;
if (request->data_io != NULL)
{
fstat (g_io_channel_unix_get_fd (request->data_io), &data_info);
sprintf (length, "%lu", (unsigned long) (ippLength (request->ipp_request) + data_info.st_size));
}
else
sprintf (length, "%lu", (unsigned long) ippLength (request->ipp_request));
httpClearFields (request->http);
httpSetField (request->http, HTTP_FIELD_CONTENT_LENGTH, length);
httpSetField (request->http, HTTP_FIELD_CONTENT_TYPE, "application/ipp");
#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 (httpPost (request->http, request->resource))
{
if (httpReconnect (request->http))
{
request->state = GTK_CUPS_POST_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
/* TODO: should add a status or error code for failed post */
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_GENERAL,
0,
0,
"Failed Post");
}
request->attempts++;
return;
}
request->attempts = 0;
request->state = GTK_CUPS_POST_WRITE_REQUEST;
request->ipp_request->state = IPP_IDLE;
}
static void
_post_write_request (GtkCupsRequest *request)
{
ipp_state_t ipp_status;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
request->poll_state = GTK_CUPS_HTTP_WRITE;
ipp_status = ippWrite (request->http, request->ipp_request);
if (ipp_status == IPP_ERROR)
{
int cups_error = cupsLastError ();
request->state = GTK_CUPS_POST_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_IPP,
ipp_status,
cups_error,
"%s",
ippErrorString (cups_error));
return;
}
if (ipp_status == IPP_DATA)
{
if (request->data_io != NULL)
request->state = GTK_CUPS_POST_WRITE_DATA;
else
{
request->state = GTK_CUPS_POST_CHECK;
request->poll_state = GTK_CUPS_HTTP_READ;
}
}
}
static void
_post_write_data (GtkCupsRequest *request)
{
gsize bytes;
char buffer[_GTK_CUPS_MAX_CHUNK_SIZE];
http_status_t http_status;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
request->poll_state = GTK_CUPS_HTTP_WRITE;
if (httpCheck (request->http))
http_status = httpUpdate (request->http);
else
http_status = request->last_status;
request->last_status = http_status;
if (http_status == HTTP_CONTINUE || http_status == HTTP_OK)
{
GIOStatus io_status;
GError *error;
error = NULL;
/* send data */
io_status =
g_io_channel_read_chars (request->data_io,
buffer,
_GTK_CUPS_MAX_CHUNK_SIZE,
&bytes,
&error);
if (io_status == G_IO_STATUS_ERROR)
{
request->state = GTK_CUPS_POST_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_IO,
io_status,
error->code,
"Error reading from cache file: %s",
error->message);
g_error_free (error);
return;
}
else if (bytes == 0 && io_status == G_IO_STATUS_EOF)
{
request->state = GTK_CUPS_POST_CHECK;
request->poll_state = GTK_CUPS_HTTP_READ;
request->attempts = 0;
return;
}
#if HAVE_CUPS_API_1_2
if (httpWrite2 (request->http, buffer, bytes) < bytes)
#else
if (httpWrite (request->http, buffer, (int) bytes) < bytes)
#endif /* HAVE_CUPS_API_1_2 */
{
int http_errno;
http_errno = httpError (request->http);
request->state = GTK_CUPS_POST_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_HTTP,
http_status,
http_errno,
"Error writing to socket in Post %s",
g_strerror (http_errno));
return;
}
}
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)
{
http_status_t http_status;
http_status = request->last_status;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s - status %i\n", G_STRFUNC, http_status));
request->poll_state = GTK_CUPS_HTTP_READ;
if (http_status == HTTP_CONTINUE)
{
goto again;
}
else if (http_status == HTTP_UNAUTHORIZED)
{
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;
}
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)
{
int error = httpError (request->http);
#ifdef G_OS_WIN32
if (error != WSAENETDOWN && error != WSAENETUNREACH)
#else
if (error != ENETDOWN && error != ENETUNREACH)
#endif /* G_OS_WIN32 */
{
request->attempts++;
goto again;
}
else
{
request->state = GTK_CUPS_POST_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_HTTP,
http_status,
error,
"Unknown HTTP error");
return;
}
}
else if (http_status == HTTP_UPGRADE_REQUIRED)
{
/* Flush any error message... */
httpFlush (request->http);
cupsSetEncryption (HTTP_ENCRYPT_REQUIRED);
request->state = GTK_CUPS_POST_CONNECT;
/* Reconnect... */
httpReconnect (request->http);
/* Upgrade with encryption... */
httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED);
request->attempts++;
goto again;
}
else if (http_status != HTTP_OK)
{
int http_errno;
http_errno = httpError (request->http);
if (http_errno == EPIPE)
request->state = GTK_CUPS_POST_CONNECT;
else
{
request->state = GTK_CUPS_POST_DONE;
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_HTTP,
http_status,
http_errno,
"HTTP Error in POST %s",
g_strerror (http_errno));
request->poll_state = GTK_CUPS_HTTP_IDLE;
httpFlush (request->http);
return;
}
request->poll_state = GTK_CUPS_HTTP_IDLE;
httpFlush (request->http);
request->last_status = HTTP_CONTINUE;
httpClose (request->http);
request->http = NULL;
return;
}
else
{
request->state = GTK_CUPS_POST_READ_RESPONSE;
return;
}
again:
http_status = HTTP_CONTINUE;
if (httpCheck (request->http))
http_status = httpUpdate (request->http);
request->last_status = http_status;
}
static void
_post_read_response (GtkCupsRequest *request)
{
ipp_state_t ipp_status;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
request->poll_state = GTK_CUPS_HTTP_READ;
if (request->result->ipp_response == NULL)
request->result->ipp_response = ippNew();
ipp_status = ippRead (request->http,
request->result->ipp_response);
if (ipp_status == IPP_ERROR)
{
int ipp_error = cupsLastError ();
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_IPP,
ipp_status,
ipp_error,
"%s",
ippErrorString (ipp_error));
ippDelete (request->result->ipp_response);
request->result->ipp_response = NULL;
request->state = GTK_CUPS_POST_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
}
else if (ipp_status == IPP_DATA)
{
request->state = GTK_CUPS_POST_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
}
}
static void
_get_send (GtkCupsRequest *request)
{
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
request->poll_state = GTK_CUPS_HTTP_WRITE;
if (request->data_io == NULL)
{
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_IO,
G_IO_STATUS_ERROR,
G_IO_CHANNEL_ERROR_FAILED,
"Get requires an open io channel");
request->state = GTK_CUPS_GET_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
return;
}
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))
{
if (httpReconnect (request->http))
{
request->state = GTK_CUPS_GET_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
/* TODO: should add a status or error code for failed GET */
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_GENERAL,
0,
0,
"Failed Get");
}
request->attempts++;
return;
}
if (httpCheck (request->http))
request->last_status = httpUpdate (request->http);
request->attempts = 0;
request->state = GTK_CUPS_GET_CHECK;
request->poll_state = GTK_CUPS_HTTP_READ;
request->ipp_request->state = IPP_IDLE;
}
static void
_get_check (GtkCupsRequest *request)
{
http_status_t http_status;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
http_status = request->last_status;
request->poll_state = GTK_CUPS_HTTP_READ;
if (http_status == HTTP_CONTINUE)
{
goto again;
}
else if (http_status == HTTP_UNAUTHORIZED)
{
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_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)
{
/* Flush any error message... */
httpFlush (request->http);
cupsSetEncryption (HTTP_ENCRYPT_REQUIRED);
request->state = GTK_CUPS_GET_CONNECT;
/* Reconnect... */
httpReconnect (request->http);
/* Upgrade with encryption... */
httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED);
request->attempts++;
goto again;
}
else if (http_status != HTTP_OK)
{
int http_errno;
http_errno = httpError (request->http);
if (http_errno == EPIPE)
request->state = GTK_CUPS_GET_CONNECT;
else
{
request->state = GTK_CUPS_GET_DONE;
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_HTTP,
http_status,
http_errno,
"HTTP Error in GET %s",
g_strerror (http_errno));
request->poll_state = GTK_CUPS_HTTP_IDLE;
httpFlush (request->http);
return;
}
request->poll_state = GTK_CUPS_HTTP_IDLE;
httpFlush (request->http);
httpClose (request->http);
request->last_status = HTTP_CONTINUE;
request->http = NULL;
return;
}
else
{
request->state = GTK_CUPS_GET_READ_DATA;
return;
}
again:
http_status = HTTP_CONTINUE;
if (httpCheck (request->http))
http_status = httpUpdate (request->http);
request->last_status = http_status;
}
static void
_get_read_data (GtkCupsRequest *request)
{
char buffer[_GTK_CUPS_MAX_CHUNK_SIZE];
gsize bytes;
gsize bytes_written;
GIOStatus io_status;
GError *error;
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
error = NULL;
request->poll_state = GTK_CUPS_HTTP_READ;
#if HAVE_CUPS_API_1_2
bytes = httpRead2 (request->http, buffer, sizeof (buffer));
#else
bytes = httpRead (request->http, buffer, sizeof (buffer));
#endif /* HAVE_CUPS_API_1_2 */
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %i bytes read\n", bytes));
if (bytes == 0)
{
request->state = GTK_CUPS_GET_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
return;
}
io_status =
g_io_channel_write_chars (request->data_io,
buffer,
bytes,
&bytes_written,
&error);
if (io_status == G_IO_STATUS_ERROR)
{
request->state = GTK_CUPS_GET_DONE;
request->poll_state = GTK_CUPS_HTTP_IDLE;
gtk_cups_result_set_error (request->result,
GTK_CUPS_ERROR_IO,
io_status,
error->code,
error->message);
g_error_free (error);
}
}
gboolean
gtk_cups_request_is_done (GtkCupsRequest *request)
{
return (request->state == GTK_CUPS_REQUEST_DONE);
}
gboolean
gtk_cups_result_is_error (GtkCupsResult *result)
{
return result->is_error;
}
ipp_t *
gtk_cups_result_get_response (GtkCupsResult *result)
{
return result->ipp_response;
}
GtkCupsErrorType
gtk_cups_result_get_error_type (GtkCupsResult *result)
{
return result->error_type;
}
int
gtk_cups_result_get_error_status (GtkCupsResult *result)
{
return result->error_status;
}
int
gtk_cups_result_get_error_code (GtkCupsResult *result)
{
return result->error_code;
}
const char *
gtk_cups_result_get_error_string (GtkCupsResult *result)
{
return result->error_msg;
}
/* This function allocates new instance of GtkCupsConnectionTest() and creates
* a socket for communication with a CUPS server 'server'.
*/
GtkCupsConnectionTest *
gtk_cups_connection_test_new (const char *server)
{
GtkCupsConnectionTest *result = NULL;
#ifdef HAVE_CUPS_API_1_2
gchar *port_str = NULL;
result = g_new (GtkCupsConnectionTest, 1);
port_str = g_strdup_printf ("%d", ippPort ());
if (server != NULL)
result->addrlist = httpAddrGetList (server, AF_UNSPEC, port_str);
else
result->addrlist = httpAddrGetList (cupsServer (), AF_UNSPEC, port_str);
g_free (port_str);
result->socket = -1;
result->current_addr = NULL;
result->at_init = GTK_CUPS_CONNECTION_NOT_AVAILABLE;
result->at_init = gtk_cups_connection_test_get_state (result);
#else
result = g_new (GtkCupsConnectionTest, 1);
#endif
return result;
}
/* A non-blocking test whether it is possible to connect to a CUPS server specified
* inside of GtkCupsConnectionTest structure.
* - you need to check it more then once.
* The connection is closed after a successful connection.
*/
GtkCupsConnectionState
gtk_cups_connection_test_get_state (GtkCupsConnectionTest *test)
{
#ifdef HAVE_CUPS_API_1_2
GtkCupsConnectionState result = GTK_CUPS_CONNECTION_NOT_AVAILABLE;
http_addrlist_t *iter;
gint error_code;
gint flags;
gint code;
if (test == NULL)
return GTK_CUPS_CONNECTION_NOT_AVAILABLE;
if (test->at_init == GTK_CUPS_CONNECTION_AVAILABLE)
{
test->at_init = GTK_CUPS_CONNECTION_NOT_AVAILABLE;
return GTK_CUPS_CONNECTION_AVAILABLE;
}
else
{
if (test->socket == -1)
{
iter = test->addrlist;
while (iter)
{
test->socket = socket (iter->addr.addr.sa_family,
SOCK_STREAM,
0);
if (test->socket >= 0)
{
flags = fcntl (test->socket, F_GETFL);
if (flags != -1)
flags |= O_NONBLOCK;
fcntl (test->socket, F_SETFL, flags);
test->current_addr = iter;
break;
}
iter = iter->next;
}
}
if (test->socket >= 0)
{
code = connect (test->socket,
&test->current_addr->addr.addr,
httpAddrLength (&test->current_addr->addr));
error_code = errno;
if (code == 0)
{
close (test->socket);
test->socket = -1;
test->current_addr = NULL;
result = GTK_CUPS_CONNECTION_AVAILABLE;
}
else
{
if (error_code == EALREADY || error_code == EINPROGRESS)
result = GTK_CUPS_CONNECTION_IN_PROGRESS;
else
result = GTK_CUPS_CONNECTION_NOT_AVAILABLE;
}
}
return result;
}
#else
return GTK_CUPS_CONNECTION_AVAILABLE;
#endif
}
/* This function frees memory used by the GtkCupsConnectionTest structure.
*/
void
gtk_cups_connection_test_free (GtkCupsConnectionTest *test)
{
if (test == NULL)
return;
#ifdef HAVE_CUPS_API_1_2
test->current_addr = NULL;
httpAddrFreeList (test->addrlist);
if (test->socket != -1)
{
close (test->socket);
test->socket = -1;
}
#endif
g_free (test);
}