diff options
-rw-r--r-- | configure.ac | 15 | ||||
-rw-r--r-- | examples/get.c | 10 | ||||
-rw-r--r-- | libsoup/Makefile.am | 25 | ||||
-rw-r--r-- | libsoup/libsoup-2.4.sym | 568 | ||||
-rw-r--r-- | libsoup/libsoup-gssapi-2.4.sym | 3 | ||||
-rw-r--r-- | libsoup/soup-auth-negotiate.c | 437 | ||||
-rw-r--r-- | libsoup/soup-auth-negotiate.h | 27 | ||||
-rw-r--r-- | libsoup/soup-auth.h | 2 | ||||
-rw-r--r-- | libsoup/soup-gssapi.c | 169 | ||||
-rw-r--r-- | libsoup/soup-gssapi.h | 41 | ||||
-rw-r--r-- | libsoup/soup-status.h | 1 |
11 files changed, 1296 insertions, 2 deletions
diff --git a/configure.ac b/configure.ac index 2a984181..3be3a8b1 100644 --- a/configure.ac +++ b/configure.ac @@ -321,6 +321,21 @@ AC_DEFINE_UNQUOTED(NTLM_AUTH, "$ntlm_auth", [Samba's 'winbind' daemon helper 'nt AX_CODE_COVERAGE +dnl ********************** +dnl *** GSSAPI support *** +dnl ********************** +AC_PATH_PROG([KRB5_CONFIG], krb5-config, none, $PATH:/usr/kerberos/bin) +if test "x$KRB5_CONFIG" != "xnone"; then + KRB5_LIBS="`${KRB5_CONFIG} --libs gssapi`" + KRB5_CFLAGS="`${KRB5_CONFIG} --cflags gssapi`" + AC_SUBST(KRB5_CFLAGS) + AC_SUBST(KRB5_LIBS) + if test "$KRB5_CONFIG" != none; then + AC_DEFINE(HAVE_GSSAPI, 1, [Whether or not gssapi libs are available]) + fi +fi +AM_CONDITIONAL(BUILD_LIBSOUP_GSSAPI, test $KRB5_CONFIG != none) + dnl **************************************************** dnl *** Warnings to show if using GCC *** dnl *** (do this last so -Werror won't mess up tests *** diff --git a/examples/get.c b/examples/get.c index a8888d49..6f761af4 100644 --- a/examples/get.c +++ b/examples/get.c @@ -89,7 +89,7 @@ get_url (const char *url) } static const char *ca_file, *proxy; -static gboolean synchronous, ntlm; +static gboolean synchronous, ntlm, negotiate; static GOptionEntry entries[] = { { "ca-file", 'c', 0, @@ -104,6 +104,9 @@ static GOptionEntry entries[] = { { "ntlm", 'n', 0, G_OPTION_ARG_NONE, &ntlm, "Use NTLM authentication", NULL }, + { "negotiate", 'N', 0, + G_OPTION_ARG_NONE, &negotiate, + "Use Negotiate authentication", NULL }, { "output", 'o', 0, G_OPTION_ARG_STRING, &output_file_path, "Write the received data to FILE instead of stdout", "FILE" }, @@ -183,6 +186,11 @@ main (int argc, char **argv) soup_uri_free (proxy_uri); } + if (negotiate) { + soup_session_add_feature_by_type(session, + SOUP_TYPE_AUTH_NEGOTIATE); + } + if (!synchronous) loop = g_main_loop_new (NULL, TRUE); diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am index 27f8b279..8e9fcb2d 100644 --- a/libsoup/Makefile.am +++ b/libsoup/Makefile.am @@ -20,7 +20,8 @@ AM_CPPFLAGS = \ $(GLIB_CFLAGS) \ $(XML_CFLAGS) \ $(SQLITE_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(KRB5_CFLAGS) libsoupincludedir = $(includedir)/libsoup-2.4/libsoup @@ -110,6 +111,8 @@ libsoup_2_4_la_SOURCES = \ soup-auth-digest.c \ soup-auth-ntlm.h \ soup-auth-ntlm.c \ + soup-auth-negotiate.h \ + soup-auth-negotiate.c \ soup-auth-domain.c \ soup-auth-domain-basic.c \ soup-auth-domain-digest.c \ @@ -242,6 +245,26 @@ libsoup_gnome_2_4_la_SOURCES = \ endif +if BUILD_LIBSOUP_GSSAPI + +lib_LTLIBRARIES += libsoup-gssapi-2.4.la + +libsoup_gssapi_2_4_la_LDFLAGS = \ + -version-info $(SOUP_CURRENT):$(SOUP_REVISION):$(SOUP_AGE) \ + -no-undefined \ + -export-symbols $(srcdir)/libsoup-gssapi-2.4.sym + +EXTRA_DIST += libsoup-gssapi-2.4.sym + +libsoup_gssapi_2_4_la_LIBADD = \ + $(KRB5_LIBS) \ + $(GLIB_LIBS) + +libsoup_gssapi_2_4_la_SOURCES = \ + soup-gssapi.c + +endif + GLIB_GENERATED = soup-enum-types.c soup-enum-types.h BUILT_SOURCES = \ $(GLIB_GENERATED) \ diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym new file mode 100644 index 00000000..11833cb5 --- /dev/null +++ b/libsoup/libsoup-2.4.sym @@ -0,0 +1,568 @@ +_SOUP_METHOD_CONNECT +_SOUP_METHOD_COPY +_SOUP_METHOD_DELETE +_SOUP_METHOD_GET +_SOUP_METHOD_HEAD +_SOUP_METHOD_LOCK +_SOUP_METHOD_MKCOL +_SOUP_METHOD_MOVE +_SOUP_METHOD_OPTIONS +_SOUP_METHOD_POST +_SOUP_METHOD_PROPFIND +_SOUP_METHOD_PROPPATCH +_SOUP_METHOD_PUT +_SOUP_METHOD_TRACE +_SOUP_METHOD_UNLOCK +_SOUP_URI_SCHEME_DATA +_SOUP_URI_SCHEME_FILE +_SOUP_URI_SCHEME_FTP +_SOUP_URI_SCHEME_HTTP +_SOUP_URI_SCHEME_HTTPS +_SOUP_URI_SCHEME_RESOURCE +_SOUP_URI_SCHEME_WS +_SOUP_URI_SCHEME_WSS +soup_add_completion +soup_add_idle +soup_add_io_watch +soup_add_timeout +soup_address_equal_by_ip +soup_address_equal_by_name +soup_address_family_get_type +soup_address_get_gsockaddr +soup_address_get_name +soup_address_get_physical +soup_address_get_port +soup_address_get_sockaddr +soup_address_get_type +soup_address_hash_by_ip +soup_address_hash_by_name +soup_address_is_resolved +soup_address_new +soup_address_new_any +soup_address_new_from_sockaddr +soup_address_resolve_async +soup_address_resolve_sync +soup_auth_authenticate +soup_auth_basic_get_type +soup_auth_digest_get_type +soup_auth_domain_accepts +soup_auth_domain_add_path +soup_auth_domain_basic_get_type +soup_auth_domain_basic_new +soup_auth_domain_basic_set_auth_callback +soup_auth_domain_challenge +soup_auth_domain_check_password +soup_auth_domain_covers +soup_auth_domain_digest_encode_password +soup_auth_domain_digest_get_type +soup_auth_domain_digest_new +soup_auth_domain_digest_set_auth_callback +soup_auth_domain_get_realm +soup_auth_domain_get_type +soup_auth_domain_remove_path +soup_auth_domain_set_filter +soup_auth_domain_set_generic_auth_callback +soup_auth_domain_try_generic_auth_callback +soup_auth_free_protection_space +soup_auth_get_authorization +soup_auth_get_host +soup_auth_get_info +soup_auth_get_protection_space +soup_auth_get_realm +soup_auth_get_saved_password +soup_auth_get_saved_users +soup_auth_get_scheme_name +soup_auth_get_type +soup_auth_has_saved_password +soup_auth_is_authenticated +soup_auth_is_for_proxy +soup_auth_is_ready +soup_auth_manager_get_type +soup_auth_manager_use_auth +soup_auth_negotiate_get_type +soup_auth_new +soup_auth_ntlm_get_type +soup_auth_save_password +soup_auth_update +soup_buffer_copy +soup_buffer_free +soup_buffer_get_as_bytes +soup_buffer_get_data +soup_buffer_get_owner +soup_buffer_get_type +soup_buffer_new +soup_buffer_new_subbuffer +soup_buffer_new_take +soup_buffer_new_with_owner +soup_byte_array_get_type +soup_cache_clear +soup_cache_dump +soup_cache_flush +soup_cache_get_max_size +soup_cache_get_type +soup_cache_load +soup_cache_new +soup_cache_response_get_type +soup_cache_set_max_size +soup_cache_type_get_type +soup_cacheability_get_type +soup_char_attributes +soup_check_version +soup_client_context_get_address +soup_client_context_get_auth_domain +soup_client_context_get_auth_user +soup_client_context_get_gsocket +soup_client_context_get_host +soup_client_context_get_local_address +soup_client_context_get_remote_address +soup_client_context_get_socket +soup_client_context_get_type +soup_client_context_steal_connection +soup_connection_state_get_type +soup_content_decoder_get_type +soup_content_sniffer_get_buffer_size +soup_content_sniffer_get_type +soup_content_sniffer_new +soup_content_sniffer_sniff +soup_cookie_applies_to_uri +soup_cookie_copy +soup_cookie_domain_matches +soup_cookie_equal +soup_cookie_free +soup_cookie_get_domain +soup_cookie_get_expires +soup_cookie_get_http_only +soup_cookie_get_name +soup_cookie_get_path +soup_cookie_get_secure +soup_cookie_get_type +soup_cookie_get_value +soup_cookie_jar_accept_policy_get_type +soup_cookie_jar_add_cookie +soup_cookie_jar_add_cookie_with_first_party +soup_cookie_jar_all_cookies +soup_cookie_jar_db_get_type +soup_cookie_jar_db_new +soup_cookie_jar_delete_cookie +soup_cookie_jar_get_accept_policy +soup_cookie_jar_get_cookie_list +soup_cookie_jar_get_cookies +soup_cookie_jar_get_type +soup_cookie_jar_is_persistent +soup_cookie_jar_new +soup_cookie_jar_save +soup_cookie_jar_set_accept_policy +soup_cookie_jar_set_cookie +soup_cookie_jar_set_cookie_with_first_party +soup_cookie_jar_text_get_type +soup_cookie_jar_text_new +soup_cookie_new +soup_cookie_parse +soup_cookie_set_domain +soup_cookie_set_expires +soup_cookie_set_http_only +soup_cookie_set_max_age +soup_cookie_set_name +soup_cookie_set_path +soup_cookie_set_secure +soup_cookie_set_value +soup_cookie_to_cookie_header +soup_cookie_to_set_cookie_header +soup_cookies_free +soup_cookies_from_request +soup_cookies_from_response +soup_cookies_to_cookie_header +soup_cookies_to_request +soup_cookies_to_response +soup_date_copy +soup_date_format_get_type +soup_date_free +soup_date_get_day +soup_date_get_hour +soup_date_get_minute +soup_date_get_month +soup_date_get_offset +soup_date_get_second +soup_date_get_type +soup_date_get_utc +soup_date_get_year +soup_date_is_past +soup_date_new +soup_date_new_from_now +soup_date_new_from_string +soup_date_new_from_time_t +soup_date_to_string +soup_date_to_time_t +soup_date_to_timeval +soup_encoding_get_type +soup_expectation_get_type +soup_form_decode +soup_form_decode_multipart +soup_form_encode +soup_form_encode_datalist +soup_form_encode_hash +soup_form_encode_valist +soup_form_request_new +soup_form_request_new_from_datalist +soup_form_request_new_from_hash +soup_form_request_new_from_multipart +soup_get_major_version +soup_get_micro_version +soup_get_minor_version +soup_header_contains +soup_header_free_list +soup_header_free_param_list +soup_header_g_string_append_param +soup_header_g_string_append_param_quoted +soup_header_parse_list +soup_header_parse_param_list +soup_header_parse_quality_list +soup_header_parse_semi_param_list +soup_headers_parse +soup_headers_parse_request +soup_headers_parse_response +soup_headers_parse_status_line +soup_http_error_quark +soup_http_version_get_type +soup_known_status_code_get_type +soup_logger_attach +soup_logger_detach +soup_logger_get_type +soup_logger_log_level_get_type +soup_logger_new +soup_logger_set_printer +soup_logger_set_request_filter +soup_logger_set_response_filter +soup_memory_use_get_type +soup_message_add_header_handler +soup_message_add_status_code_handler +soup_message_body_append +soup_message_body_append_buffer +soup_message_body_append_take +soup_message_body_complete +soup_message_body_flatten +soup_message_body_free +soup_message_body_get_accumulate +soup_message_body_get_chunk +soup_message_body_get_type +soup_message_body_got_chunk +soup_message_body_new +soup_message_body_set_accumulate +soup_message_body_truncate +soup_message_body_wrote_chunk +soup_message_content_sniffed +soup_message_disable_feature +soup_message_finished +soup_message_flags_get_type +soup_message_get_address +soup_message_get_first_party +soup_message_get_flags +soup_message_get_http_version +soup_message_get_https_status +soup_message_get_priority +soup_message_get_soup_request +soup_message_get_type +soup_message_get_uri +soup_message_got_body +soup_message_got_chunk +soup_message_got_headers +soup_message_got_informational +soup_message_headers_append +soup_message_headers_clean_connection_headers +soup_message_headers_clear +soup_message_headers_foreach +soup_message_headers_free +soup_message_headers_free_ranges +soup_message_headers_get +soup_message_headers_get_content_disposition +soup_message_headers_get_content_length +soup_message_headers_get_content_range +soup_message_headers_get_content_type +soup_message_headers_get_encoding +soup_message_headers_get_expectations +soup_message_headers_get_headers_type +soup_message_headers_get_list +soup_message_headers_get_one +soup_message_headers_get_ranges +soup_message_headers_get_type +soup_message_headers_header_contains +soup_message_headers_header_equals +soup_message_headers_iter_init +soup_message_headers_iter_next +soup_message_headers_new +soup_message_headers_remove +soup_message_headers_replace +soup_message_headers_set_content_disposition +soup_message_headers_set_content_length +soup_message_headers_set_content_range +soup_message_headers_set_content_type +soup_message_headers_set_encoding +soup_message_headers_set_expectations +soup_message_headers_set_range +soup_message_headers_set_ranges +soup_message_headers_type_get_type +soup_message_io_cleanup +soup_message_is_keepalive +soup_message_new +soup_message_new_from_uri +soup_message_priority_get_type +soup_message_restarted +soup_message_set_chunk_allocator +soup_message_set_first_party +soup_message_set_flags +soup_message_set_http_version +soup_message_set_priority +soup_message_set_redirect +soup_message_set_request +soup_message_set_response +soup_message_set_status +soup_message_set_status_full +soup_message_set_uri +soup_message_starting +soup_message_wrote_body +soup_message_wrote_body_data +soup_message_wrote_chunk +soup_message_wrote_headers +soup_message_wrote_informational +soup_multipart_append_form_file +soup_multipart_append_form_string +soup_multipart_append_part +soup_multipart_free +soup_multipart_get_length +soup_multipart_get_part +soup_multipart_get_type +soup_multipart_input_stream_get_headers +soup_multipart_input_stream_get_type +soup_multipart_input_stream_new +soup_multipart_input_stream_next_part +soup_multipart_input_stream_next_part_async +soup_multipart_input_stream_next_part_finish +soup_multipart_new +soup_multipart_new_from_message +soup_multipart_to_message +soup_password_manager_get_passwords_async +soup_password_manager_get_passwords_sync +soup_password_manager_get_type +soup_proxy_resolver_default_get_type +soup_proxy_resolver_get_proxy_async +soup_proxy_resolver_get_proxy_sync +soup_proxy_resolver_get_type +soup_proxy_uri_resolver_get_proxy_uri_async +soup_proxy_uri_resolver_get_proxy_uri_sync +soup_proxy_uri_resolver_get_type +soup_request_data_get_type +soup_request_error_get_type +soup_request_error_quark +soup_request_file_get_file +soup_request_file_get_type +soup_request_get_content_length +soup_request_get_content_type +soup_request_get_session +soup_request_get_type +soup_request_get_uri +soup_request_http_get_message +soup_request_http_get_type +soup_request_send +soup_request_send_async +soup_request_send_finish +soup_requester_error_get_type +soup_requester_error_quark +soup_requester_get_type +soup_requester_new +soup_requester_request +soup_requester_request_uri +soup_server_accept_iostream +soup_server_add_auth_domain +soup_server_add_early_handler +soup_server_add_handler +soup_server_add_websocket_handler +soup_server_disconnect +soup_server_get_async_context +soup_server_get_listener +soup_server_get_listeners +soup_server_get_port +soup_server_get_type +soup_server_get_uris +soup_server_is_https +soup_server_listen +soup_server_listen_all +soup_server_listen_fd +soup_server_listen_local +soup_server_listen_options_get_type +soup_server_listen_socket +soup_server_new +soup_server_pause_message +soup_server_quit +soup_server_remove_auth_domain +soup_server_remove_handler +soup_server_run +soup_server_run_async +soup_server_set_ssl_cert_file +soup_server_unpause_message +soup_session_abort +soup_session_add_feature +soup_session_add_feature_by_type +soup_session_async_get_type +soup_session_async_new +soup_session_async_new_with_options +soup_session_cancel_message +soup_session_feature_add_feature +soup_session_feature_attach +soup_session_feature_detach +soup_session_feature_get_type +soup_session_feature_has_feature +soup_session_feature_remove_feature +soup_session_get_async_context +soup_session_get_feature +soup_session_get_feature_for_message +soup_session_get_features +soup_session_get_type +soup_session_has_feature +soup_session_new +soup_session_new_with_options +soup_session_pause_message +soup_session_prefetch_dns +soup_session_prepare_for_uri +soup_session_queue_message +soup_session_redirect_message +soup_session_remove_feature +soup_session_remove_feature_by_type +soup_session_request +soup_session_request_http +soup_session_request_http_uri +soup_session_request_uri +soup_session_requeue_message +soup_session_send +soup_session_send_async +soup_session_send_finish +soup_session_send_message +soup_session_steal_connection +soup_session_sync_get_type +soup_session_sync_new +soup_session_sync_new_with_options +soup_session_unpause_message +soup_session_websocket_connect_async +soup_session_websocket_connect_finish +soup_session_would_redirect +soup_socket_connect_async +soup_socket_connect_sync +soup_socket_disconnect +soup_socket_get_fd +soup_socket_get_local_address +soup_socket_get_remote_address +soup_socket_get_type +soup_socket_io_status_get_type +soup_socket_is_connected +soup_socket_is_ssl +soup_socket_listen +soup_socket_new +soup_socket_read +soup_socket_read_until +soup_socket_start_proxy_ssl +soup_socket_start_ssl +soup_socket_write +soup_ssl_supported +soup_status_get_phrase +soup_status_get_type +soup_status_proxify +soup_str_case_equal +soup_str_case_hash +soup_tld_domain_is_public_suffix +soup_tld_error_get_type +soup_tld_error_quark +soup_tld_get_base_domain +soup_uri_copy +soup_uri_copy_host +soup_uri_decode +soup_uri_encode +soup_uri_equal +soup_uri_free +soup_uri_get_fragment +soup_uri_get_host +soup_uri_get_password +soup_uri_get_path +soup_uri_get_port +soup_uri_get_query +soup_uri_get_scheme +soup_uri_get_type +soup_uri_get_user +soup_uri_host_equal +soup_uri_host_hash +soup_uri_new +soup_uri_new_with_base +soup_uri_normalize +soup_uri_set_fragment +soup_uri_set_host +soup_uri_set_password +soup_uri_set_path +soup_uri_set_port +soup_uri_set_query +soup_uri_set_query_from_fields +soup_uri_set_query_from_form +soup_uri_set_scheme +soup_uri_set_user +soup_uri_to_string +soup_uri_uses_default_port +soup_value_array_append +soup_value_array_append_vals +soup_value_array_from_args +soup_value_array_get_nth +soup_value_array_insert +soup_value_array_new +soup_value_array_new_with_vals +soup_value_array_to_args +soup_value_hash_insert +soup_value_hash_insert_vals +soup_value_hash_insert_value +soup_value_hash_lookup +soup_value_hash_lookup_vals +soup_value_hash_new +soup_value_hash_new_with_vals +soup_websocket_client_prepare_handshake +soup_websocket_client_verify_handshake +soup_websocket_close_code_get_type +soup_websocket_connection_close +soup_websocket_connection_get_close_code +soup_websocket_connection_get_close_data +soup_websocket_connection_get_connection_type +soup_websocket_connection_get_io_stream +soup_websocket_connection_get_origin +soup_websocket_connection_get_protocol +soup_websocket_connection_get_state +soup_websocket_connection_get_type +soup_websocket_connection_get_uri +soup_websocket_connection_new +soup_websocket_connection_send_binary +soup_websocket_connection_send_text +soup_websocket_connection_type_get_type +soup_websocket_data_type_get_type +soup_websocket_error_get_quark +soup_websocket_error_get_type +soup_websocket_server_check_handshake +soup_websocket_server_process_handshake +soup_websocket_state_get_type +soup_xmlrpc_build_fault +soup_xmlrpc_build_method_call +soup_xmlrpc_build_method_response +soup_xmlrpc_build_request +soup_xmlrpc_build_response +soup_xmlrpc_error_get_type +soup_xmlrpc_error_quark +soup_xmlrpc_extract_method_call +soup_xmlrpc_extract_method_response +soup_xmlrpc_fault_get_type +soup_xmlrpc_fault_quark +soup_xmlrpc_message_new +soup_xmlrpc_message_set_fault +soup_xmlrpc_message_set_response +soup_xmlrpc_params_free +soup_xmlrpc_params_parse +soup_xmlrpc_parse_method_call +soup_xmlrpc_parse_method_response +soup_xmlrpc_parse_request +soup_xmlrpc_parse_response +soup_xmlrpc_request_new +soup_xmlrpc_set_fault +soup_xmlrpc_set_response +soup_xmlrpc_variant_get_datetime +soup_xmlrpc_variant_new_datetime diff --git a/libsoup/libsoup-gssapi-2.4.sym b/libsoup/libsoup-gssapi-2.4.sym new file mode 100644 index 00000000..2df49d62 --- /dev/null +++ b/libsoup/libsoup-gssapi-2.4.sym @@ -0,0 +1,3 @@ +soup_gss_client_init +soup_gss_client_step +soup_gss_client_cleanup diff --git a/libsoup/soup-auth-negotiate.c b/libsoup/soup-auth-negotiate.c new file mode 100644 index 00000000..79db1075 --- /dev/null +++ b/libsoup/soup-auth-negotiate.c @@ -0,0 +1,437 @@ +/* -*- Mode: C; tabstop: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-auth-negotiate.c: HTTP Negotiate Authentication helper + * + * Copyright (C) 2009,2013 Guido Guenther <agx@sigxcpu.org> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_GSSAPI +# include <gssapi/gssapi.h> +#endif + +#include <string.h> + +#include "soup-auth-negotiate.h" +#include "soup-gssapi.h" +#include "soup-headers.h" +#include "soup-message.h" +#include "soup-message-private.h" +#include "soup-misc.h" +#include "soup-uri.h" + +static gboolean soup_gss_build_response (SoupNegotiateConnectionState *conn, + SoupAuth *auth, GError **err); +static gchar** parse_trusted_uris (void); +static gboolean check_auth_trusted_uri (SoupAuthNegotiate *negotiate, + SoupMessage *msg); + +typedef struct { + char *username; +} SoupAuthNegotiatePrivate; +#define SOUP_AUTH_NEGOTIATE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_NEGOTIATE, SoupAuthNegotiatePrivate)) + +G_DEFINE_TYPE (SoupAuthNegotiate, soup_auth_negotiate, SOUP_TYPE_CONNECTION_AUTH) + +/* Function pointers to dlopen'ed libsoup-gssapi */ +struct { + int (*client_init)(SoupNegotiateConnectionState *conn, + const char *host, + GError **err); + int (*client_step)(SoupNegotiateConnectionState *conn, + const char *challenge, + GError **err); + void (*client_cleanup)(SoupNegotiateConnectionState *conn); +} soup_gssapi_syms; +gboolean have_gssapi; + +static gchar **trusted_uris; + +static void +soup_auth_negotiate_init (SoupAuthNegotiate *negotiate) +{ +} + +static void +soup_auth_negotiate_finalize (GObject *object) +{ + SoupAuthNegotiatePrivate *priv = SOUP_AUTH_NEGOTIATE_GET_PRIVATE (object); + + g_free (priv->username); + + G_OBJECT_CLASS (soup_auth_negotiate_parent_class)->finalize (object); +} + +static gpointer +soup_auth_negotiate_create_connection_state (SoupConnectionAuth *auth) +{ + return g_slice_new0 (SoupNegotiateConnectionState); +} + +static void +soup_auth_negotiate_free_connection_state (SoupConnectionAuth *auth, + gpointer state) +{ + SoupNegotiateConnectionState *conn = state; + + if (have_gssapi) + soup_gssapi_syms.client_cleanup (conn); + g_free (conn->response_header); +} + + +static gboolean +soup_auth_negotiate_update_connection (SoupConnectionAuth *auth, SoupMessage *msg, + const char *header, gpointer state) +{ + SoupAuthNegotiatePrivate *priv = + SOUP_AUTH_NEGOTIATE_GET_PRIVATE (auth); + SoupNegotiateConnectionState *conn = state; + GError *err = NULL; + + if (conn->state > SOUP_NEGOTIATE_RECEIVED_CHALLENGE) { + /* We already authenticated, but then got another 401. + * That means "permission denied", so don't try to + * authenticate again. + */ + conn->state = SOUP_NEGOTIATE_FAILED; + + /* Make sure we don't claim to be authenticated */ + g_free (priv->username); + priv->username = NULL; + + return FALSE; + } + + /* Found negotiate header, start negotiate */ + if (strcmp (header, "Negotiate") == 0) { + conn->state = SOUP_NEGOTIATE_RECEIVED_CHALLENGE; + if (soup_gss_build_response (conn, SOUP_AUTH (auth), &err)) + return TRUE; + else { + /* FIXME: report further upward via + * soup_message_get_error_message */ + g_warning ("gssapi step failed: %s", err->message); + } + } + g_clear_error (&err); + return FALSE; +} + +static GSList * +soup_auth_negotiate_get_protection_space (SoupAuth *auth, SoupURI *source_uri) +{ + char *space, *p; + + space = g_strdup (source_uri->path); + + /* Strip filename component */ + p = strrchr (space, '/'); + if (p && p != space && p[1]) + *p = '\0'; + + return g_slist_prepend (NULL, space); +} + +static void +soup_auth_negotiate_authenticate (SoupAuth *auth, const char *username, + const char *password) +{ + SoupAuthNegotiatePrivate *priv = SOUP_AUTH_NEGOTIATE_GET_PRIVATE (auth); + + g_return_if_fail (username != NULL); + priv->username = g_strdup (username); +} + +static gboolean +soup_auth_negotiate_is_authenticated (SoupAuth *auth) +{ + return SOUP_AUTH_NEGOTIATE_GET_PRIVATE (auth)->username != NULL; +} + +static gboolean +soup_auth_negotiate_is_ready (SoupAuth *auth, + SoupMessage *msg) +{ + SoupAuthNegotiate* negotiate = SOUP_AUTH_NEGOTIATE (auth); + return check_auth_trusted_uri (negotiate, msg); +} + + +static void +check_server_response(SoupMessage *msg, gpointer state) +{ + gchar **parts, *p; + gint ret; + const char *auth_headers; + SoupNegotiateConnectionState *conn = state; + GError *err = NULL; + + if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) + return; + + /* FIXME: need to check for proxy-auth too */ + auth_headers = soup_message_headers_get_one (msg->response_headers, + "WWW-Authenticate"); + parts = g_strsplit (auth_headers, " ", 0); + if (g_strv_length (parts) != 2) { + g_warning ("Failed to parse auth header %s", auth_headers); + conn->state = SOUP_NEGOTIATE_FAILED; + goto out; + } + if (g_ascii_strcasecmp (parts[0], "Negotiate")) { + g_warning ("Failed to parse auth header %s", auth_headers); + conn->state = SOUP_NEGOTIATE_FAILED; + } + + p = parts[1]; + ret = soup_gssapi_syms.client_step (conn, p, &err); + + if (ret != AUTH_GSS_COMPLETE) { + g_warning ("%s", err->message); + conn->state = SOUP_NEGOTIATE_FAILED; + } + out: + g_clear_error (&err); + g_strfreev (parts); +} + + +static void +remove_server_response_handler(SoupMessage *msg, gpointer state) +{ + g_signal_handlers_disconnect_by_func (msg, + G_CALLBACK (check_server_response), + state); +} + + +static char * +soup_auth_negotiate_get_connection_authorization (SoupConnectionAuth *auth, + SoupMessage *msg, + gpointer state) +{ + SoupNegotiateConnectionState *conn = state; + char *header = NULL; + + if (conn->state == SOUP_NEGOTIATE_RECEIVED_CHALLENGE) { + header = conn->response_header; + conn->response_header = NULL; + conn->state = SOUP_NEGOTIATE_SENT_RESPONSE; + } + + + g_signal_connect (msg, + "finished", + G_CALLBACK (remove_server_response_handler), + conn); + + /* Wait for the 2xx response to verify server response */ + g_signal_connect (msg, + "got_headers", + G_CALLBACK (check_server_response), + conn); + + return header; +} + +static gboolean +soup_auth_negotiate_is_connection_ready (SoupConnectionAuth *auth, + SoupMessage *msg, + gpointer state) +{ + SoupNegotiateConnectionState *conn = state; + + return conn->state != SOUP_NEGOTIATE_FAILED; +} + +static gboolean +soup_gssapi_load (void) +{ + GModule *gssapi; + const char *modulename = PACKAGE "-gssapi-2.4." G_MODULE_SUFFIX; + + if (!g_module_supported ()) + return FALSE; + + gssapi = g_module_open (modulename, G_MODULE_BIND_LOCAL); + if (!gssapi) { + g_warning ("Failed to load %s - negotiate support will " + "be disabled.", modulename); + return FALSE; + } + +#define GSSAPI_BIND_SYMBOL(name) \ + g_return_val_if_fail (g_module_symbol (gssapi, "soup_gss_" #name, (gpointer)&soup_gssapi_syms.name), FALSE) + + GSSAPI_BIND_SYMBOL(client_step); + GSSAPI_BIND_SYMBOL(client_init); + GSSAPI_BIND_SYMBOL(client_cleanup); +#undef GSSPI_BIND_SYMBOL + return TRUE; +} + +static void +soup_auth_negotiate_class_init (SoupAuthNegotiateClass *auth_negotiate_class) +{ + SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_negotiate_class); + SoupConnectionAuthClass *connauth_class = + SOUP_CONNECTION_AUTH_CLASS (auth_negotiate_class); + GObjectClass *object_class = G_OBJECT_CLASS (auth_negotiate_class); + + g_type_class_add_private (auth_negotiate_class, sizeof (SoupAuthNegotiatePrivate)); + + auth_class->scheme_name = "Negotiate"; + auth_class->strength = 7; + + auth_class->get_protection_space = soup_auth_negotiate_get_protection_space; + auth_class->authenticate = soup_auth_negotiate_authenticate; + auth_class->is_authenticated = soup_auth_negotiate_is_authenticated; + auth_class->is_ready = soup_auth_negotiate_is_ready; + + connauth_class->create_connection_state = soup_auth_negotiate_create_connection_state; + connauth_class->free_connection_state = soup_auth_negotiate_free_connection_state; + connauth_class->update_connection = soup_auth_negotiate_update_connection; + connauth_class->get_connection_authorization = soup_auth_negotiate_get_connection_authorization; + connauth_class->is_connection_ready = soup_auth_negotiate_is_connection_ready; + + object_class->finalize = soup_auth_negotiate_finalize; + + trusted_uris = parse_trusted_uris (); + have_gssapi = soup_gssapi_load(); +} + +static gboolean +soup_gss_build_response (SoupNegotiateConnectionState *conn, SoupAuth *auth, GError **err) +{ + if (!have_gssapi) { + if (err && *err == NULL) { + g_set_error (err, + SOUP_HTTP_ERROR, + SOUP_STATUS_GSSAPI_FAILED, + "GSSAPI unavailable"); + } + return FALSE; + } + + if (!soup_gssapi_syms.client_init (conn, soup_auth_get_host (SOUP_AUTH (auth)), err)) + return FALSE; + + if (soup_gssapi_syms.client_step (conn, "", err) != AUTH_GSS_CONTINUE) + return FALSE; + + return TRUE; +} + +/* Parses a comma separated list of URIS from the environment. */ +static gchar** +parse_trusted_uris(void) +{ + gchar **uris = NULL; + const gchar *env; + + env = g_getenv ("SOUP_AUTH_TRUSTED_URIS"); + if (env) + uris = g_strsplit (env, ",", -1); + return uris; +} + + +/* check if scheme://host:port from msg matches the trusted uri */ +static gboolean +match_base_uri (SoupMessage *msg, const gchar *trusted) +{ + SoupURI *uri; + gboolean ret = FALSE; + + /* params of the trusted uri */ + gchar **trusted_parts = NULL; + gchar **trusted_host_port = NULL; + const gchar *trusted_host = NULL; + gint trusted_host_len; + + /* params of the msg's uri */ + const gchar *host = NULL; + gint port; + gint host_len; + + uri = soup_message_get_uri (msg); + /* split trusted uri into scheme and host/port */ + if (strstr (trusted, "://")) { + trusted_parts = g_strsplit (trusted, "://", -1); + + /* The scheme has to match exactly */ + if (g_ascii_strcasecmp (trusted_parts[0], + soup_uri_get_scheme (uri))) { + goto out; + } + if (strlen (trusted_parts[1]) == 0) { + /* scheme only, we're done */ + ret = TRUE; + goto out; + } else + trusted_host = trusted_parts[1]; + } else { + trusted_host = trusted; + } + + trusted_host_port = g_strsplit (trusted_host, ":", 2); + /* If we got a port in the trusted uri it has to match exactly */ + if (g_strv_length (trusted_host_port) > 1) { + port = g_ascii_strtoll (trusted_host_port[1], NULL, 10); + if (port != soup_uri_get_port (uri)) { + goto out; + } + } + + trusted_host = trusted_host_port[0]; + host = soup_uri_get_host (uri); + if (g_str_has_suffix (host, trusted_host)) { + /* if the msg host ends with host from the trusted uri, then make + * sure it is either an exact match, or prefixed with a dot. We + * don't want "foobar.com" to match "bar.com" + */ + if (g_ascii_strcasecmp (host, trusted_host) == 0) { + ret = TRUE; + goto out; + } else { + /* we don't want example.com to match fooexample.com + */ + trusted_host_len = strlen (trusted_host); + host_len = strlen (host); + if (host[host_len - trusted_host_len - 1] == '.') { + ret = TRUE; + } + } + } +out: + g_strfreev (trusted_parts); + g_strfreev (trusted_host_port); + return ret; +} + + +static gboolean +check_auth_trusted_uri (SoupAuthNegotiate *negotiate, SoupMessage *msg) +{ + SoupAuthNegotiatePrivate *priv = + SOUP_AUTH_NEGOTIATE_GET_PRIVATE (negotiate); + int i; + + g_return_val_if_fail (negotiate != NULL, FALSE); + g_return_val_if_fail (priv != NULL, FALSE); + g_return_val_if_fail (msg != NULL, FALSE); + + if (!trusted_uris) { + return FALSE; + } + + for (i=0; i < g_strv_length (trusted_uris); i++) { + if (match_base_uri (msg, trusted_uris[i])) + return TRUE; + } + return FALSE; +} diff --git a/libsoup/soup-auth-negotiate.h b/libsoup/soup-auth-negotiate.h new file mode 100644 index 00000000..dbdfc3cb --- /dev/null +++ b/libsoup/soup-auth-negotiate.h @@ -0,0 +1,27 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2010 Guido Guenther <agx@sigxcpu.org> + */ + +#ifndef SOUP_AUTH_NEGOTIATE_H +#define SOUP_AUTH_NEGOTIATE_H 1 + +#include "soup-connection-auth.h" + +#define SOUP_AUTH_NEGOTIATE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_AUTH_NEGOTIATE, SoupAuthNegotiate)) +#define SOUP_AUTH_NEGOTIATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_NEGOTIATE, SoupAuthNegotiateClass)) +#define SOUP_IS_AUTH_NEGOTIATE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_AUTH_NEGOTIATE)) +#define SOUP_IS_AUTH_NEGOTIATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_AUTH_NEGOTIATE)) +#define SOUP_AUTH_NEGOTIATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_NEGOTIATE, SoupAuthNegotiateClass)) + +typedef struct { + SoupConnectionAuth parent; + +} SoupAuthNegotiate; + +typedef struct { + SoupConnectionAuthClass parent_class; + +} SoupAuthNegotiateClass; + +#endif /* SOUP_AUTH_NEGOTIATE_H */ diff --git a/libsoup/soup-auth.h b/libsoup/soup-auth.h index d2e6a100..75ca01c2 100644 --- a/libsoup/soup-auth.h +++ b/libsoup/soup-auth.h @@ -115,6 +115,8 @@ GType soup_auth_digest_get_type (void); #define SOUP_TYPE_AUTH_NTLM (soup_auth_ntlm_get_type ()) SOUP_AVAILABLE_IN_2_4 GType soup_auth_ntlm_get_type (void); +#define SOUP_TYPE_AUTH_NEGOTIATE (soup_auth_negotiate_get_type ()) +GType soup_auth_negotiate_get_type (void); /* Deprecated SoupPasswordManager-related APIs: all are now no-ops */ SOUP_AVAILABLE_IN_2_28 diff --git a/libsoup/soup-gssapi.c b/libsoup/soup-gssapi.c new file mode 100644 index 00000000..78aaa773 --- /dev/null +++ b/libsoup/soup-gssapi.c @@ -0,0 +1,169 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-gssapi.c: GSSAPI related functions + * + * Copyright (C) 2013 Guido Guenther <agx@sigxcpu.org> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <soup-status.h> +#include <soup-gssapi.h> + +#include <gssapi/gssapi.h> +#include <gssapi/gssapi_krb5.h> + +int +soup_gss_client_init (SoupNegotiateConnectionState *conn, const char *host, GError **err); + +int +soup_gss_client_step (SoupNegotiateConnectionState *conn, const char *host, GError **err); + +void +soup_gss_client_cleanup (SoupNegotiateConnectionState *conn); + +static void +soup_gss_error (OM_uint32 err_maj, OM_uint32 err_min, GError **err) +{ + OM_uint32 maj_stat, min_stat, msg_ctx = 0; + gss_buffer_desc status; + char *buf_maj = NULL, *buf_min = NULL; + + do { + maj_stat = gss_display_status (&min_stat, + err_maj, + GSS_C_GSS_CODE, + GSS_C_NO_OID, + &msg_ctx, + &status); + if (GSS_ERROR (maj_stat)) + break; + + buf_maj = g_strdup ((char*) status.value); + gss_release_buffer (&min_stat, &status); + + maj_stat = gss_display_status (&min_stat, + err_min, + GSS_C_MECH_CODE, + GSS_C_NULL_OID, + &msg_ctx, + &status); + if (!GSS_ERROR (maj_stat)) { + buf_min = g_strdup ((char*) status.value); + gss_release_buffer (&min_stat, &status); + } + + if (err && *err == NULL) { + g_set_error (err, + SOUP_HTTP_ERROR, + SOUP_STATUS_GSSAPI_FAILED, + "%s %s", + buf_maj, + buf_min ? buf_min : ""); + } + g_free (buf_maj); + g_free (buf_min); + buf_min = buf_maj = NULL; + } while (!GSS_ERROR (maj_stat) && msg_ctx != 0); +} + +G_MODULE_EXPORT int +soup_gss_client_init (SoupNegotiateConnectionState *conn, const char *host, GError **err) +{ + OM_uint32 maj_stat, min_stat; + char *service = NULL; + gss_buffer_desc token = GSS_C_EMPTY_BUFFER; + gboolean ret = FALSE; + gchar *h; + + conn->server_name = GSS_C_NO_NAME; + conn->context = GSS_C_NO_CONTEXT; + + h = g_ascii_strdown (host, -1); + service = g_strconcat ("HTTP/", h, NULL); + token.length = strlen (service); + token.value = (char *)service; + + maj_stat = gss_import_name (&min_stat, + &token, + (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME, + &conn->server_name); + + if (GSS_ERROR (maj_stat)) { + soup_gss_error (maj_stat, min_stat, err); + ret = FALSE; + goto out; + } + + ret = TRUE; +out: + g_free (h); + g_free (service); + return ret; +} + +G_MODULE_EXPORT int +soup_gss_client_step (SoupNegotiateConnectionState *conn, const char *challenge, GError **err) +{ + OM_uint32 maj_stat, min_stat; + gss_buffer_desc in = GSS_C_EMPTY_BUFFER; + gss_buffer_desc out = GSS_C_EMPTY_BUFFER; + int ret = AUTH_GSS_CONTINUE; + + g_clear_pointer (&conn->response_header, g_free); + + if (challenge && *challenge) { + size_t len; + in.value = g_base64_decode (challenge, &len); + in.length = len; + } + + maj_stat = gss_init_sec_context (&min_stat, + GSS_C_NO_CREDENTIAL, + &conn->context, + conn->server_name, + GSS_C_NO_OID, + GSS_C_MUTUAL_FLAG, + GSS_C_INDEFINITE, + GSS_C_NO_CHANNEL_BINDINGS, + &in, + NULL, + &out, + NULL, + NULL); + + if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED)) { + soup_gss_error (maj_stat, min_stat, err); + ret = AUTH_GSS_ERROR; + goto out; + } + + ret = (maj_stat == GSS_S_COMPLETE) ? AUTH_GSS_COMPLETE : AUTH_GSS_CONTINUE; + if (out.length) { + char *response = g_base64_encode ((const unsigned char *)out.value, out.length); + conn->response_header = g_strconcat ("Negotiate ", response, NULL); + g_free (response); + maj_stat = gss_release_buffer (&min_stat, &out); + } + +out: + if (out.value) + gss_release_buffer (&min_stat, &out); + if (in.value) + g_free (in.value); + return ret; +} + + +G_MODULE_EXPORT void +soup_gss_client_cleanup (SoupNegotiateConnectionState *conn) +{ + OM_uint32 min_stat; + + gss_release_name (&min_stat, &conn->server_name); +} + diff --git a/libsoup/soup-gssapi.h b/libsoup/soup-gssapi.h new file mode 100644 index 00000000..05a2b48f --- /dev/null +++ b/libsoup/soup-gssapi.h @@ -0,0 +1,41 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-auth-negotiate.c: HTTP Negotiate Authentication helper + * + * Copyright (C) 2013 Guido Guenther <agx@sigxcpu.org> + */ + +#ifndef SOUP_GSSAPI_H +#define SOUP_GSSAPI_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifdef HAVE_GSSAPI +# include <gssapi/gssapi.h> +#endif + +#define AUTH_GSS_ERROR -1 +#define AUTH_GSS_COMPLETE 1 +#define AUTH_GSS_CONTINUE 0 + +typedef enum { + SOUP_NEGOTIATE_NEW, + SOUP_NEGOTIATE_RECEIVED_CHALLENGE, /* received intial negotiate header */ + SOUP_NEGOTIATE_SENT_RESPONSE, /* sent response to server */ + SOUP_NEGOTIATE_FAILED +} SoupNegotiateState; + +typedef struct { + SoupNegotiateState state; + +#ifdef HAVE_GSSAPI + gss_ctx_id_t context; + gss_name_t server_name; +#endif + + char *response_header; +} SoupNegotiateConnectionState; + +#endif /* SOUP_GSSAPI_H */ diff --git a/libsoup/soup-status.h b/libsoup/soup-status.h index 28d481db..0ff7405b 100644 --- a/libsoup/soup-status.h +++ b/libsoup/soup-status.h @@ -34,6 +34,7 @@ typedef enum { SOUP_STATUS_TRY_AGAIN, SOUP_STATUS_TOO_MANY_REDIRECTS, SOUP_STATUS_TLS_FAILED, + SOUP_STATUS_GSSAPI_FAILED, /* HTTP Status Codes */ SOUP_STATUS_CONTINUE = 100, |